lutaml-model 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99e9857c1f8a86d0fb3566c6d9e5e11723e203e7f58bd7fcfd3c5a0cb1702b6c
4
- data.tar.gz: 270002530a82256d2682a060a9616320b76f9592aeb74d17451d26d311a45508
3
+ metadata.gz: 16b8569b5f2b450dfa8218c1c55a2175f154790220a54536bc9acbf6bc718312
4
+ data.tar.gz: d9d62e793690a72856e2663a67eb4eeb9cd8b909a861a91b093d8b849674a983
5
5
  SHA512:
6
- metadata.gz: f1d98496343787aad60a34ee00c7df537b9fcd4160209cdd920d8265afdc048e7a26dbc3567b30ed1a5f5eb8120684b81dc9bb04a8964fd4495be67ae339bf5e
7
- data.tar.gz: d51b92c1bda980ac8f30890236329880db515eb11e20e73a647dc78cdba2de58fc869d58a5905a20681f4b946dff205968f9f1cf619bac10aa677157497f05ff
6
+ metadata.gz: 5e491d76d913445b9929b4e1939becc31c4b1ccb656aeaaa3e83caa20bf3c5e53142c3cc45f1a82b0a85d4b7111ab6994d5a5c03588c4f42dd8f76d101c95db0
7
+ data.tar.gz: e18a44f0047929b93c2bc66c82f8942d3cf48818c6597722bba0b78107c2630aa59f84b3b4034a105155d889e64fa7e8f11881fbc9eca076b3103c21f8f189cf
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-02-13 10:44:38 UTC using RuboCop version 1.71.2.
3
+ # on 2025-02-15 02:54:02 UTC using RuboCop version 1.71.2.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -138,7 +138,7 @@ RSpec/DescribedClass:
138
138
  Exclude:
139
139
  - 'spec/lutaml/model/xml_mapping_spec.rb'
140
140
 
141
- # Offense count: 152
141
+ # Offense count: 157
142
142
  # Configuration parameters: CountAsOne.
143
143
  RSpec/ExampleLength:
144
144
  Max: 54
@@ -149,7 +149,7 @@ RSpec/LeakyConstantDeclaration:
149
149
  - 'spec/benchmarks/xml_parsing_benchmark_spec.rb'
150
150
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
151
151
 
152
- # Offense count: 206
152
+ # Offense count: 208
153
153
  RSpec/MultipleExpectations:
154
154
  Max: 14
155
155
 
@@ -163,13 +163,12 @@ RSpec/MultipleMemoizedHelpers:
163
163
  RSpec/NestedGroups:
164
164
  Max: 4
165
165
 
166
- # Offense count: 8
166
+ # Offense count: 5
167
167
  RSpec/PendingWithoutReason:
168
168
  Exclude:
169
169
  - 'spec/lutaml/model/type/date_time_spec.rb'
170
170
  - 'spec/lutaml/model/type/time_spec.rb'
171
171
  - 'spec/lutaml/model/type/time_without_date_spec.rb'
172
- - 'spec/lutaml/model/validation_spec.rb'
173
172
 
174
173
  # Offense count: 3
175
174
  # Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata.
data/Gemfile CHANGED
@@ -8,6 +8,7 @@ gemspec
8
8
  gem "benchmark-ips"
9
9
  gem "bigdecimal"
10
10
  gem "equivalent-xml"
11
+ gem "liquid"
11
12
  gem "lutaml-xsd"
12
13
  gem "multi_json"
13
14
  gem "nokogiri"
data/README.adoc CHANGED
@@ -413,9 +413,11 @@ end
413
413
 
414
414
  ==== Decimal type
415
415
 
416
- The Decimal type is an optional type that is disabled by default.
416
+ WARNING: Decimal is an optional feature.
417
417
 
