lutaml-model 0.3.30 → 0.4.0

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: cca2f0b92a3e71807ed2201909e02ea5877dc1468c34c62d887d523962e63968
4
- data.tar.gz: 56fe25dc8f9229d2bcabe1315c88f0f7d8fb3a3ca5f01b78b0e6494fb65ebc75
3
+ metadata.gz: fa60d5495804e6bfc0203ba1a237f38b89b9626d036beeb2be8ca7aebb22426f
4
+ data.tar.gz: 63d8d18127bee85cfaa73973f57554bc56385805dfeea1dad343c552277089d6
5
5
  SHA512:
6
- metadata.gz: a6d19950a00e397146f8a8cfc8eeeb0e3c965f9fb7f5cb3bb5518cf636505f31ec72750552385b533d410eec51c6e325d0d57070fca52189ff00a48c8380c156
7
- data.tar.gz: b7d55249da057ffe7f222c997ae4f628c7dd5acdeee9d7a1a5919f7f6d034ac5342b961696797029233dcd931a7db82be67f00f89a5b78463c3a7c101fbc3e4f
6
+ metadata.gz: 322a29941e7685a6e118d083f1b6e11ce6b8abc1cacb5508dd501f874c5ea3f2dc6f0e6719ce4895deb31c877319a3afe0e20d0820a81b8fd60d9fe59c408472
7
+ data.tar.gz: ce087acefc221ed41180658b8c40d4cff5c0196c208647b9d1c5d0d39799849860abf80b981fe753b30a1176ca8633ed696440beeddef5a4493c1e829340f250
data/README.adoc CHANGED
@@ -569,6 +569,12 @@ The values set inside the `values:` option can be of any type, but they must
569
569
  match the type of the attribute. The values are compared using the `==` operator,
570
570
  so the type must implement the `==` method.
571
571
 
572
+ Also, If all the elements in `values` directive are strings then `lutaml-model` add some enum convenience methods, for each of the value the following three methods are added
573
+
574
+ * `value1`: will return value if set
575
+ * `value1?`: will return true if value is set, false otherwise
576
+ * `value1=`: will set the value of `name_of_attribute` equal to `value1` if truthy value is given, and remove it otherwise.
577
+
572
578
  .Using the `values` directive to define acceptable values for an attribute (basic types)
573
579
  [example]
574
580
  ====
@@ -76,6 +76,10 @@ module Lutaml
76
76
  @raw
77
77
  end
78
78
 
79
+ def enum?
80
+ !enum_values.empty?
81
+ end
82
+
79
83
  def default
80
84
  value = if delegate
81
85
  type.attributes[to].default
@@ -98,7 +102,7 @@ module Lutaml
98
102
 
99
103
  def valid_value!(value)
100
104
  return true if value.nil? && !collection?
101
- return true if enum_values.empty?
105
+ return true unless enum?
102
106
 
103
107
  unless valid_value?(value)
104
108
  raise Lutaml::Model::InvalidValueError.new(name, value, enum_values)
@@ -208,7 +212,7 @@ module Lutaml
208
212
  serialize(v, format, options)
209
213
  end
210
214
  elsif type <= Serialize
211
- type.hash_representation(value, format, options)
215
+ type.public_send(:"as_#{format}", value, options)
212
216
  else
213
217
  # Convert to Value instance if not already
214
218
  value = type.new(value) unless value.is_a?(Type::Value)
@@ -81,16 +81,104 @@ module Lutaml
81
81
  attr = Attribute.new(name, type, options)
82
82
  attributes[name] = attr
83
83
 
84
- define_method(name) do
85
- instance_variable_get(:"@#{name}")
84
+ if attr.enum?
85
+ add_enum_methods_to_model(
86
+ model,
87
+ name,
88
+ options[:values],
89
+ collection: options[:collection],
90
+ )
91
+ else
92
+ define_method(name) do
93
+ instance_variable_get(:"@#{name}")
94
+ end
95
+
96
+ define_method(:"#{name}=") do |value|
97
+ value_set_for(name)
98
+ instance_variable_set(:"@#{name}", attr.cast_value(value))
99
+ end
100
+ end
101
+ end
102
+
103
+ def add_enum_methods_to_model(klass, enum_name, values, collection: false)
104
+ add_enum_getter_if_not_defined(klass, enum_name, collection)
105
+ add_enum_setter_if_not_defined(klass, enum_name, values, collection)
106
+
107
+ return unless values.all?(::String)
108
+
109
+ values.each do |value|
110
+ Utils.add_method_if_not_defined(klass, "#{value}?") do
111
+ curr_value = public_send(:"#{enum_name}")
112
+
113
+ if collection
114
+ curr_value.include?(value)
115
+ else
116
+ curr_value == value
117
+ end
118
+ end
119
+
120
+ Utils.add_method_if_not_defined(klass, value.to_s) do
121
+ public_send(:"#{value}?")
122
+ end
123
+
124
+ Utils.add_method_if_not_defined(klass, "#{value}=") do |val|
125
+ value_set_for(enum_name)
126
+ enum_vals = public_send(:"#{enum_name}")
127
+
128
+ enum_vals = if !!val
129
+ if collection
130
+ enum_vals << value
131
+ else
132
+ [value]
133
+ end
134
+ elsif collection
135
+ enum_vals.delete(value)
136
+ enum_vals
137
+ else
138
+ []
139
+ end
140
+
141
+ instance_variable_set(:"@#{enum_name}", enum_vals)
142
+ end
143
+
144
+ Utils.add_method_if_not_defined(klass, "#{value}!") do
145
+ public_send(:"#{value}=", true)
146
+ end
86
147
  end
