lutaml-model 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +2 -0
  3. data/.rubocop_todo.yml +86 -23
  4. data/Gemfile +2 -0
  5. data/README.adoc +1441 -220
  6. data/lib/lutaml/model/attribute.rb +33 -10
  7. data/lib/lutaml/model/choice.rb +56 -0
  8. data/lib/lutaml/model/config.rb +1 -0
  9. data/lib/lutaml/model/constants.rb +7 -0
  10. data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
  11. data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
  12. data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
  13. data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
  14. data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
  15. data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
  16. data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
  17. data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
  18. data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
  19. data/lib/lutaml/model/error.rb +9 -0
  20. data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
  21. data/lib/lutaml/model/key_value_mapping.rb +34 -3
  22. data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
  23. data/lib/lutaml/model/liquefiable.rb +59 -0
  24. data/lib/lutaml/model/mapping_hash.rb +9 -1
  25. data/lib/lutaml/model/mapping_rule.rb +19 -2
  26. data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
  27. data/lib/lutaml/model/schema/xml_compiler.rb +762 -0
  28. data/lib/lutaml/model/schema.rb +5 -0
  29. data/lib/lutaml/model/schema_location.rb +7 -0
  30. data/lib/lutaml/model/sequence.rb +71 -0
  31. data/lib/lutaml/model/serialize.rb +139 -33
  32. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
  33. data/lib/lutaml/model/type/decimal.rb +0 -4
  34. data/lib/lutaml/model/type/hash.rb +11 -11
  35. data/lib/lutaml/model/type/time.rb +3 -3
  36. data/lib/lutaml/model/utils.rb +19 -15
  37. data/lib/lutaml/model/validation.rb +12 -1
  38. data/lib/lutaml/model/version.rb +1 -1
  39. data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
  40. data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
  41. data/lib/lutaml/model/xml_adapter/element.rb +32 -0
  42. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +13 -9
  43. data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
  44. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
  45. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
  46. data/lib/lutaml/model/xml_adapter/xml_document.rb +82 -25
  47. data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
  48. data/lib/lutaml/model/xml_mapping.rb +53 -9
  49. data/lib/lutaml/model/xml_mapping_rule.rb +8 -6
  50. data/lib/lutaml/model.rb +2 -0
  51. data/lutaml-model.gemspec +5 -0
  52. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
  53. data/spec/ceramic_spec.rb +39 -0
  54. data/spec/fixtures/ceramic.rb +23 -0
  55. data/spec/fixtures/xml/address_example_260.xsd +9 -0
  56. data/spec/fixtures/xml/invalid_math_document.xml +4 -0
  57. data/spec/fixtures/xml/math_document_schema.xsd +56 -0
  58. data/spec/fixtures/xml/test_schema.xsd +53 -0
  59. data/spec/fixtures/xml/user.xsd +10 -0
  60. data/spec/fixtures/xml/valid_math_document.xml +4 -0
  61. data/spec/lutaml/model/cdata_spec.rb +4 -5
  62. data/spec/lutaml/model/choice_spec.rb +168 -0
  63. data/spec/lutaml/model/collection_spec.rb +1 -1
  64. data/spec/lutaml/model/custom_model_spec.rb +7 -21
  65. data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
  66. data/spec/lutaml/model/defaults_spec.rb +3 -1
  67. data/spec/lutaml/model/delegation_spec.rb +7 -5
  68. data/spec/lutaml/model/enum_spec.rb +35 -0
  69. data/spec/lutaml/model/group_spec.rb +160 -0
  70. data/spec/lutaml/model/inheritance_spec.rb +25 -0
  71. data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
  72. data/spec/lutaml/model/liquefiable_spec.rb +121 -0
  73. data/spec/lutaml/model/map_all_spec.rb +188 -0
  74. data/spec/lutaml/model/mixed_content_spec.rb +95 -56
  75. data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
  76. data/spec/lutaml/model/schema/xml_compiler_spec.rb +1624 -0
  77. data/spec/lutaml/model/sequence_spec.rb +216 -0
  78. data/spec/lutaml/model/transformation_spec.rb +230 -0
  79. data/spec/lutaml/model/type_spec.rb +138 -31
  80. data/spec/lutaml/model/utils_spec.rb +32 -0
  81. data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
  82. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
  83. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
  84. data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
  85. data/spec/lutaml/model/xml_mapping_spec.rb +250 -112
  86. metadata +77 -2
