lutaml-model 0.3.30 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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