148
+ end
149
+
150
+ def add_enum_getter_if_not_defined(klass, enum_name, collection)
151
+ Utils.add_method_if_not_defined(klass, enum_name) do
152
+ i = instance_variable_get(:"@#{enum_name}") || []
87
153
 
88
- define_method(:"#{name}=") do |value|
89
- value_set_for(name)
90
- instance_variable_set(:"@#{name}", attr.cast_value(value))
154
+ if !collection && i.is_a?(Array)
155
+ i.first
156
+ else
157
+ i.uniq
158
+ end
91
159
  end
92
160
  end
93
161
 
162
+ def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
163
+ Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
164
+ value = [value] unless value.is_a?(Array)
165
+
166
+ value_set_for(enum_name)
167
+
168
+ if collection
169
+ curr_value = public_send(:"#{enum_name}")
170
+
171
+ instance_variable_set(:"@#{enum_name}", curr_value + value)
172
+ else
173
+ instance_variable_set(:"@#{enum_name}", value)
174
+ end
175
+ end
176
+ end
177
+
178
+ def enums
179
+ attributes.select { |_, attr| attr.enum? }
180
+ end
181
+
94
182
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
95
183
  define_method(format) do |&block|
96
184
  klass = format == :xml ? XmlMapping : KeyValueMapping
@@ -135,7 +223,7 @@ module Lutaml
135
223
  end
136
224
  end
137
225
 
138
- define_method(:"as_#{format}") do |instance|
226
+ define_method(:"as_#{format}") do |instance, options = {}|
139
227
  if instance.is_a?(Array)
140
228
  return instance.map { |item| public_send(:"as_#{format}", item) }
141
229
  end
@@ -147,7 +235,7 @@ module Lutaml
147
235
 
148
236
  return instance if format == :xml
149
237
 
150
- hash_representation(instance, format)
238
+ hash_representation(instance, format, options)
151
239
  end
152
240
  end
153
241
 
@@ -166,7 +254,7 @@ module Lutaml
166
254
  mappings.each_with_object({}) do |rule, hash|
167
255
  name = rule.to
168
256
  next if except&.include?(name) || (only && !only.include?(name))
169
- next if !rule.render_default? && instance.using_default?(rule.to)
257
+ next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))
170
258
 
171
259
  next handle_delegate(instance, rule, hash, format) if rule.delegate
172
260
 
@@ -176,15 +264,15 @@ module Lutaml
176
264
 
177
265
  value = instance.send(name)
178
266
 
179
- next if value.nil? && !rule.render_nil
267
+ next if Utils.blank?(value) && !rule.render_nil
180
268
 
181
269
  attribute = attributes[name]
182
270
 
183
- hash[rule.from] = if rule.child_mappings
184
- generate_hash_from_child_mappings(value, rule.child_mappings)
185
- else
186
- attribute.serialize(value, format, options)
187
- end
271
+ hash[rule.from.to_s] = if rule.child_mappings
272
+ generate_hash_from_child_mappings(value, rule.child_mappings)
273
+ else
274
+ attribute.serialize(value, format, options)
275
+ end
188
276
  end
189
277
  end
190
278
 
@@ -194,7 +282,7 @@ module Lutaml
194
282
  return if value.nil? && !rule.render_nil
195
283
 
196
284
  attribute = instance.send(rule.delegate).class.attributes[name]
197
- hash[rule.from] = attribute.serialize(value, format)
285
+ hash[rule.from.to_s] = attribute.serialize(value, format)
198
286
  end
199
287
 
200
288
  def mappings_for(format)
@@ -379,10 +467,10 @@ module Lutaml
379
467
 
380
468
  attr = attribute_for_rule(rule)
381
469
 