@@ -47,7 +47,7 @@ module CollectionTests
47
47
  end
48
48
 
49
49
  def city_from_xml(model, node)
50
- model.city = node
50
+ model.city = node.text
51
51
  end
52
52
 
53
53
  def city_to_xml(model, parent, doc)
@@ -55,9 +55,8 @@ class CustomModelParentMapper < Lutaml::Model::Serializable
55
55
 
56
56
  def child_from_xml(model, value)
57
57
  model.child_mapper ||= CustomModelChild.new
58
-
59
- model.child_mapper.street = value["street"].text
60
- model.child_mapper.city = value["city"].text
58
+ model.child_mapper.street = value.find_child_by_name("street").text
59
+ model.child_mapper.city = value.find_child_by_name("city").text
61
60
  end
62
61
  end
63
62
 
@@ -126,10 +125,10 @@ module CustomModelSpecs
126
125
 
127
126
  def bibdata_from_xml(model, value)
128
127
  model.bibdata = BibliographicItem.new(
129
- "type" => value["type"],
130
- "title" => value["title"],
131
- "language" => value["title"]["language"],
132
- "schema_version" => value["schema-version"],
128
+ "type" => value.find_attribute_value("type"),
129
+ "title" => value.find_child_by_name("title"),
130
+ "language" => value.find_child_by_name("title").find_attribute_value("language"),
131
+ "schema_version" => value.find_attribute_value("schema-version"),
133
132
  )
134
133
  end
135
134
 
@@ -410,22 +409,9 @@ RSpec.describe "CustomModel" do
410
409
  </MixedWithNestedContent>
411
410
  XML
412
411
 
413
- expected_xml = <<~XML
414
- <MixedWithNestedContent>
415
- <street>
416
- A &lt;p&gt;b&lt;/p&gt; B &lt;p&gt;c&lt;/p&gt; C
417
- </street>
418
- <bibdata type="collection" schema-version="v1.2.8">
419
- <title language="en">
420
- JCGM Collection 1
421
- </title>
422
- </bibdata>
423
- </MixedWithNestedContent>
424
- XML
425
-
426
412
  bibdata = CustomModelSpecs::MixedWithNestedContent.from_xml(xml)
427
413
 
428
- expect(bibdata.to_xml).to be_equivalent_to(expected_xml)
414
+ expect(bibdata.to_xml).to be_equivalent_to(xml)
429
415
  end
430
416
  end
431
417
  end
@@ -68,7 +68,7 @@ class CustomSerialization < Lutaml::Model::Serializable
68
68
  end
69
69
 
70
70
  def name_from_xml(model, value)
71
- model.full_name = value.sub(/^XML Masterpiece: /, "")
71
+ model.full_name = value.text.sub(/^XML Masterpiece: /, "")
72
72
  end
73
73
 
74
74
  def size_to_xml(model, parent, doc)
@@ -86,7 +86,7 @@ class CustomSerialization < Lutaml::Model::Serializable
86
86
  end
87
87
 
88
88
  def color_from_xml(model, value)
89
- model.color = value.downcase
89
+ model.color = value.text.downcase
90
90
  end
91
91
 
92
92
  def description_to_xml(model, parent, doc)
@@ -98,6 +98,37 @@ class CustomSerialization < Lutaml::Model::Serializable
98
98
  end
99
99
  end
100
100
 
101
+ class GrammarInfo < Lutaml::Model::Serializable
102
+ attribute :part_of_speech, :string, values: %w[user admin super_admin]
103
+
104
+ key_value do
105
+ map :part_of_speech, with: { to: :part_of_speech_to_key_value, from: :part_of_speech_from_key_value }
106
+ end
107
+
108
+ xml do
109
+ root "GrammarInfo"
110
+ map_element :part_of_speech, with: { to: :part_of_speech_to_xml, from: :part_of_speech_from_xml }
111
+ end
112
+
113
+ def part_of_speech_from_key_value(model, value)
114
+ model.part_of_speech = value
115
+ end
116
+
117
+ def part_of_speech_to_key_value(model, doc)
118
+ doc["part_of_speech"] = model.part_of_speech
119
+ end
120
+
121
+ def part_of_speech_from_xml(model, node)
122
+ model.part_of_speech = node.text
123
+ end
124
+
125
+ def part_of_speech_to_xml(model, parent, doc)
126
+ el = doc.create_element("part_of_speech")
127
+ doc.add_text(el, model.part_of_speech)
128
+ doc.add_element(parent, el)
129
+ end
130
+ end
131
+
101
132
  RSpec.describe CustomSerialization do