418
- NOTE: The reason why the Decimal type is disalbed by default is that the
418
+ The Decimal type is a value type that is disabled by default.
419
+
420
+ NOTE: The reason why the Decimal type is disabled by default is that the
419
421
  `BigDecimal` class became optional to the standard Ruby library from Ruby 3.4
420
422
  onwards. The `Decimal` type is only enabled when the `bigdecimal` library is
421
423
  loaded.
@@ -693,7 +695,7 @@ end
693
695
  Lutaml lets you create reusable element and attribute collections using `no_root`. These can be imported into other models using:
694
696
 
695
697
  - `import_model`: imports both attributes and mappings
696
- - `import_model_attributes`: imports only attributes
698
+ - `import_model_attributes`: imports only attributes
697
699
  - `import_model_mappings`: imports only mappings
698
700
 
699
701
  NOTE: This feature works with XML. Import order determines how elements and attributes are overwritten.
@@ -3898,7 +3900,7 @@ class Person < Lutaml::Model::Serializable
3898
3900
  }
3899
3901
  end
3900
3902
 
3901
- # Mapping-level transformation in XML format
3903
+ # Mapping-level transformation in XML format
3902
3904
  xml do
3903
3905
  map "full-name", to: :name, transform: {
3904
3906
  export: ->(value) { "Dr. #{value}" },
@@ -4098,39 +4100,39 @@ NOTE: For `NokogiriAdapter`, we can also call `to_xml` on `value.node.adapter_no
4098
4100
 
4099
4101
  # Nokogiri Adapter Node
4100
4102
 
4101
- #<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656ed8
4102
- # @attributes={},
4103
- # @children=
4103
+ #<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656ed8
4104
+ # @attributes={},
4105
+ # @children=
4104
4106
  # [#<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656cd0 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">,
4105
- # #<Lutaml::Model::XmlAdapter::NokogiriElement:0x00000001076569b0
4106
- # @attributes={},
4107
- # @children=
4107
+ # #<Lutaml::Model::XmlAdapter::NokogiriElement:0x00000001076569b0
4108
+ # @attributes={},
4109
+ # @children=
4108
4110
  # [#<Lutaml::Model::XmlAdapter::NokogiriElement:0x00000001076567f8 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="Metadata">],
4109
- # @default_namespace=nil,
4110
- # @name="category",
4111
- # @namespace_prefix=nil,
4112
- # @text="Metadata">,
4111
+ # @default_namespace=nil,
4112
+ # @name="category",
4113
+ # @namespace_prefix=nil,
4114
+ # @text="Metadata">,
4113
4115
  # #<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656028 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">],
4114
- # @default_namespace=nil,
4116
+ # @default_namespace=nil,
4115
4117
  # @name="metadata",
4116
4118
  # @namespace_prefix=nil,
4117
4119
  # @text="\n Metadata\n ">
4118
4120
 
4119
4121
  # Ox Adapter Node
4120
4122
 
4121
- #<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584f78
4122
- # @attributes={},
4123
- # @children=
4124
- # [#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584e60
4125
- # @attributes={},
4123
+ #<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584f78
4124
+ # @attributes={},
4125
+ # @children=
4126
+ # [#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584e60
4127
+ # @attributes={},
4126
4128
  # @children=[#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584d48 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="Metadata">],
4127
- # @default_namespace=nil,
4128
- # @name="category",
4129
- # @namespace_prefix=nil,
4130
- # @text="Metadata">],
4131
- # @default_namespace=nil,
4132
- # @name="metadata",
4133
- # @namespace_prefix=nil,
4129
+ # @default_namespace=nil,
4130
+ # @name="category",
4131
+ # @namespace_prefix=nil,
4132
+ # @text="Metadata">],
4133
+ # @default_namespace=nil,
4134
+ # @name="metadata",
4135
+ # @namespace_prefix=nil,
4134
4136
  # @text=nil>
4135
4137
 
4136
4138
  # Oga Adapter Node
@@ -4190,7 +4192,7 @@ class CustomModelParentMapper < Lutaml::Model::Serializable
4190
4192
  map_element :CustomModelChild,
4191
4193
  with: { to: :child_to_xml, from: :child_from_xml }
4192
4194
  end
4193
-
4195
+
4194
4196
  def child_to_xml(model, parent, doc)
4195
4197
  child_el = doc.create_element("CustomModelChild")
4196
4198
  street_el = doc.create_element("street")
@@ -4227,7 +4229,7 @@ end
4227
4229
  [source,ruby]
4228
4230
  ----
4229
4231
  > instance = CustomModelParentMapper.from_xml(xml)
4230
- > #<CustomModelParent:0x0000000107c9ca68 @child_mapper=#<CustomModelChild:0x0000000107c95218 @city="London", @street="Oxford Street">, @first_name="John">
4232
+ > #<CustomModelParent:0x0000000107c9ca68 @child_mapper=#<CustomModelChild:0x0000000107c95218 @city="London", @street="Oxford Street">, @first_name="John">
4231
4233
  > CustomModelParentMapper.to_xml(instance)
4232
4234
  > #<CustomModelParent><first_name>John</first_name><CustomModelChild><street>Oxford Street</street><city>London</city></CustomModelChild></CustomModelParent>
4233
4235
  ----
@@ -4595,27 +4597,228 @@ klin.validate
4595
4597
  ====
4596
4598
 
4597
4599
 
4598
- == Liquid Compatability
4600
+ == Liquid template access
4601
+
4602
+ WARNING: The Liquid template feature is optional. To enable it, please
4603
+ explicitly require the `liquid` gem.
4604
+
4605
+ The https://shopify.github.io/liquid/[Liquid template language] is an
4606
+ open-source template language developed by Shopify and written in Ruby.
4607
+
4608
+ `Lutaml::Model::Serializable` objects can be safely accessed within Liquid
4609
+ templates through a `to_liquid` method that converts the objects into
4610
+ `Liquid::Drop` instances.
4599
4611
 
4600
- `to_liquid` can be used to convert a class that inherit from *Lutaml::Model::Serializable* to `LiquidDrop` to be safely used in liquid templates. The returned drop provides all the attributes defined in the class as methods.
4612
+ * All attributes are accessible in the Liquid template by their names.
4613
+ * Nested attributes are also converted into `Liquid::Drop` objects so
4614
+ inner attributes can be accessed using the Liquid dot notation.
4615
+
4616
+ NOTE: Every `Lutaml::Model::Serializable` class extends the `Liquefiable` module
4617
+ which generates a corresponding `Liquid::Drop` class.
4618
+
4619
+ NOTE: Methods defined in the `Lutaml::Model::Serializable` class are not
4620
+ accessible in the Liquid template.
4621
+
4622
+ .Using `to_liquid` to convert model instances into corresponding Liquid drop instances
4601
4623
 
4602
4624
  [example]
4603
4625
  ====
4604
4626
  [source,ruby]
4605
4627
  ----
4606
- class Person < Lutaml::Model::Serializable
4628
+ class Ceramic < Lutaml::Model::Serializable
4629
+ attribute :name, :string
4630
+ attribute :temperature, :integer
4631
+ end
4632
+
4633
+ ceramic = Ceramic.new({ name: "Porcelain Vase", temperature: 1200 })
4634
+ ceramic_drop = ceramic.to_liquid
4635
+ # Ceramic::CeramicDrop
4636
+
4637
+ puts ceramic_drop.name
4638
+ # "Porcelain Vase"
4639
+ puts ceramic_drop.temperature
4640
+ # 1200
4641
+ ----
4642
+ ====
4643
+
4644
+ .Accessing LutaML::Model objects within a Liquid template
4645
+ [example]
4646
+ ====
4647
+ [source,ruby]
4648
+ ----
4649
+ class Ceramic < Lutaml::Model::Serializable
4650
+ attribute :name, :string
4651
+ attribute :temperature, :integer
4652
+ end
4653
+
4654
+ class CeramicCollection < Lutaml::Model::Serializable
4655
+ attribute :ceramics, Ceramic, collection: true
4656
+ end
4657
+ ----
4658
+
4659
+ `sample.yml`:
4660
+
4661
+ [source,yaml]
4662
+ ----
4663
+ ---
4664
+ ceramics:
4665
+ - name: Porcelain Vase
4666
+ temperature: 1200
4667
+ - name: Earthenware Pot
4668
+ temperature: 950
4669
+ - name: Stoneware Jug
4670
+ temperature: 1200
4671
+ ----
4672
+
4673
+ `template.liquid`:
4674
+
4675
+ [source,liquid]
4676
+ ----
4677
+ {% for ceramic in ceramic_collection.ceramics %}
4678
+ * Name: "{{ ceramic.name }}"
4679
+ ** Temperature: {{ ceramic.temperature }}
4680
+ {%- endfor %}
4681
+ ----
4682
+
4683
+ [source,ruby]
4684
+ ----
4685
+ # Load the Lutaml::Model collection
4686
+ ceramic_collection = CeramicCollection.from_yaml(File.read("sample.yml"))
4687
+
4688
+ # Load the Liquid template
4689
+ template = Liquid::Template.parse(File.read("template.liquid"))
4690
+
4691
+ # Pass the Lutaml::Model collection to the Liquid template and render
4692
+ output = template.render("ceramic_collection" => ceramic_collection)
4693
+ puts output
4694
+ # >
4695
+ # * Name: "Porcelain Vase"
4696
+ # ** Temperature: 1200
4697
+ # * Name: "Earthenware Pot"
4698
+ # ** Temperature: 950
4699
+ # * Name: "Stoneware Jug"
4700
+ # ** Temperature: 1200
4701
+ ----
4702
+ ====
4703
+
4704
+ .Accessing nested LutaML::Model objects within nested Liquid templates
4705
+ [example]
4706
+ ====
4707
+ [source,ruby]
4708
+ ----
4709
+ class Glaze < Lutaml::Model::Serializable
4710
+ attribute :color, :string
4711
+ attribute :opacity, :string
4712
+ end
4713
+
4714
+ class CeramicWork < Lutaml::Model::Serializable
4607
4715
  attribute :name, :string
4608
- attribute :age, integer
4716
+ attribute :glaze, Glaze
4717
+ end
4718
+
4719
+ class CeramicCollection < Lutaml::Model::Serializable
4720
+ attribute :ceramics, Ceramic, collection: true
4609
4721
  end
4610
4722
 
4611
- person = Person.new({ name: "John", age: 22 })
4612
- person_drop = person.to_liquid
4613
- # Person::PersonDrop
4723
+ ceramic_work = CeramicWork.new({
4724
+ name: "Celadon Bowl",
4725
+ glaze: Glaze.new({
4726
+ color: "Jade Green",
4727
+ opacity: "Translucent"
4728
+ })
4729
+ })
4730
+ ceramic_work_drop = ceramic_work.to_liquid
4731
+ # CeramicWork::CeramicWorkDrop
4732
+
4733
+ puts ceramic_work_drop.name
4734
+ # "Celadon Bowl"
4735
+ puts ceramic_work_drop.glaze.color
4736
+ # "Jade Green"
4737
+ puts ceramic_work_drop.glaze.opacity
4738
+ # "Translucent"
4739
+ ----
4740
+
4741
+ `ceramics.yml`:
4742
+
4743
+ [source,yaml]
4744
+ ----
4745
+ ---
4746
+ ceramics:
4747
+ - name: Celadon Bowl
4748
+ glaze:
4749
+ color: Jade Green
4750
+ opacity: Translucent
4751
+ - name: Earthenware Pot
4752
+ glaze:
4753
+ color: Rust Red
4754
+ opacity: Opaque
4755
+ - name: Stoneware Jug
4756
+ glaze:
4757
+ color: Cobalt Blue
4758
+ opacity: Transparent
4759
+ ----
4760
+
4761
+
4762
+ `templates/_ceramics.liquid`:
4763
+
4764
+ [source,liquid]
4765
+ ----
4766
+ {% for ceramic in ceramic_collection.ceramics %}
4767
+ {% render 'ceramic' ceramic: ceramic %}
4768
+ {%- endfor %}
4769
+ ----
4770
+
4771
+ NOTE: `render` is a Liquid tag that renders a partial template, by default
4772
+ Liquid uses the pattern `_%s.liquid` to find the partial template. Here
4773
+ `ceramic` refers to the file at `templates/_ceramic.liquid`.
4774
+
4775
+ `templates/_ceramic.liquid`:
4776
+
4777
+ [source,liquid]
4778
+ ----
4779
+ * Name: "{{ ceramic.name }}"
4780
+ ** Temperature: {{ ceramic.temperature }}
4781
+ {%- if ceramic.glaze %}
4782
+ ** Glaze (color): {{ ceramic.glaze.color }}
4783
+ ** Glaze (opacity): {{ ceramic.glaze.opacity }}
4784
+ {%- endif %}
4785
+ ----
4786
+
4787
+ [source,ruby]
4788
+ ----
4789
+ require 'liquid'
4790
+
4791
+ # Create a Liquid template object that supports dynamic loading
4792
+ template = Liquid::Template.new
4793
+
4794
+ # Link the Liquid template object to a "local file system" (directory)
4795
+ file_system = Liquid::LocalFileSystem.new('templates/')
4796
+ template.registers[:file_system] = file_system
4797
+
4798
+ # Load the partial template, this is necessary.
4799
+ # This will also allow Liquid to load any inner partials from the file system
4800
+ # dynamically (see `file_system.pattern` to see what it loads)
4801
+ template.parse(file_system.read_template_file('ceramics'))
4802
+
4803
+ # Read the lutaml-model collection
4804
+ ceramic_collection = CeramicCollection.from_yaml(File.read("ceramics.yml"))
4614
4805
 
4615
- puts person_drop.name
4616
- # "John"
4617
- puts person_drop.age
4618
- # 22
4806
+ # Render the template with the collection
4807
+ output = template.render("ceramic_collection" => ceramic_collection)
4808
+ puts output
4809
+ # >
4810
+ # * Name: "Celadon Bowl"
4811
+ # ** Temperature: 1200
4812
+ # ** Glaze (color): Jade Green
4813
+ # ** Glaze (finish): Translucent
4814
+ # * Name: "Earthenware Pot"
4815
+ # ** Temperature: 950
4816
+ # ** Glaze (color): Rust Red
4817
+ # ** Glaze (finish): Opaque
4818
+ # * Name: "Stoneware Jug"
4819
+ # ** Temperature: 1200
4820
+ # ** Glaze (color): Cobalt Blue
4821
+ # ** Glaze (finish): Transparent
4619
4822
  ----
4620
4823
  ====
4621
4824
 
@@ -0,0 +1,9 @@
1
+ module Lutaml
2
+ module Model
3
+ class LiquidNotEnabledError < Error
4
+ def to_s
5
+ "Liquid functionality is not available by default; please install and require `liquid` gem to use this functionality"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -6,6 +6,7 @@ module Lutaml
6
6
  end
7
7
 
8
8
  require_relative "error/invalid_value_error"
9
+ require_relative "error/liquid_not_enabled_error"
9
10
  require_relative "error/incorrect_mapping_argument_error"
10
11
  require_relative "error/pattern_not_matched_error"
11
12
  require_relative "error/unknown_adapter_type_error"
@@ -1,5 +1,3 @@
1
- require "liquid"
2
-
3
1
  module Lutaml
4
2
  module Model
5
3
  module Liquefiable
@@ -9,6 +7,7 @@ module Lutaml
9
7
 
10
8
  module ClassMethods
11
9
  def register_liquid_drop_class
10
+ validate_liquid!
12
11
  if drop_class
13
12
  raise "#{drop_class_name} Already exists!"
14
13
  end
@@ -38,6 +37,7 @@ module Lutaml
38
37
 
39
38
  def register_drop_method(method_name)
40
39
  register_liquid_drop_class unless drop_class
40
+ return if drop_class.method_defined?(method_name)
41
41
 
42
42
  drop_class.define_method(method_name) do
43
43
  value = @object.public_send(method_name)
@@ -49,9 +49,22 @@ module Lutaml
49
49
  end
50
50
  end
51
51
  end
52
+
53
+ def validate_liquid!
54
+ return if Object.const_defined?(:Liquid)
55
+
56
+ raise Lutaml::Model::LiquidNotEnabledError
57
+ end
52
58
  end
53
59
 
54
60
  def to_liquid
61
+ self.class.validate_liquid!
62
+
63
+ if is_a?(Lutaml::Model::Serializable)
64
+ self.class.attributes.each_key do |attr_name|
65
+ self.class.register_drop_method(attr_name)
66
+ end
67
+ end
55
68
  self.class.drop_class.new(self)
56
69
  end
57
70
  end
@@ -117,8 +117,6 @@ module Lutaml
117
117
  end
118
118
  end
119
119
 
120
- register_drop_method(name)
121
-
122
120
  attr
123
121
  end
124
122
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.6.3"
5
+ VERSION = "0.6.4"
6
6
  end
7
7
  end
@@ -0,0 +1,6 @@
1
+ * Name: "{{ ceramic.name }}"
2
+ ** Temperature: {{ ceramic.temperature }}
3
+ {%- if ceramic.glaze %}
4
+ ** Glaze (color): {{ ceramic.glaze.color }}
5
+ ** Glaze (opacity): {{ ceramic.glaze.opacity }}
6
+ {%- endif %}
@@ -0,0 +1,3 @@
1
+ {% for ceramic in ceramic_collection.ceramics %}
2
+ {% render 'ceramic' ceramic: ceramic %}
3
+ {%- endfor %}
@@ -0,0 +1,4 @@
1
+ {% for ceramic in ceramic_collection.ceramics %}
2
+ * Name: "{{ ceramic.name }}"
3
+ ** Temperature: {{ ceramic.temperature }}
4
+ {%- endfor %}
@@ -1,4 +1,5 @@
1
1
  require "spec_helper"
2
+ require "liquid"
2
3
  require_relative "../../fixtures/address"
3
4
 
4
5
  class LiquefiableClass
@@ -16,6 +17,23 @@ class LiquefiableClass
16
17
  end
17
18
  end
18
19
 
20
+ module LiquefiableSpec
21
+ class Glaze < Lutaml::Model::Serializable
22
+ attribute :color, :string
23
+ attribute :opacity, :string
24
+ end
25
+
26
+ class Ceramic < Lutaml::Model::Serializable
27
+ attribute :name, :string
28
+ attribute :temperature, :integer
29
+ attribute :glaze, Glaze
30
+ end
31
+
32
+ class CeramicCollection < Lutaml::Model::Serializable
33
+ attribute :ceramics, Ceramic, collection: true
34
+ end
35
+ end
36
+
19
37
  RSpec.describe Lutaml::Model::Liquefiable do
20
38
  before do
21
39
  stub_const("DummyModel", Class.new(LiquefiableClass))
@@ -26,14 +44,27 @@ RSpec.describe Lutaml::Model::Liquefiable do
26
44
  describe ".register_liquid_drop_class" do
27
45
  context "when drop class does not exist" do
28
46
  it "creates a new drop class" do
29
- expect { dummy.class.register_liquid_drop_class }.to change {
30
- dummy.class.const_defined?(:DummyModelDrop)
31
- }
47
+ expect do
48
+ dummy.class.register_liquid_drop_class
49
+ end.to change {
50
+ dummy.class.const_defined?(:DummyModelDrop)
51
+ }
32
52
  .from(false)
33
53
  .to(true)
34
54
  end
35
55
  end
36
56
 
57
+ context "when 'liquid' is not available" do
58
+ before { allow(Object).to receive(:const_defined?).with(:Liquid).and_return(false) }
59
+
60
+ it "raises an error" do
61
+ expect { dummy.class.register_liquid_drop_class }.to raise_error(
62
+ Lutaml::Model::LiquidNotEnabledError,
63
+ "Liquid functionality is not available by default; please install and require `liquid` gem to use this functionality",
64
+ )
65
+ end
66
+ end
67
+
37
68
  context "when drop class already exists" do
38
69
  it "raises an error" do
39
70
  dummy.class.register_liquid_drop_class
@@ -69,26 +100,41 @@ RSpec.describe Lutaml::Model::Liquefiable do
69
100
  end
70
101
 
71
102
  it "defines a method on the drop class" do
72
- expect { dummy.class.register_drop_method(:display_name) }.to change {
73
- dummy.to_liquid.respond_to?(:display_name)
74
- }
103
+ expect do
104
+ dummy.class.register_drop_method(:display_name)
105
+ end.to change {
106
+ dummy.to_liquid.respond_to?(:display_name)
107
+ }
75
108
  .from(false)
76
109
  .to(true)
77
110
  end
78
111
  end
79
112
 
80
113
  describe ".to_liquid" do
81
- before do
82
- dummy.class.register_liquid_drop_class
83
- dummy.class.register_drop_method(:display_name)
84
- end
114
+ context "when liquid is not enabled" do
115
+ before { allow(Object).to receive(:const_defined?).with(:Liquid).and_return(false) }
85
116
 
86
- it "returns an instance of the drop class" do
87
- expect(dummy.to_liquid).to be_a(dummy.class.drop_class)
117
+ it "raises an error" do
118
+ expect { dummy.to_liquid }.to raise_error(
119
+ Lutaml::Model::LiquidNotEnabledError,
120
+ "Liquid functionality is not available by default; please install and require `liquid` gem to use this functionality",
121
+ )
122
+ end
88
123
  end
89
124
 
90
- it "allows access to registered methods via the drop class" do
91
- expect(dummy.to_liquid.display_name).to eq("TestName (42)")
125
+ context "when liquid is enabled" do
126
+ before do
127
+ dummy.class.register_liquid_drop_class
128
+ dummy.class.register_drop_method(:display_name)
129
+ end
130
+
131
+ it "returns an instance of the drop class" do
132
+ expect(dummy.to_liquid).to be_a(dummy.class.drop_class)
133
+ end
134
+
135
+ it "allows access to registered methods via the drop class" do
136
+ expect(dummy.to_liquid.display_name).to eq("TestName (42)")
137
+ end
92
138
  end
93
139
  end
94
140
 
@@ -118,4 +164,96 @@ RSpec.describe Lutaml::Model::Liquefiable do
118
164
  end
119
165
  end
120
166
  end
167
+
168
+ describe "working with liquid templates" do
169
+ let(:liquid_template_dir) do
170
+ File.join(File.dirname(__FILE__), "../../fixtures/liquid_templates")
171
+ end
172
+
173
+ describe "rendering simple models with liquid templates" do
174
+ let :yaml do
175
+ <<~YAML
176
+ ---
177
+ ceramics:
178
+ - name: Porcelain Vase
179
+ temperature: 1200
180
+ - name: Earthenware Pot
181
+ temperature: 950
182
+ - name: Stoneware Jug
183
+ temperature: 1200
184
+ YAML
185
+ end
186
+ let :template_path do
187
+ File.join(liquid_template_dir, "_ceramics_in_one.liquid")
188
+ end
189
+
190
+ it "renders" do
191
+ template = Liquid::Template.parse(File.read(template_path))
192
+ ceramic_collection = LiquefiableSpec::CeramicCollection.from_yaml(yaml)
193
+ output = template.render("ceramic_collection" => ceramic_collection)
194
+
195
+ expected_output = <<~OUTPUT
196
+ * Name: "Porcelain Vase"
197
+ ** Temperature: 1200
198
+ * Name: "Earthenware Pot"
199
+ ** Temperature: 950
200
+ * Name: "Stoneware Jug"
201
+ ** Temperature: 1200
202
+ OUTPUT
203
+
204
+ expect(output.strip).to eq(expected_output.strip)
205
+ end
206
+ end
207
+
208
+ describe "rendering nested models with liquid templates from file system" do
209
+ let :yaml do
210
+ <<~YAML
211
+ ---
212
+ ceramics:
213
+ - name: Celadon Bowl
214
+ temperature: 1200
215
+ glaze:
216
+ color: Jade Green
217
+ opacity: Translucent
218
+ - name: Earthenware Pot
219
+ temperature: 950
220
+ glaze:
221
+ color: Rust Red
222
+ opacity: Opaque
223
+ - name: Stoneware Jug
224
+ temperature: 1200
225
+ glaze:
226
+ color: Cobalt Blue
227
+ opacity: Transparent
228
+ YAML
229
+ end
230
+
231
+ it "renders" do
232
+ template = Liquid::Template.new
233
+ file_system = Liquid::LocalFileSystem.new(liquid_template_dir)
234
+ template.registers[:file_system] = file_system
235
+ template.parse(file_system.read_template_file("ceramics"))
236
+
237
+ ceramic_collection = LiquefiableSpec::CeramicCollection.from_yaml(yaml)
238
+ output = template.render("ceramic_collection" => ceramic_collection)
239
+ # puts output
240
+
241
+ expected_output = <<~OUTPUT
242
+ * Name: "Celadon Bowl"
243
+ ** Temperature: 1200
244
+ ** Glaze (color): Jade Green
245
+ ** Glaze (opacity): Translucent
246
+ * Name: "Earthenware Pot"
247
+ ** Temperature: 950
248
+ ** Glaze (color): Rust Red
249
+ ** Glaze (opacity): Opaque
250
+ * Name: "Stoneware Jug"
251
+ ** Temperature: 1200
252
+ ** Glaze (color): Cobalt Blue
253
+ ** Glaze (opacity): Transparent
254
+ OUTPUT
255
+ expect(output.strip).to eq(expected_output.strip)
256
+ end
257
+ end
258
+ end
121
259
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "liquid"
3
4
  require "rspec/matchers"
4
5
  require "equivalent-xml"
5
6
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-14 00:00:00.000000000 Z
11
+ date: 2025-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -111,6 +111,7 @@ files:
111
111
  - lib/lutaml/model/error/incorrect_sequence_error.rb
112
112
  - lib/lutaml/model/error/invalid_choice_range_error.rb
113
113
  - lib/lutaml/model/error/invalid_value_error.rb
114
+ - lib/lutaml/model/error/liquid_not_enabled_error.rb
114
115
  - lib/lutaml/model/error/multiple_mappings_error.rb
115
116
  - lib/lutaml/model/error/no_root_mapping_error.rb
116
117
  - lib/lutaml/model/error/no_root_namespace_error.rb
@@ -192,6 +193,9 @@ files:
192
193
  - spec/ceramic_spec.rb
193
194
  - spec/fixtures/address.rb
194
195
  - spec/fixtures/ceramic.rb
196
+ - spec/fixtures/liquid_templates/_ceramic.liquid
197
+ - spec/fixtures/liquid_templates/_ceramics.liquid
198
+ - spec/fixtures/liquid_templates/_ceramics_in_one.liquid
195
199
  - spec/fixtures/person.rb
196
200
  - spec/fixtures/sample_model.rb
197
201
  - spec/fixtures/vase.rb