382
- value = if doc.key?(rule.name) || doc.key?(rule.name.to_sym)
383
- doc[rule.name] || doc[rule.name.to_sym]
470
+ value = if doc.key?(rule.name.to_s) || doc.key?(rule.name.to_sym)
471
+ doc[rule.name.to_s] || doc[rule.name.to_sym]
384
472
  else
385
- attr.default
473
+ attr&.default
386
474
  end
387
475
 
388
476
  if rule.custom_methods[:from]
@@ -490,7 +578,9 @@ module Lutaml
490
578
  value = []
491
579
  end
492
580
 
493
- instance_variable_set(:"@#{name}", self.class.ensure_utf8(value))
581
+ default = using_default?(name)
582
+ public_send(:"#{name}=", self.class.ensure_utf8(value))
583
+ using_default_for(name) if default
494
584
  end
495
585
  end
496
586
 
@@ -539,6 +629,14 @@ module Lutaml
539
629
  hash[key.to_sym] || hash[key.to_s]
540
630
  end
541
631
 
632
+ def pretty_print_instance_variables
633
+ (instance_variables - %i[@using_default]).sort
634
+ end
635
+
636
+ def to_yaml_hash
637
+ self.class.as_yaml(self)
638
+ end
639
+
542
640
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
543
641
  define_method(:"to_#{format}") do |options = {}|
544
642
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
@@ -34,10 +34,10 @@ module Lutaml
34
34
  # value&.iso8601
35
35
  # end
36
36
 
37
- # # YAML timestamp format (native)
38
- # def to_yaml
39
- # value
40
- # end
37
+ # YAML timestamp format (native)
38
+ def to_yaml
39
+ value&.iso8601
40
+ end
41
41
 
42
42
  # # TOML time format (HH:MM:SS.mmm)
43
43
  # def to_toml
@@ -39,7 +39,7 @@ module Lutaml
39
39
  end
40
40
 
41
41
  def blank?(value)
42
- value.respond_to?(:empty?) ? value.empty? : !value
42
+ value.respond_to?(:empty?) ? value.empty? : value.nil?
43
43
  end
44
44
 
45
45
  def add_method_if_not_defined(klass, method_name, &block)
@@ -4,9 +4,13 @@ module Lutaml
4
4
  def validate
5
5
  errors = []
6
6
  self.class.attributes.each do |name, attr|
7
- value = instance_variable_get(:"@#{name}")
7
+ value = public_send(:"#{name}")
8
8
  begin
9
- attr.validate_value!(value)
9
+ if value.respond_to?(:validate!)
10
+ value.validate!
11
+ else
12
+ attr.validate_value!(value)
13
+ end
10
14
  rescue Lutaml::Model::InvalidValueError,
11
15
  Lutaml::Model::CollectionCountOutOfRangeError,
12
16
  PatternNotMatchedError => e
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.30"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
@@ -138,6 +138,22 @@ RSpec.describe CustomSerialization do
138
138
  end
139
139
  end
140
140
 
141
+ context "with partial JSON input" do
142
+ it "deserializes from JSON with missing attributes" do
143
+ json = {
144
+ name: "JSON Masterpiece: Vase",
145
+ color: "BLUE",
146
+ }.to_json
147
+
148
+ ceramic = described_class.from_json(json)
149
+
150
+ expect(ceramic.full_name).to eq("Vase")
151
+ expect(ceramic.color).to eq("blue")
152
+ expect(ceramic.size).to be_nil
153
+ expect(ceramic.description).to be_nil
154
+ end
155
+ end
156
+
141
157
  context "with XML serialization" do
142
158
  it "serializes to XML with custom methods" do
143
159
  expected_xml = <<~XML