102
133
  let(:attributes) do
103
134
  {
@@ -183,4 +214,45 @@ RSpec.describe CustomSerialization do
183
214
  expect(ceramic.description).to eq(model.description)
184
215
  end
185
216
  end
217
+
218
+ context "when enum used with custom methods" do
219
+ let(:hash) do
220
+ {
221
+ "part_of_speech" => "user",
222
+ }
223
+ end
224
+
225
+ it "correctly persist value for yaml" do
226
+ instance = GrammarInfo.from_yaml(hash.to_yaml)
227
+ serialized = instance.to_yaml
228
+
229
+ expect(instance.part_of_speech).to eq("user")
230
+ expect(serialized).to eq(hash.to_yaml)
231
+ end
232
+
233
+ it "correctly persist value for json" do
234
+ instance = GrammarInfo.from_json(hash.to_json)
235
+ serialized = instance.to_json
236
+
237
+ expect(instance.part_of_speech).to eq("user")
238
+ expect(serialized).to eq(hash.to_json)
239
+ end
240
+
241
+ it "correctly handles value for xml" do
242
+ xml_input = <<~XML
243
+ <GrammarInfo>
244
+ <part_of_speech>user</part_of_speech>
245
+ </GrammarInfo>
246
+ XML
247
+
248
+ instance = GrammarInfo.from_xml(xml_input)
249
+ expect(instance.part_of_speech).to eq("user")
250
+ expect(instance.user?).to be true
251
+ expect(instance.admin?).to be false
252
+ expect(instance.super_admin?).to be false
253
+
254
+ serialized = instance.to_xml
255
+ expect(serialized).to be_equivalent_to(xml_input)
256
+ end
257
+ end
186
258
  end
@@ -115,7 +115,9 @@ module DefaultsSpec
115
115
  model Lang
116
116
 
117
117
  attribute :lang, :string, default: -> { "en" }
118
- attribute :content, :string, default: -> { "default value not render when render_default is false" }
118
+ attribute :content, :string, default: -> {
119
+ "default value not render when render_default is false"
120
+ }
119
121
 
120
122
  xml do
121
123
  root "CustomModelWithDefaultValue"
@@ -99,14 +99,16 @@ RSpec.describe Delegation do
99
99
  end
100
100
 
101
101
  it "serializes to JSON with pretty formatting" do
102
- expected_pretty_json = {
103
- type: "Vase",
104
- color: "Blue",
105
- }.to_json
102
+ expected_pretty_json = <<~JSON.chomp
103
+ {
104
+ "type": "Vase",
105
+ "color": "Blue"
106
+ }
107
+ JSON
106
108
 
107
109
  generated_json = delegation.to_json(only: %i[type color], pretty: true)
108
110
 
109
- expect(generated_json.strip).to eq(expected_pretty_json.strip)
111
+ expect(generated_json).to eq(expected_pretty_json)
110
112
  end
111
113
 
112
114
  it "serializes to XML with pretty formatting" do
@@ -43,6 +43,21 @@ RSpec.describe "Enum" do
43
43
  .from(nil)
44
44
  .to("user")
45
45
  end
46
+
47
+ it "sets and unsets all enum values correctly" do
48
+ object.user = true
49
+ object.admin = false
50
+ object.super_admin = false
51
+
52
+ expect(object.user?).to be true
53
+ expect(object.admin?).to be false
54
+ expect(object.super_admin?).to be false
55
+
56
+ expect { object.user = false }
57
+ .to change(object, :user?)
58
+ .from(true)
59
+ .to(false)
60
+ end
46
61
  end
47
62
 
48
63
  describe "#multi_value" do
@@ -59,6 +74,26 @@ RSpec.describe "Enum" do
59
74
  .from([])
60
75
  .to(%w[dual plural])
61
76
  end
77
+
78
+ it "sets and unsets all enum values correctly" do
79
+ object.singular = false
80
+ object.dual = true
81
+ object.plural = true
82
+
83
+ expect(object.singular?).to be false
84
+ expect(object.dual?).to be true
85
+ expect(object.plural?).to be true
86
+
87
+ expect { object.plural = false }
88
+ .to change(object, :plural?)
89
+ .from(true)
90
+ .to(false)
91
+
92
+ expect { object.dual = false }
93
+ .to change(object, :dual?)
94
+ .from(true)
95
+ .to(false)
96
+ end
62
97
  end
63
98
 
64
99
  EnumSpec::WithEnum.enums.each_value do |enum_attr|
@@ -0,0 +1,160 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module GroupSpec
5
+ class Ceramic < Lutaml::Model::Serializable
6
+ attribute :type, :string
7
+ attribute :name, :string
8
+
9
+ xml do
10
+ no_root
11
+ map_element :type, to: :type
12
+ map_element :name, to: :name
13
+ end
14
+ end
15
+
16
+ class CeramicCollection < Lutaml::Model::Serializable
17
+ attribute :ceramic, Ceramic, collection: 1..2
18
+
19
+ xml do
20
+ root "collection"
21
+ map_element "ceramic", to: :ceramic
22
+ end
23
+ end
24
+
25
+ class AttributeValueType < Lutaml::Model::Type::Decimal
26
+ end
27
+
28
+ class GroupOfItems < Lutaml::Model::Serializable
29
+ attribute :name, :string
30
+ attribute :type, :string
31
+ attribute :description, :string
32
+ attribute :code, :string
33
+
34
+ xml do
35
+ no_root
36
+ sequence do
37
+ map_element "name", to: :name
38
+ map_element "type", to: :type
39
+ map_element "description", to: :description,
40
+ namespace: "http://www.sparxsystems.com/profiles/GML/1.0",
41
+ prefix: "GML"
42
+ end
43
+ map_attribute "code", to: :code, namespace: "http://www.example.com", prefix: "ex1"
44
+ end
45
+ end
46
+
47
+ class ComplexType < Lutaml::Model::Serializable
48
+ attribute :tag, AttributeValueType
49
+ attribute :content, :string
50
+ attribute :group, :string
51
+ import_model_attributes GroupOfItems
52
+
53
+ xml do
54
+ root "GroupOfItems"
55
+ map_attribute "tag", to: :tag
56
+ map_content to: :content
57
+ map_element :group, to: :group
58
+ import_model_mappings GroupOfItems
59
+ end
60
+ end
61
+
62
+ class SimpleType < Lutaml::Model::Serializable
63
+ import_model GroupOfItems
64
+ end
65
+
66
+ class GenericType < Lutaml::Model::Serializable
67
+ import_model_mappings GroupOfItems
68
+ end
69
+
70
+ class GroupWithRoot < Lutaml::Model::Serializable
71
+ attribute :name, :string
72
+
73
+ xml do
74
+ root "group"
75
+ map_element :name, to: :name
76
+ end
77
+ end
78
+ end
79
+
80
+ RSpec.describe "Group" do
81
+ context "with no_root" do
82
+ let(:mapper) { GroupSpec::CeramicCollection }
83
+
84
+ it "raises error if root-less class used directly for parsing" do
85
+ xml = <<~XML
86
+ <type>Data</type>
87
+ <name>Smith</name>
88
+ XML
89
+
90
+ expect { GroupSpec::Ceramic.from_xml(xml) }.to raise_error(
91
+ Lutaml::Model::NoRootMappingError,
92
+ "GroupSpec::Ceramic has `no_root`, it allowed only for reusable models",
93
+ )
94
+ end
95
+
96
+ it "raises error if root_less class used for deserializing" do
97
+ ceramic = GroupSpec::Ceramic.new(type: "Data", name: "Starc")
98
+
99
+ expect { ceramic.to_xml }.to raise_error(
100
+ Lutaml::Model::NoRootMappingError,
101
+ "GroupSpec::Ceramic has `no_root`, it allowed only for reusable models",
102
+ )
103
+ end
104
+
105
+ it "correctly get the element of root-less class" do
106
+ xml = <<~XML
107
+ <collection>
108
+ <ceramic>
109
+ <type>Data</type>
110
+ </ceramic>
111
+ </collection>
112
+ XML
113
+
114
+ expect { mapper.from_xml(xml) }.not_to raise_error
115
+ end
116
+ end
117
+
118
+ context "with model" do
119
+ it "import attributes" do
120
+ expect(GroupSpec::ComplexType.attributes).to include(GroupSpec::GroupOfItems.attributes)
121
+ end
122
+
123
+ it "import mappings in xml block" do
124
+ expect(GroupSpec::ComplexType.mappings_for(:xml).elements).to include(*GroupSpec::GroupOfItems.mappings_for(:xml).elements)
125
+ end
126
+
127
+ it "import mappings outside xml block" do
128
+ expect(GroupSpec::GenericType.mappings_for(:xml).elements).to include(*GroupSpec::GroupOfItems.mappings_for(:xml).elements)
129
+ end
130
+
131
+ it "import attributes and mappings in xml block" do
132
+ expect(GroupSpec::ComplexType.attributes).to include(GroupSpec::GroupOfItems.attributes)
133
+ expect(GroupSpec::ComplexType.mappings_for(:xml).elements).to include(*GroupSpec::GroupOfItems.mappings_for(:xml).elements)
134
+ end
135
+
136
+ it "import attributes and mappings outside the xml block" do
137
+ expect(GroupSpec::SimpleType.attributes).to include(GroupSpec::GroupOfItems.attributes)
138
+ expect(GroupSpec::SimpleType.mappings_for(:xml).elements).to include(*GroupSpec::GroupOfItems.mappings_for(:xml).elements)
139
+ end
140
+
141
+ it "raises error if root is defined on imported class" do
142
+ expect do
143
+ Class.new(Lutaml::Model::Serializable) do
144
+ import_model GroupSpec::GroupWithRoot
145
+ end
146
+ end.to raise_error(Lutaml::Model::ImportModelWithRootError, "Cannot import a model `GroupSpec::GroupWithRoot` with a root element")
147
+ end
148
+
149
+ it "raises error if namespace is defined with no_root" do
150
+ expect do
151
+ Class.new(Lutaml::Model::Serializable) do
152
+ xml do
153
+ no_root
154
+ namespace "http://www.omg.org/spec/XMI/20131001", "xmi"
155
+ end
156
+ end
157
+ end.to raise_error(Lutaml::Model::NoRootNamespaceError, "Cannot assign namespace to `no_root`")
158
+ end
159
+ end
160
+ end
@@ -34,6 +34,22 @@ module InheritanceSpec
34
34
  map_element "gender", to: :age
35
35
  end
36
36
  end
37
+
38
+ class ParentWithMapAll < Lutaml::Model::Serializable
39
+ attribute :id, :string
40
+ attribute :description, :string
41
+
42
+ xml do
43
+ map_attribute "id", to: :id
44
+ map_all_content to: :description
45
+ end
46
+ end
47
+
48
+ class Child < ParentWithMapAll
49
+ xml do
50
+ root "child"
51
+ end
52
+ end
37
53
  end
38
54
 
39
55
  RSpec.describe "Inheritance" do
@@ -89,4 +105,13 @@ RSpec.describe "Inheritance" do
89
105
  end
90
106
  end
91
107
  end
108
+
109
+ context "with map_all in parent" do
110
+ let(:xml) { "<child id=\"en\">Some <b>bold</b> Content</child>" }
111
+
112
+ it "round trip correctly" do
113
+ parsed = InheritanceSpec::Child.from_xml(xml)
114
+ expect(parsed.to_xml).to eq(xml)
115
+ end
116
+ end
92
117
  end
@@ -83,4 +83,31 @@ RSpec.describe Lutaml::Model::KeyValueMapping do
83
83
  expect(m.custom_methods.object_id).not_to eq(dup_m.custom_methods.object_id)
84
84
  end
85
85
  end
86
+
87
+ context "with map_all option" do
88
+ before do
89
+ mapping.map_all(
90
+ render_nil: true,
91
+ delegate: :container,
92
+ )
93
+ end
94
+
95
+ it "handles JSON mapping" do
96
+ expect(mapping.mappings[0].render_nil).to be true
97
+ expect(mapping.mappings[0].delegate).to eq(:container)
98
+ expect(mapping.mappings[0].raw_mapping?).to be true
99
+ end
100
+
101
+ it "handles YAML mapping" do
102
+ expect(mapping.mappings[0].render_nil).to be true
103
+ expect(mapping.mappings[0].delegate).to eq(:container)
104
+ expect(mapping.mappings[0].raw_mapping?).to be true
105
+ end
106
+
107
+ it "handles TOML mapping" do
108
+ expect(mapping.mappings[0].render_nil).to be true
109
+ expect(mapping.mappings[0].delegate).to eq(:container)
110
+ expect(mapping.mappings[0].raw_mapping?).to be true
111
+ end
112
+ end
86
113
  end
@@ -0,0 +1,121 @@
1
+ require "spec_helper"
2
+ require_relative "../../fixtures/address"
3
+
4
+ class LiquefiableClass
5
+ include Lutaml::Model::Liquefiable
6
+
7
+ attr_accessor :name, :value
8
+
9
+ def initialize(name, value)
10
+ @name = name
11
+ @value = value
12
+ end
13
+
14
+ def display_name
15
+ "#{name} (#{value})"
16
+ end
17
+ end
18
+
19
+ RSpec.describe Lutaml::Model::Liquefiable do
20
+ before do
21
+ stub_const("DummyModel", Class.new(LiquefiableClass))
22
+ end
23
+
24
+ let(:dummy) { DummyModel.new("TestName", 42) }
25
+
26
+ describe ".register_liquid_drop_class" do
27
+ context "when drop class does not exist" do
28
+ it "creates a new drop class" do
29
+ expect { dummy.class.register_liquid_drop_class }.to change {
30
+ dummy.class.const_defined?(:DummyModelDrop)
31
+ }
32
+ .from(false)
33
+ .to(true)
34
+ end
35
+ end
36
+
37
+ context "when drop class already exists" do
38
+ it "raises an error" do
39
+ dummy.class.register_liquid_drop_class
40
+ expect { dummy.class.register_liquid_drop_class }.to raise_error(RuntimeError, "DummyModelDrop Already exists!")
41
+ end
42
+ end
43
+ end
44
+
45
+ describe ".drop_class_name" do
46
+ it "returns the correct drop class name" do
47
+ expect(dummy.class.drop_class_name).to eq("DummyModelDrop")
48
+ end
49
+ end
50
+
51
+ describe ".drop_class" do
52
+ context "when drop class exists" do
53
+ it "returns the drop class" do
54
+ dummy.class.register_liquid_drop_class
55
+ expect(dummy.class.drop_class).to eq(DummyModel::DummyModelDrop)
56
+ end
57
+ end
58
+
59
+ context "when drop class does not exist" do
60
+ it "returns nil" do
61
+ expect(dummy.class.drop_class).to be_nil
62
+ end
63
+ end
64
+ end
65
+
66
+ describe ".register_drop_method" do
67
+ before do
68
+ dummy.class.register_liquid_drop_class
69
+ end
70
+
71
+ 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
+ }
75
+ .from(false)
76
+ .to(true)
77
+ end
78
+ end
79
+
80
+ describe ".to_liquid" do
81
+ before do
82
+ dummy.class.register_liquid_drop_class
83
+ dummy.class.register_drop_method(:display_name)
84
+ end
85
+
86
+ it "returns an instance of the drop class" do
87
+ expect(dummy.to_liquid).to be_a(dummy.class.drop_class)
88
+ end
89
+
90
+ it "allows access to registered methods via the drop class" do
91
+ expect(dummy.to_liquid.display_name).to eq("TestName (42)")
92
+ end
93
+ end
94
+
95
+ context "with serializeable classes" do
96
+ let(:address) do
97
+ Address.new(
98
+ {
99
+ country: "US",
100
+ post_code: "12345",
101
+ person: [Person.new, Person.new],
102
+ },
103
+ )
104
+ end
105
+
106
+ describe ".to_liquid" do
107
+ it "returns correct drop object" do
108
+ expect(address.to_liquid).to be_a(Address::AddressDrop)
109
+ end
110
+
111
+ it "returns array of drops for collection objects" do
112
+ person_classes = address.to_liquid.person.map(&:class)
113
+ expect(person_classes).to eq([Person::PersonDrop, Person::PersonDrop])
114
+ end
115
+
116
+ it "returns `US` for country" do
117
+ expect(address.to_liquid.country).to eq("US")
118
+ end
119
+ end
120
+ end
121
+ end