From d80caac14cbb7c2f76f7e0188b1f95350129d1b4 Mon Sep 17 00:00:00 2001 From: Andy Yin Date: Fri, 21 Nov 2025 10:22:38 -0800 Subject: [PATCH] feat: Custom property classes --- lib/literal/properties.rb | 3 +- test/properties/custom_property_class.test.rb | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 test/properties/custom_property_class.test.rb diff --git a/lib/literal/properties.rb b/lib/literal/properties.rb index 1bc5b50..56996ef 100644 --- a/lib/literal/properties.rb +++ b/lib/literal/properties.rb @@ -13,7 +13,7 @@ def self.extended(base) base.include(base.__send__(:__literal_extension__)) end - def prop(name, type, kind = :keyword, reader: false, writer: false, predicate: false, default: nil, &coercion) + def prop(name, type, kind = :keyword, reader: false, writer: false, predicate: false, default: nil, **kwargs, &coercion) if default && !(Proc === default || default.frozen?) raise Literal::ArgumentError.new("The default must be a frozen object or a Proc.") end @@ -48,6 +48,7 @@ def prop(name, type, kind = :keyword, reader: false, writer: false, predicate: f writer:, predicate:, default:, + **kwargs, coercion:, ) diff --git a/test/properties/custom_property_class.test.rb b/test/properties/custom_property_class.test.rb new file mode 100644 index 0000000..57debc6 --- /dev/null +++ b/test/properties/custom_property_class.test.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +class DescriptionProperty < Literal::Property + def initialize(description: nil, **kwargs) + super(**kwargs) + + @description = description + end + + attr_reader :description +end + +module DescriptionProperties + def prop(name, type, kind = :keyword, description: nil, **, &coercion) + super + end + + def __literal_property_class__ + DescriptionProperty + end +end + +Example = Literal::Object + +test "custom property class can accept additional keyword arguments" do + example = Class.new(Example) do + extend DescriptionProperties + + prop :name, String, description: "The person's name" + prop :age, Integer, description: "The person's age" + end + + name_prop = example.literal_properties[:name] + age_prop = example.literal_properties[:age] + + assert_equal name_prop.description, "The person's name" + assert_equal age_prop.description, "The person's age" + assert_equal name_prop.class, DescriptionProperty + assert_equal age_prop.class, DescriptionProperty +end + +test "custom property class works with all property features" do + example = Class.new(Example) do + extend DescriptionProperties + + prop :name, String, description: "Name", reader: :public + prop :age, Integer, description: "Age", default: 0, reader: :public + end + + object = example.new(name: "John") + assert_equal object.name, "John" + assert_equal object.age, 0 + + name_prop = example.literal_properties[:name] + age_prop = example.literal_properties[:age] + + assert_equal name_prop.description, "Name" + assert_equal age_prop.description, "Age" +end + +test "custom property class with nil description" do + example = Class.new(Example) do + extend DescriptionProperties + + prop :name, String + end + + name_prop = example.literal_properties[:name] + assert_equal name_prop.description, nil +end + +test "invalid keyword arguments on base Literal::Properties raise ArgumentError" do + example = Class.new(Example) + + assert_raises(ArgumentError) do + example.prop :name, String, description: "This should fail" + end +end + +test "invalid keyword arguments on base Literal::Properties raise ArgumentError for other unknown args" do + example = Class.new(Example) + + assert_raises(ArgumentError) do + example.prop :name, String, unknown_arg: "This should fail" + end +end