@@ -0,0 +1,131 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module EnumSpec
5
+ class WithEnum < Lutaml::Model::Serializable
6
+ attribute :without_enum, :string
7
+ attribute :single_value, :string, values: %w[user admin super_admin]
8
+ attribute :multi_value, :string, values: %w[singular dual plural], collection: true
9
+ end
10
+ end
11
+
12
+ RSpec.describe "Enum" do
13
+ let(:single_value_attr) do
14
+ EnumSpec::WithEnum.attributes[:single_value]
15
+ end
16
+
17
+ let(:multi_value_attr) do
18
+ EnumSpec::WithEnum.attributes[:multi_value]
19
+ end
20
+
21
+ let(:without_enum_attr) do
22
+ EnumSpec::WithEnum.attributes[:without_enum]
23
+ end
24
+
25
+ let(:object) do
26
+ EnumSpec::WithEnum.new
27
+ end
28
+
29
+ context "when values are provided for an attribute" do
30
+ it "is marked as enum for single_value" do
31
+ expect(single_value_attr.enum?).to be(true)
32
+ end
33
+
34
+ it "is marked as enum for multi_value" do
35
+ expect(multi_value_attr.enum?).to be(true)
36
+ end
37
+
38
+ context "with enum convinience methods" do
39
+ describe "#single_value" do
40
+ it "returns single value" do
41
+ expect { object.single_value = "user" }
42
+ .to change(object, :single_value)
43
+ .from(nil)
44
+ .to("user")
45
+ end
46
+ end
47
+
48
+ describe "#multi_value" do
49
+ it "returns single value in array" do
50
+ expect { object.multi_value = "dual" }
51
+ .to change(object, :multi_value)
52
+ .from([])
53
+ .to(["dual"])
54
+ end
55
+
56
+ it "returns multiple value in array" do
57
+ expect { object.multi_value = %w[dual plural] }
58
+ .to change(object, :multi_value)
59
+ .from([])
60
+ .to(%w[dual plural])
61
+ end
62
+ end
63
+
64
+ EnumSpec::WithEnum.enums.each_value do |enum_attr|
65
+ enum_attr.enum_values.each do |value|
66
+ describe "##{value}=" do
67
+ it "sets the #{value} if true" do
68
+ expect { object.public_send(:"#{value}=", true) }
69
+ .to change { object.public_send(:"#{value}?") }
70
+ .from(false)
71
+ .to(true)
72
+ end
73
+
74
+ it "unsets the #{value} if false" do
75
+ object.public_send(:"#{value}=", true)
76
+
77
+ expect { object.public_send(:"#{value}=", false) }
78
+ .to change(object, "#{value}?")
79
+ .from(true)
80
+ .to(false)
81
+ end
82
+ end
83
+
84
+ describe "##{value}!" do
85
+ it "method #{value}? should be present" do
86
+ expect(object.respond_to?(:"#{value}!")).to be(true)
87
+ end
88
+
89
+ it "sets #{value} to true for enum" do
90
+ expect { object.public_send(:"#{value}!") }
91
+ .to change(object, "#{value}?")
92
+ .from(false)
93
+ .to(true)
94
+ end
95
+ end
96
+
97
+ describe "##{value}?" do
98
+ it "method #{value}? should be present" do
99
+ expect(object.respond_to?(:"#{value}?")).to be(true)
100
+ end
101
+
102
+ it "is false if role is not #{value}" do
103
+ expect(object.public_send(:"#{value}?")).to be(false)
104
+ end
105
+
106
+ it "is true if role is set to #{value}" do
107
+ expect { object.public_send(:"#{value}=", value) }
108
+ .to change(object, "#{value}?")
109
+ .from(false)
110
+ .to(true)
111
+ end
112
+ end
113
+
114
+ it "adds a method named #{value}=" do
115
+ expect(object.respond_to?(:"#{value}?")).to be(true)
116
+ end
117
+
118
+ it "adds a method named #{value}!" do
119
+ expect(object.respond_to?(:"#{value}!")).to be(true)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ context "when values are not provided for an attribute" do
127
+ it "is not marked as enum" do
128
+ expect(without_enum_attr.enum?).to be(false)
129
+ end
130
+ end
131
+ end
@@ -68,7 +68,6 @@ RSpec.describe RenderNil do
68
68
  name: nil,
69
69
  clay_type: nil,
70
70
  glaze: nil,
71
- dimensions: [],
72
71
  }.to_json
73
72
 
74
73
  expect(model.to_json).to eq(expected_json)
@@ -113,7 +112,6 @@ RSpec.describe RenderNil do
113
112
  ---
114
113
  name:
115
114
  glaze:
116
- dimensions: []
117
115
  YAML
118
116
 
119
117
  generated_yaml = model.to_yaml.strip
@@ -131,8 +131,8 @@ RSpec.describe Lutaml::Model::Serializable do
131
131
 
132
132
  let(:expected_hash) do
133
133
  {
134
- na: "John",
135
- ag: "18",
134
+ "na" => "John",
135
+ "ag" => "18",
136
136
  }
137
137
  end
138
138
 
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.3.30
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-12-05 00:00:00.000000000 Z
11
+ date: 2024-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -139,6 +139,7 @@ files:
139
139
  - spec/lutaml/model/custom_serialization_spec.rb
140
140
  - spec/lutaml/model/defaults_spec.rb
141
141
  - spec/lutaml/model/delegation_spec.rb
142
+ - spec/lutaml/model/enum_spec.rb
142
143
  - spec/lutaml/model/inheritance_spec.rb
143
144
  - spec/lutaml/model/json_adapter_spec.rb
144
145
  - spec/lutaml/model/key_value_mapping_spec.rb