lutaml-model 0.3.30 → 0.5.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 +4 -4
- data/.rubocop_todo.yml +34 -18
- data/README.adoc +172 -8
- data/lib/lutaml/model/attribute.rb +6 -2
- data/lib/lutaml/model/key_value_mapping.rb +0 -1
- data/lib/lutaml/model/key_value_mapping_rule.rb +3 -1
- data/lib/lutaml/model/mapping_rule.rb +14 -2
- data/lib/lutaml/model/serialize.rb +174 -61
- data/lib/lutaml/model/type/decimal.rb +5 -0
- data/lib/lutaml/model/type/time.rb +4 -4
- data/lib/lutaml/model/utils.rb +1 -1
- data/lib/lutaml/model/validation.rb +6 -2
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +1 -0
- data/lib/lutaml/model/xml_adapter/builder/oga.rb +180 -0
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +1 -0
- data/lib/lutaml/model/xml_adapter/oga/document.rb +20 -0
- data/lib/lutaml/model/xml_adapter/oga/element.rb +117 -0
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +77 -44
- data/lib/lutaml/model/xml_adapter/xml_document.rb +11 -9
- data/lib/lutaml/model/xml_mapping.rb +0 -1
- data/lib/lutaml/model/xml_mapping_rule.rb +16 -4
- data/spec/address_spec.rb +1 -0
- data/spec/fixtures/sample_model.rb +7 -0
- data/spec/lutaml/model/custom_model_spec.rb +47 -1
- data/spec/lutaml/model/custom_serialization_spec.rb +16 -0
- data/spec/lutaml/model/enum_spec.rb +131 -0
- data/spec/lutaml/model/included_spec.rb +192 -0
- data/spec/lutaml/model/mixed_content_spec.rb +48 -32
- data/spec/lutaml/model/multiple_mapping_spec.rb +329 -0
- data/spec/lutaml/model/ordered_content_spec.rb +1 -1
- data/spec/lutaml/model/render_nil_spec.rb +3 -2
- data/spec/lutaml/model/serializable_spec.rb +3 -3
- data/spec/lutaml/model/type/boolean_spec.rb +62 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -11
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +1 -1
- data/spec/lutaml/model/xml_adapter_spec.rb +2 -2
- data/spec/lutaml/model/xml_mapping_spec.rb +24 -9
- data/spec/sample_model_spec.rb +114 -0
- metadata +9 -2
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lutaml
|
4
|
+
module Model
|
5
|
+
module XmlAdapter
|
6
|
+
module Oga
|
7
|
+
class Element < XmlElement
|
8
|
+
def initialize(node, parent: nil)
|
9
|
+
name = case node
|
10
|
+
when ::Oga::XML::Element
|
11
|
+
namespace_name = node.namespace_name
|
12
|
+
add_namespaces(node)
|
13
|
+
children = parse_children(node)
|
14
|
+
attributes = node_attributes(node)
|
15
|
+
node.name
|
16
|
+
when ::Oga::XML::Text
|
17
|
+
"text"
|
18
|
+
end
|
19
|
+
super(
|
20
|
+
name,
|
21
|
+
Hash(attributes),
|
22
|
+
Array(children),
|
23
|
+
node.text,
|
24
|
+
parent_document: parent,
|
25
|
+
namespace_prefix: namespace_name,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def text?
|
30
|
+
children.empty? && text&.length&.positive?
|
31
|
+
end
|
32
|
+
|
33
|
+
def text
|
34
|
+
@text
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_xml(builder = Builder::Oga.build)
|
38
|
+
build_xml(builder).to_xml
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_xml(builder = Builder::Oga.build)
|
42
|
+
if name == "text"
|
43
|
+
builder.add_text(builder.current_node, @text)
|
44
|
+
else
|
45
|
+
builder.create_element(name, build_attributes(self)) do |xml|
|
46
|
+
children.each { |child| child.build_xml(xml) }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
builder
|
51
|
+
end
|
52
|
+
|
53
|
+
def inner_xml
|
54
|
+
children.map(&:to_xml).join
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def node_attributes(node)
|
60
|
+
node.attributes.each_with_object({}) do |attr, hash|
|
61
|
+
next if attr_is_namespace?(attr)
|
62
|
+
|
63
|
+
name = if attr.namespace
|
64
|
+
"#{attr.namespace.name}:#{attr.name}"
|
65
|
+
else
|
66
|
+
attr.name
|
67
|
+
end
|
68
|
+
hash[name] = XmlAttribute.new(
|
69
|
+
name,
|
70
|
+
attr.value,
|
71
|
+
namespace: attr.namespace&.uri,
|
72
|
+
namespace_prefix: attr.namespace&.name,
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_children(node)
|
78
|
+
node.children.map { |child| self.class.new(child, parent: self) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_namespaces(node)
|
82
|
+
node.namespaces.each_value do |namespace|
|
83
|
+
add_namespace(XmlNamespace.new(namespace.uri, namespace.name))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def attr_is_namespace?(attr)
|
88
|
+
attribute_is_namespace?(attr.name) ||
|
89
|
+
namespaces[attr.name]&.uri == attr.value
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_attributes(node, _options = {})
|
93
|
+
attrs = node.attributes.transform_values(&:value)
|
94
|
+
|
95
|
+
attrs.merge(build_namespace_attributes(node))
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_namespace_attributes(node)
|
99
|
+
namespace_attrs = {}
|
100
|
+
|
101
|
+
node.own_namespaces.each_value do |namespace|
|
102
|
+
namespace_attrs[namespace.attr_name] = namespace.uri
|
103
|
+
end
|
104
|
+
|
105
|
+
node.children.each do |child|
|
106
|
+
namespace_attrs = namespace_attrs.merge(
|
107
|
+
build_namespace_attributes(child),
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
namespace_attrs
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -1,69 +1,102 @@
|
|
1
1
|
require "oga"
|
2
2
|
require_relative "xml_document"
|
3
|
+
require_relative "oga/document"
|
4
|
+
require_relative "oga/element"
|
5
|
+
require_relative "builder/oga"
|
3
6
|
|
4
7
|
module Lutaml
|
5
8
|
module Model
|
6
9
|
module XmlAdapter
|
7
10
|
class OgaAdapter < XmlDocument
|
8
|
-
def self.parse(xml,
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def to_h
|
15
|
-
{ @root.name => parse_element(@root) }
|
11
|
+
def self.parse(xml, options = {})
|
12
|
+
encoding = options[:encoding] || xml.encoding.to_s
|
13
|
+
xml = xml.encode("UTF-16").encode("UTF-8") if encoding && encoding != "UTF-8"
|
14
|
+
parsed = ::Oga.parse_xml(xml)
|
15
|
+
@root = Oga::Element.new(parsed.children.first)
|
16
|
+
new(@root, encoding)
|
16
17
|
end
|
17
18
|
|
18
19
|
def to_xml(options = {})
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
builder_options = {}
|
21
|
+
|
22
|
+
builder_options[:encoding] = if options.key?(:encoding)
|
23
|
+
options[:encoding] || "UTF-8"
|
24
|
+
elsif options.key?(:parse_encoding)
|
25
|
+
options[:parse_encoding]
|
26
|
+
else
|
27
|
+
"UTF-8"
|
28
|
+
end
|
29
|
+
builder = Builder::Oga.build(options) do |xml|
|
30
|
+
if @root.is_a?(Oga::Element)
|
31
|
+
@root.build_xml(xml)
|
32
|
+
else
|
33
|
+
build_element(xml, @root, options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
xml_data = builder.to_xml.encode!(builder_options[:encoding])
|
22
37
|
options[:declaration] ? declaration(options) + xml_data : xml_data
|
38
|
+
rescue Encoding::ConverterNotFoundError
|
39
|
+
invalid_encoding!(builder_options[:encoding])
|
23
40
|
end
|
24
41
|
|
25
42
|
private
|
26
43
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
build_element(builder, child, options)
|
32
|
-
end
|
33
|
-
builder.text(element.text) if element.text
|
34
|
-
end
|
35
|
-
end
|
44
|
+
def build_ordered_element(builder, element, options = {})
|
45
|
+
mapper_class = options[:mapper_class] || element.class
|
46
|
+
xml_mapping = mapper_class.mappings_for(:xml)
|
47
|
+
return xml unless xml_mapping
|
36
48
|
|
37
|
-
|
38
|
-
attributes.transform_values(&:value)
|
39
|
-
end
|
49
|
+
attributes = build_attributes(element, xml_mapping).compact
|
40
50
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
51
|
+
tag_name = options[:tag_name] || xml_mapping.root_element
|
52
|
+
builder.create_and_add_element(tag_name,
|
53
|
+
attributes: attributes) do |el|
|
54
|
+
index_hash = {}
|
55
|
+
content = []
|
45
56
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
57
|
+
element.element_order.each do |name|
|
58
|
+
index_hash[name] ||= -1
|
59
|
+
curr_index = index_hash[name] += 1
|
60
|
+
|
61
|
+
element_rule = xml_mapping.find_by_name(name)
|
62
|
+
next if element_rule.nil?
|
63
|
+
|
64
|
+
attribute_def = attribute_definition_for(element, element_rule,
|
65
|
+
mapper_class: mapper_class)
|
66
|
+
value = attribute_value_for(element, element_rule)
|
67
|
+
|
68
|
+
next if element_rule == xml_mapping.content_mapping && element_rule.cdata && name == "text"
|
69
|
+
|
70
|
+
if element_rule == xml_mapping.content_mapping
|
71
|
+
text = xml_mapping.content_mapping.serialize(element)
|
72
|
+
text = text[curr_index] if text.is_a?(Array)
|
73
|
+
|
74
|
+
next el.add_text(el, text, cdata: element_rule.cdata) if element.mixed?
|
52
75
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
76
|
+
content << text
|
77
|
+
elsif !value.nil? || element_rule.render_nil?
|
78
|
+
value = value[curr_index] if attribute_def.collection?
|
79
|
+
|
80
|
+
add_to_xml(
|
81
|
+
el,
|
82
|
+
element,
|
83
|
+
nil,
|
84
|
+
value,
|
85
|
+
options.merge(
|
86
|
+
attribute: attribute_def,
|
87
|
+
rule: element_rule,
|
88
|
+
mapper_class: mapper_class,
|
89
|
+
),
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
el.add_text(el, content.join)
|
57
95
|
end
|
58
|
-
super(node.name, attributes, parse_children(node), node.text)
|
59
96
|
end
|
60
97
|
|
61
|
-
|
62
|
-
|
63
|
-
def parse_children(node)
|
64
|
-
node.children.select do |child|
|
65
|
-
child.is_a?(Oga::XML::Element)
|
66
|
-
end.map { |child| OgaElement.new(child) }
|
98
|
+
def invalid_encoding!(encoding)
|
99
|
+
raise Error, "unknown encoding name - #{encoding}"
|
67
100
|
end
|
68
101
|
end
|
69
102
|
end
|
@@ -233,20 +233,16 @@ module Lutaml
|
|
233
233
|
end
|
234
234
|
|
235
235
|
process_content_mapping(element, xml_mapping.content_mapping,
|
236
|
-
prefixed_xml)
|
236
|
+
prefixed_xml, mapper_class)
|
237
237
|
end
|
238
238
|
end
|
239
239
|
|
240
|
-
def process_content_mapping(element, content_rule, xml)
|
240
|
+
def process_content_mapping(element, content_rule, xml, mapper_class)
|
241
241
|
return unless content_rule
|
242
242
|
|
243
243
|
if content_rule.custom_methods[:to]
|
244
|
-
|
245
|
-
|
246
|
-
element,
|
247
|
-
xml.parent,
|
248
|
-
xml,
|
249
|
-
)
|
244
|
+
mapper_class.new.send(content_rule.custom_methods[:to], element,
|
245
|
+
xml.parent, xml)
|
250
246
|
else
|
251
247
|
text = content_rule.serialize(element)
|
252
248
|
text = text.join if text.is_a?(Array)
|
@@ -340,7 +336,9 @@ module Lutaml
|
|
340
336
|
next if options[:except]&.include?(mapping_rule.to)
|
341
337
|
next if mapping_rule.custom_methods[:to]
|
342
338
|
|
343
|
-
|
339
|
+
mapping_rule_name = mapping_rule.multiple_mappings? ? mapping_rule.name.first : mapping_rule.name
|
340
|
+
|
341
|
+
if mapping_rule.namespace && mapping_rule.prefix && mapping_rule_name != "lang"
|
344
342
|
hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
|
345
343
|
end
|
346
344
|
|
@@ -378,6 +376,10 @@ module Lutaml
|
|
378
376
|
key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
|
379
377
|
{ key => xml_mapping.namespace_uri }
|
380
378
|
end
|
379
|
+
|
380
|
+
def self.type
|
381
|
+
Utils.snake_case(self).split("/").last.split("_").first
|
382
|
+
end
|
381
383
|
end
|
382
384
|
end
|
383
385
|
end
|
@@ -19,7 +19,8 @@ module Lutaml
|
|
19
19
|
namespace_set: false,
|
20
20
|
prefix_set: false,
|
21
21
|
attribute: false,
|
22
|
-
default_namespace: nil
|
22
|
+
default_namespace: nil,
|
23
|
+
id: nil
|
23
24
|
)
|
24
25
|
super(
|
25
26
|
name,
|
@@ -29,6 +30,7 @@ module Lutaml
|
|
29
30
|
with: with,
|
30
31
|
delegate: delegate,
|
31
32
|
attribute: attribute,
|
33
|
+
id: id
|
32
34
|
)
|
33
35
|
|
34
36
|
@namespace = if namespace.to_s == "inherit"
|
@@ -45,6 +47,7 @@ module Lutaml
|
|
45
47
|
|
46
48
|
@namespace_set = namespace_set
|
47
49
|
@prefix_set = prefix_set
|
50
|
+
@id = id
|
48
51
|
end
|
49
52
|
|
50
53
|
def namespace_set?
|
@@ -72,14 +75,23 @@ module Lutaml
|
|
72
75
|
end
|
73
76
|
|
74
77
|
def prefixed_name
|
78
|
+
rule_name = multiple_mappings? ? name.first : name
|
75
79
|
if prefix
|
76
|
-
"#{prefix}:#{
|
80
|
+
"#{prefix}:#{rule_name}"
|
81
|
+
else
|
82
|
+
rule_name
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def namespaced_names(parent_namespace = nil)
|
87
|
+
if multiple_mappings?
|
88
|
+
name.map { |rule_name| namespaced_name(parent_namespace, rule_name) }
|
77
89
|
else
|
78
|
-
|
90
|
+
[namespaced_name(parent_namespace)]
|
79
91
|
end
|
80
92
|
end
|
81
93
|
|
82
|
-
def namespaced_name(parent_namespace = nil)
|
94
|
+
def namespaced_name(parent_namespace = nil, name = self.name)
|
83
95
|
if name == "lang"
|
84
96
|
"#{prefix}:#{name}"
|
85
97
|
elsif namespace_set? || @attribute
|
data/spec/address_spec.rb
CHANGED
@@ -100,6 +100,7 @@ RSpec.describe Address do
|
|
100
100
|
expect(address_from_json.post_code).to eq("01001")
|
101
101
|
expect(address_from_json.person.first.first_name).to eq("Tom")
|
102
102
|
expect(address_from_json.person.last.first_name).to eq("Jack")
|
103
|
+
expect(address_from_json.person.last.active).to be(false)
|
103
104
|
end
|
104
105
|
|
105
106
|
it "serializes to XML with a collection of persons" do
|
@@ -36,5 +36,12 @@ class SampleModel < Lutaml::Model::Serializable
|
|
36
36
|
yaml do
|
37
37
|
map "name", to: :name
|
38
38
|
map "age", to: :age
|
39
|
+
map "balance", to: :balance
|
40
|
+
map "tags", to: :tags
|
41
|
+
map "preferences", to: :preferences
|
42
|
+
map "status", to: :status
|
43
|
+
map "large_number", to: :large_number
|
44
|
+
map "email", to: :email
|
45
|
+
map "role", to: :role
|
39
46
|
end
|
40
47
|
end
|
@@ -78,7 +78,7 @@ module CustomModelSpecs
|
|
78
78
|
end
|
79
79
|
|
80
80
|
class Id
|
81
|
-
attr_accessor :id
|
81
|
+
attr_accessor :id, :prefix
|
82
82
|
end
|
83
83
|
|
84
84
|
class Docid < Lutaml::Model::Serializable
|
@@ -147,6 +147,28 @@ module CustomModelSpecs
|
|
147
147
|
end
|
148
148
|
end
|
149
149
|
end
|
150
|
+
|
151
|
+
class CustomId < Lutaml::Model::Serializable
|
152
|
+
model Id
|
153
|
+
attribute :id, :string
|
154
|
+
attribute :prefix, :string
|
155
|
+
|
156
|
+
xml do
|
157
|
+
root "custom-id"
|
158
|
+
map_attribute "prefix", to: :prefix
|
159
|
+
map_content with: { to: :id_to_xml, from: :id_from_xml }
|
160
|
+
end
|
161
|
+
|
162
|
+
def id_to_xml(model, _parent, doc)
|
163
|
+
content = "ABC-#{model.id}"
|
164
|
+
doc.add_text(doc, content)
|
165
|
+
end
|
166
|
+
|
167
|
+
def id_from_xml(model, value)
|
168
|
+
id = value.split("-").last
|
169
|
+
model.id = id.to_i
|
170
|
+
end
|
171
|
+
end
|
150
172
|
end
|
151
173
|
|
152
174
|
RSpec.describe "CustomModel" do
|
@@ -407,4 +429,28 @@ RSpec.describe "CustomModel" do
|
|
407
429
|
end
|
408
430
|
end
|
409
431
|
end
|
432
|
+
|
433
|
+
context "with custom methods" do
|
434
|
+
describe ".xml serialization" do
|
435
|
+
it "handles custom content mapping methods" do
|
436
|
+
xml = '<custom-id prefix="ABC">ABC-123</custom-id>'
|
437
|
+
|
438
|
+
instance = CustomModelSpecs::Id.new
|
439
|
+
instance.id = 123
|
440
|
+
instance.prefix = "ABC"
|
441
|
+
result_xml = CustomModelSpecs::CustomId.to_xml(instance)
|
442
|
+
expect(result_xml).to eq(xml)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
describe ".xml deserialization" do
|
447
|
+
it "handles custom content mapping methods" do
|
448
|
+
xml = '<custom-id prefix="ABC">ABC-123</custom-id>'
|
449
|
+
instance = CustomModelSpecs::CustomId.from_xml(xml)
|
450
|
+
|
451
|
+
expect(instance.id).to eq(123)
|
452
|
+
expect(instance.prefix).to eq("ABC")
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
410
456
|
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
|