son_jay 0.3.0 → 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 +8 -8
- data/features/json_parsing.feature +31 -0
- data/features/json_serialization.feature +37 -0
- data/features/step_definitions/json_parsing_steps.rb +4 -0
- data/features/support/attribute_value_verification.rb +7 -2
- data/lib/son_jay/object_model/extra_data.rb +36 -0
- data/lib/son_jay/object_model/properties/abstract.rb +91 -0
- data/lib/son_jay/object_model/properties/properties_with_extra.rb +27 -0
- data/lib/son_jay/object_model/properties/properties_without_extra.rb +25 -0
- data/lib/son_jay/object_model/properties.rb +10 -56
- data/lib/son_jay/object_model/property_definition.rb +1 -1
- data/lib/son_jay/object_model.rb +67 -19
- data/lib/son_jay/version.rb +1 -1
- data/lib/son_jay.rb +9 -0
- data/spec/object_model/extra_data_spec.rb +37 -0
- data/spec/object_model_spec.rb +193 -3
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZDJkNWY1ZGNjOTVhYWZlOTA0ODc0NDY5MzFlMjIzYjg1YzIzNDkzOQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZDI3OTM3Y2YyMGVmZjc0YzI2MzJlMDZlZDQ0NTI0NzQyMDVmMWI2YQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MDQ0YTUxYjY1ZGZjMWQzNDg4Yzk4YWZlZjViYzIyOWIzZmI1OGY4NjM3ODQ2
|
10
|
+
MTljZjc3NjExODkyNDNhMjU4OGZkNGJkZjYzZjI5ZWEzMWUwZGFjNjYyMjM2
|
11
|
+
NjYwZTMyYjMzZDNlMTI4NDE1YTEzZjE4ZTA4NDI5MjM3ZWNhZjI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YzNhZmZkZGM1YjU5NDdlMDMwOTc5MTMwNGFhZjc5ZTc1YzVmZDYwZjYzYTk0
|
14
|
+
ZmE5ZmFkYWI2YjU2NjBlNGQ1NjRjOWI4NTAyYTdkZjAzYzU3OTRiZjEyNjJj
|
15
|
+
ODFhOGJiMWRjOTZhZDIyZjQ1MDQ2YTRhZjcxOWNjODg5MjVlNDc=
|
@@ -225,3 +225,34 @@ Feature: Parsing data from JSON
|
|
225
225
|
| 1 | "Jan 1" | 0 | 3 |
|
226
226
|
| rows[2][0].is_head | rows[2][0].value | rows[2][1].is_head | rows[2][1].value |
|
227
227
|
| 1 | "Jan 3" | 0 | 2 |
|
228
|
+
|
229
|
+
Scenario: Object data with extra properties
|
230
|
+
Given an object model defined as:
|
231
|
+
"""
|
232
|
+
class SimpleObjectModel < SonJay::ObjectModel
|
233
|
+
allow_extras
|
234
|
+
|
235
|
+
properties do
|
236
|
+
property :id
|
237
|
+
property :name
|
238
|
+
end
|
239
|
+
end
|
240
|
+
"""
|
241
|
+
And JSON data defined as:
|
242
|
+
"""
|
243
|
+
json = <<-JSON
|
244
|
+
{
|
245
|
+
"id" : 55 ,
|
246
|
+
"name" : "Polygon" ,
|
247
|
+
"published" : true ,
|
248
|
+
"featured" : false
|
249
|
+
}
|
250
|
+
JSON
|
251
|
+
"""
|
252
|
+
When the JSON is parsed to a model instance as:
|
253
|
+
"""
|
254
|
+
instance = SimpleObjectModel.parse_json( json )
|
255
|
+
"""
|
256
|
+
Then the instance attributes and indexed properties are as follows:
|
257
|
+
| id | name | ['published'] | ['featured'] |
|
258
|
+
| 55 | "Polygon" | true | false |
|
@@ -258,3 +258,40 @@ Feature: Serializing data to JSON
|
|
258
258
|
]
|
259
259
|
}
|
260
260
|
"""
|
261
|
+
|
262
|
+
Scenario: Object data with extra properties
|
263
|
+
Given an object model defined as:
|
264
|
+
"""
|
265
|
+
class SimpleObjectModel < SonJay::ObjectModel
|
266
|
+
allow_extras
|
267
|
+
|
268
|
+
properties do
|
269
|
+
property :id
|
270
|
+
property :name
|
271
|
+
end
|
272
|
+
end
|
273
|
+
"""
|
274
|
+
And a model instance defined as:
|
275
|
+
"""
|
276
|
+
instance = SimpleObjectModel.new
|
277
|
+
"""
|
278
|
+
When the instance's property values are assigned as:
|
279
|
+
"""
|
280
|
+
instance.id = 55
|
281
|
+
instance.name = "Polygon"
|
282
|
+
instance['published'] = true
|
283
|
+
instance['featured'] = false
|
284
|
+
"""
|
285
|
+
And the model is serialized to JSON as:
|
286
|
+
"""
|
287
|
+
json = instance.to_json
|
288
|
+
"""
|
289
|
+
Then the resulting JSON is equivalent to:
|
290
|
+
"""
|
291
|
+
{
|
292
|
+
"id" : 55 ,
|
293
|
+
"name" : "Polygon" ,
|
294
|
+
"published" : true ,
|
295
|
+
"featured" : false
|
296
|
+
}
|
297
|
+
"""
|
@@ -14,3 +14,7 @@ end
|
|
14
14
|
Then(/^the instance attributes are as follows:$/) do |table|
|
15
15
|
object_attributes_match_table! context_data[:instance], table
|
16
16
|
end
|
17
|
+
|
18
|
+
Then(/^the instance attributes and indexed properties are as follows:$/) do |table|
|
19
|
+
object_attributes_match_table! context_data[:instance], table
|
20
|
+
end
|
@@ -3,10 +3,15 @@ def object_attributes_match_table!(obj, table)
|
|
3
3
|
attribute_exprs = row_pairs.map( &:first ).reduce( :+ )
|
4
4
|
expected_exprs = row_pairs.map( &:last ).reduce( :+ )
|
5
5
|
|
6
|
-
actuals = attribute_exprs .map{ |expr|
|
7
|
-
expecteds = expected_exprs .map{ |expr| eval(
|
6
|
+
actuals = attribute_exprs .map{ |expr| eval_obj_expression(obj, expr) }
|
7
|
+
expecteds = expected_exprs .map{ |expr| eval( expr ) }
|
8
8
|
|
9
9
|
actual_hash = Hash[ attribute_exprs.zip( actuals ) ]
|
10
10
|
expected_hash = Hash[ attribute_exprs.zip( expecteds ) ]
|
11
11
|
expect( actual_hash ).to eq( expected_hash )
|
12
12
|
end
|
13
|
+
|
14
|
+
def eval_obj_expression(obj, expr)
|
15
|
+
expr = ".#{expr}" unless expr.start_with?('[')
|
16
|
+
eval("obj#{expr}")
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module SonJay
|
4
|
+
class ObjectModel
|
5
|
+
|
6
|
+
class ExtraData
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@data = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(name, value)
|
14
|
+
name = "#{name}" unless String === name
|
15
|
+
@data[name] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](name)
|
19
|
+
name = "#{name}" unless String === name
|
20
|
+
@data[name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash_merge(other)
|
24
|
+
@data.merge( other )
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
@data.dup
|
29
|
+
end
|
30
|
+
|
31
|
+
def_delegator :@data, :empty?
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module SonJay
|
4
|
+
class ObjectModel
|
5
|
+
module Properties
|
6
|
+
|
7
|
+
class Abstract
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :model_properties
|
11
|
+
|
12
|
+
def initialize(property_definitions)
|
13
|
+
@data = {}
|
14
|
+
@model_properties = Set.new
|
15
|
+
property_definitions.each do |d|
|
16
|
+
is_model_property = !! d.model_class
|
17
|
+
@data[d.name] = is_model_property ? d.model_class.new : nil
|
18
|
+
@model_properties << d.name if is_model_property
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def_delegators :@data, *[
|
23
|
+
:length ,
|
24
|
+
:values ,
|
25
|
+
]
|
26
|
+
|
27
|
+
def [](name)
|
28
|
+
name = "#{name}" unless String === name
|
29
|
+
@data[name]
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch(name)
|
33
|
+
name = "#{name}" unless String === name
|
34
|
+
@data.fetch(name)
|
35
|
+
rescue KeyError
|
36
|
+
raise PropertyNameError.new(name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def []=(name, value)
|
40
|
+
name = "#{name}" unless String === name
|
41
|
+
raise PropertyNameError.new(name) unless @data.has_key?(name)
|
42
|
+
@data[name] = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_data(data)
|
46
|
+
data.each_pair do |name, value|
|
47
|
+
load_property name, value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_property(name, value)
|
52
|
+
name = "#{name}" unless String === name
|
53
|
+
if @data.has_key?( name )
|
54
|
+
load_defined_property name, value
|
55
|
+
else
|
56
|
+
load_extra_property name, value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def extra
|
61
|
+
raise NotImplementedError, "Subclass responsibility"
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_json(options = ::JSON::State.new)
|
65
|
+
options = ::JSON::State.new(options) unless options.kind_of?(::JSON::State)
|
66
|
+
hash_for_json.to_json( options )
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def load_defined_property(name_string, value)
|
72
|
+
if @model_properties.include?( name_string )
|
73
|
+
@data[ name_string ].sonj_content.load_data value
|
74
|
+
else
|
75
|
+
@data[ name_string ] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def load_extra_property(name_string, value)
|
80
|
+
raise NotImplementedError, "Subclass responsibility"
|
81
|
+
end
|
82
|
+
|
83
|
+
def hash_for_json
|
84
|
+
raise NotImplementedError, "Subclass responsibility"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module SonJay
|
2
|
+
class ObjectModel
|
3
|
+
module Properties
|
4
|
+
|
5
|
+
class PropertiesWithExtra < Abstract
|
6
|
+
|
7
|
+
def extra
|
8
|
+
@extra ||= ObjectModel::ExtraData.new
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def load_extra_property(name_string, value)
|
14
|
+
extra[ name_string ] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash_for_json
|
18
|
+
extra.empty? ?
|
19
|
+
@data :
|
20
|
+
extra.hash_merge( @data )
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SonJay
|
2
|
+
class ObjectModel
|
3
|
+
module Properties
|
4
|
+
|
5
|
+
class PropertiesWithoutExtra < Abstract
|
6
|
+
|
7
|
+
def extra
|
8
|
+
raise SonJay::DisabledMethodError
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def load_extra_property(name_string, value)
|
14
|
+
# Ignore extra.
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash_for_json
|
18
|
+
@data
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,66 +1,20 @@
|
|
1
|
-
require '
|
1
|
+
require 'son_jay/object_model/properties/abstract'
|
2
|
+
require 'son_jay/object_model/properties/properties_without_extra'
|
3
|
+
require 'son_jay/object_model/properties/properties_with_extra'
|
2
4
|
|
3
5
|
module SonJay
|
4
6
|
class ObjectModel
|
5
|
-
class Properties
|
6
7
|
|
7
|
-
|
8
|
-
def initialize(name)
|
9
|
-
super "No such property name as %s" % name.inspect
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
extend Forwardable
|
14
|
-
|
15
|
-
def initialize(property_definitions)
|
16
|
-
@data = {}
|
17
|
-
@model_properties = Set.new
|
18
|
-
property_definitions.each do |d|
|
19
|
-
is_model_property = !! d.model_class
|
20
|
-
@data[d.name] = is_model_property ? d.model_class.new : nil
|
21
|
-
@model_properties << d.name if is_model_property
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def_delegators :@data, *[
|
26
|
-
:length ,
|
27
|
-
:values ,
|
28
|
-
]
|
29
|
-
|
30
|
-
def [](name)
|
31
|
-
name = "#{name}"
|
32
|
-
@data.fetch(name)
|
33
|
-
rescue KeyError
|
34
|
-
raise NameError.new(name)
|
35
|
-
end
|
8
|
+
module Properties
|
36
9
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
def load_data(data)
|
44
|
-
data.each_pair do |name, value|
|
45
|
-
load_property name, value
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def load_property(name, value)
|
50
|
-
name = "#{name}"
|
51
|
-
return unless @data.has_key?( name )
|
52
|
-
if @model_properties.include?( name )
|
53
|
-
@data[name].sonj_content.load_data value
|
54
|
-
else
|
55
|
-
@data[name] = value
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def to_json(options = ::JSON::State.new)
|
60
|
-
options = ::JSON::State.new(options) unless options.kind_of?(::JSON::State)
|
61
|
-
@data.to_json( options )
|
10
|
+
def self.new(property_definitions, allow_extra)
|
11
|
+
klass = allow_extra ?
|
12
|
+
self::PropertiesWithExtra :
|
13
|
+
self::PropertiesWithoutExtra
|
14
|
+
klass.new( property_definitions )
|
62
15
|
end
|
63
16
|
|
64
17
|
end
|
18
|
+
|
65
19
|
end
|
66
20
|
end
|
data/lib/son_jay/object_model.rb
CHANGED
@@ -2,6 +2,7 @@ require 'set'
|
|
2
2
|
require 'son_jay/object_model/properties'
|
3
3
|
require 'son_jay/object_model/property_definition'
|
4
4
|
require 'son_jay/object_model/properties_definer'
|
5
|
+
require 'son_jay/object_model/extra_data'
|
5
6
|
|
6
7
|
module SonJay
|
7
8
|
class ObjectModel
|
@@ -11,31 +12,82 @@ module SonJay
|
|
11
12
|
|
12
13
|
def initialize
|
13
14
|
definitions = self.class.property_definitions
|
14
|
-
@sonj_content = ObjectModel::Properties.new(
|
15
|
+
@sonj_content = ObjectModel::Properties.new(
|
16
|
+
definitions, self.class.extras_allowed?
|
17
|
+
)
|
15
18
|
end
|
16
19
|
|
17
20
|
def to_json(*args)
|
18
21
|
sonj_content.to_json( *args )
|
19
22
|
end
|
20
23
|
|
24
|
+
def []=(name, value)
|
25
|
+
name = "#{name}" unless String === name
|
26
|
+
target = property_store_for( name )
|
27
|
+
target[ name ] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](name)
|
31
|
+
name = "#{name}" unless String === name
|
32
|
+
source = property_store_for( name )
|
33
|
+
source[ name ]
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch(name)
|
37
|
+
sonj_content.fetch( name )
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def property_store_for(name_string)
|
43
|
+
store = sonj_content
|
44
|
+
if (
|
45
|
+
self.class.extras_allowed? &&
|
46
|
+
(! sonj_content.model_properties.include?(name_string) )
|
47
|
+
) then
|
48
|
+
store = sonj_content.extra
|
49
|
+
end
|
50
|
+
store
|
51
|
+
end
|
52
|
+
|
21
53
|
class << self
|
22
54
|
|
23
|
-
def
|
24
|
-
@
|
55
|
+
def extras_allowed?
|
56
|
+
@extras_allowed ||= false
|
25
57
|
end
|
26
58
|
|
27
59
|
def property_definitions
|
28
|
-
@property_definitions ||=
|
29
|
-
|
60
|
+
@property_definitions ||= _evaluate_property_definitions
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
30
64
|
|
31
|
-
|
65
|
+
def properties(&property_initializations)
|
66
|
+
_property_initializations << property_initializations
|
67
|
+
end
|
32
68
|
|
33
|
-
|
34
|
-
|
35
|
-
|
69
|
+
def allow_extras(allowed = true)
|
70
|
+
@extras_allowed = allowed
|
71
|
+
end
|
36
72
|
|
37
|
-
|
73
|
+
def _evaluate_property_definitions
|
74
|
+
@property_definitions = [].tap do |definitions|
|
75
|
+
definer = PropertiesDefiner.new( definitions )
|
76
|
+
_property_initializations.each do |pi|
|
77
|
+
definer.instance_eval &pi
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
_validate_model_dependencies!
|
38
82
|
|
83
|
+
_apply_property_definitions property_definitions
|
84
|
+
end
|
85
|
+
|
86
|
+
def _property_initializations
|
87
|
+
@property_initializations ||= []
|
88
|
+
end
|
89
|
+
|
90
|
+
def _apply_property_definitions(definitions)
|
39
91
|
definitions.each do |d|
|
40
92
|
name = d.name
|
41
93
|
class_eval <<-CODE
|
@@ -43,22 +95,18 @@ module SonJay
|
|
43
95
|
def #{name}=(value) ; sonj_content[#{name.inspect}] = value ; end
|
44
96
|
CODE
|
45
97
|
end
|
46
|
-
|
47
|
-
@property_definitions
|
48
98
|
end
|
49
99
|
|
50
|
-
|
51
|
-
|
52
|
-
def validate_model_dependencies!(dependants=Set.new)
|
100
|
+
def _validate_model_dependencies!(dependants=Set.new)
|
53
101
|
raise InfiniteRegressError if dependants.include?(self)
|
54
102
|
dependants << self
|
55
|
-
|
56
|
-
next unless d.respond_to?( :
|
57
|
-
d.send :
|
103
|
+
_hard_model_dependencies.each do |d|
|
104
|
+
next unless d.respond_to?( :_validate_model_dependencies!, true )
|
105
|
+
d.send :_validate_model_dependencies!, dependants
|
58
106
|
end
|
59
107
|
end
|
60
108
|
|
61
|
-
def
|
109
|
+
def _hard_model_dependencies
|
62
110
|
property_definitions.map( &:model_class ).compact.uniq
|
63
111
|
end
|
64
112
|
end
|
data/lib/son_jay/version.rb
CHANGED
data/lib/son_jay.rb
CHANGED
@@ -6,5 +6,14 @@ require 'son_jay/model_array'
|
|
6
6
|
require 'son_jay/value_array'
|
7
7
|
|
8
8
|
module SonJay
|
9
|
+
|
9
10
|
class InfiniteRegressError < StandardError ; end
|
11
|
+
class DisabledMethodError < NameError ; end
|
12
|
+
|
13
|
+
class PropertyNameError < KeyError
|
14
|
+
def initialize(name)
|
15
|
+
super "No such property name as %s" % name.inspect
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
10
19
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SonJay::ObjectModel::ExtraData do
|
4
|
+
|
5
|
+
it "provides value access by name symbol or string" do
|
6
|
+
subject[ :aaa ] = 1
|
7
|
+
subject[ 'bbb' ] = 2
|
8
|
+
expect( subject[ 'aaa' ] ).to eq( 1 )
|
9
|
+
expect( subject[ :bbb ] ).to eq( 2 )
|
10
|
+
end
|
11
|
+
|
12
|
+
it "merges with a hash, returning a hash" do
|
13
|
+
subject[ :aa ] = 1
|
14
|
+
subject[ :bb ] = 2
|
15
|
+
|
16
|
+
actual = subject.hash_merge(
|
17
|
+
"bb" => 22,
|
18
|
+
"cc" => 33
|
19
|
+
)
|
20
|
+
|
21
|
+
expect( actual ).to eq(
|
22
|
+
'aa' => 1,
|
23
|
+
'bb' => 22,
|
24
|
+
'cc' => 33
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "indicates when it is empty" do
|
29
|
+
expect( subject ).to be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it "indicates when it is not empty" do
|
33
|
+
subject[:a] = 'a'
|
34
|
+
expect( subject ).not_to be_empty
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/spec/object_model_spec.rb
CHANGED
@@ -89,12 +89,65 @@ describe SonJay::ObjectModel do
|
|
89
89
|
to be_kind_of( subject_module::DetailZ )
|
90
90
|
end
|
91
91
|
|
92
|
-
it "rejects
|
93
|
-
expect{ sonj_content['qq'] }.to raise_exception(
|
94
|
-
|
92
|
+
it "rejects assignment of an undefined property" do
|
93
|
+
expect{ sonj_content['qq'] = 0 }.to raise_exception(
|
94
|
+
SonJay::PropertyNameError
|
95
95
|
)
|
96
96
|
end
|
97
97
|
|
98
|
+
it "returns nil for name-indexed access to a non-existent property" do
|
99
|
+
expect( sonj_content[ 'qq' ] ).to be_nil
|
100
|
+
expect( sonj_content[ :rr ] ).to be_nil
|
101
|
+
end
|
102
|
+
|
103
|
+
it "has fetchable value properties by name string or symbol" do
|
104
|
+
sonj_content[ :aaa ] = 1
|
105
|
+
sonj_content[ 'bbb' ] = 'XYZ'
|
106
|
+
|
107
|
+
expect( sonj_content.fetch( 'aaa' ) ).to eq( 1 )
|
108
|
+
expect( sonj_content.fetch( :bbb ) ).to eq( 'XYZ' )
|
109
|
+
end
|
110
|
+
|
111
|
+
it "has nil defaults for value property fetches" do
|
112
|
+
expect( sonj_content.fetch( 'aaa' ) ).to be_nil
|
113
|
+
expect( sonj_content.fetch( :bbb ) ).to be_nil
|
114
|
+
end
|
115
|
+
|
116
|
+
it "has name-indexed fetchable values for defined modeled-object properties by string or symbol" do
|
117
|
+
expect( sonj_content.fetch('detail_xy') ).
|
118
|
+
to be_kind_of( subject_module::DetailXY )
|
119
|
+
expect( sonj_content.fetch(:detail_z) ).
|
120
|
+
to be_kind_of( subject_module::DetailZ )
|
121
|
+
end
|
122
|
+
|
123
|
+
it "rejects fetch of an undefined property by name" do
|
124
|
+
expect{ sonj_content.fetch('qq') }.to raise_exception(
|
125
|
+
SonJay::PropertyNameError
|
126
|
+
)
|
127
|
+
expect{ sonj_content.fetch(:rr) }.to raise_exception(
|
128
|
+
SonJay::PropertyNameError
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
context "without extras allowed" do
|
133
|
+
it "rejects access to extra properties object" do
|
134
|
+
expect{ sonj_content.extra }.
|
135
|
+
to raise_exception( SonJay::DisabledMethodError )
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "with extras allowed" do
|
140
|
+
before do
|
141
|
+
model_class.class_eval do
|
142
|
+
allow_extras
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
it "allows access to extra properties object" do
|
147
|
+
expect( sonj_content.extra.to_h ).to eq( {} )
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
98
151
|
end
|
99
152
|
|
100
153
|
it "has direct property accessor methods for each property" do
|
@@ -106,6 +159,27 @@ describe SonJay::ObjectModel do
|
|
106
159
|
to be_kind_of( subject_module::DetailZ )
|
107
160
|
end
|
108
161
|
|
162
|
+
it "has name-index acces to each property" do
|
163
|
+
model_instance.aaa = 11
|
164
|
+
model_instance['bbb'] = 22
|
165
|
+
expect( model_instance[:aaa] ).to eq( 11 )
|
166
|
+
expect( model_instance.bbb ).to eq( 22 )
|
167
|
+
expect( model_instance[:detail_xy] ).
|
168
|
+
to be_kind_of( subject_module::DetailXY )
|
169
|
+
expect( model_instance['detail_z'] ).
|
170
|
+
to be_kind_of( subject_module::DetailZ )
|
171
|
+
end
|
172
|
+
|
173
|
+
it "has name-index fetchable access to each property" do
|
174
|
+
model_instance.aaa, model_instance.bbb = 11, 22
|
175
|
+
expect( model_instance.fetch( :aaa ) ).to eq( 11 )
|
176
|
+
expect( model_instance.fetch( 'bbb' ) ).to eq( 22 )
|
177
|
+
expect( model_instance.fetch(:detail_xy) ).
|
178
|
+
to be_kind_of( subject_module::DetailXY )
|
179
|
+
expect( model_instance.fetch('detail_z') ).
|
180
|
+
to be_kind_of( subject_module::DetailZ )
|
181
|
+
end
|
182
|
+
|
109
183
|
it "serializes to a JSON object representation w/ value properties" do
|
110
184
|
instance = detail_xy_class.new
|
111
185
|
instance.xxx, instance.yyy = 'ABC', nil
|
@@ -155,6 +229,122 @@ describe SonJay::ObjectModel do
|
|
155
229
|
expect( instance.detail_z.zzz ).to eq('z')
|
156
230
|
end
|
157
231
|
|
232
|
+
context "with more properties added via an additional ::properies block" do
|
233
|
+
before do
|
234
|
+
|
235
|
+
model_class.class_eval do
|
236
|
+
properties do
|
237
|
+
property :mmm
|
238
|
+
property :nnn
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
it "has number of entries equal to total number of defined properties" do
|
245
|
+
expect( model_instance.sonj_content.length ).to eq( 6 )
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
context "with extras not allowed" do
|
251
|
+
it "rejects name-index writing of arbitrary extra properties" do
|
252
|
+
expect{ model_instance['qqq'] = 111 }.to raise_exception(
|
253
|
+
SonJay::PropertyNameError
|
254
|
+
)
|
255
|
+
expect{ model_instance[:rrr] = 222 }.to raise_exception(
|
256
|
+
SonJay::PropertyNameError
|
257
|
+
)
|
258
|
+
end
|
259
|
+
|
260
|
+
it "parses from JSON with extra properties to an instance with defined properties filled in" do
|
261
|
+
json = <<-JSON
|
262
|
+
{
|
263
|
+
"aaa": 123 ,
|
264
|
+
"bbb": "XYZ" ,
|
265
|
+
"detail_xy": { "xxx": "x", "yyy": "y" } ,
|
266
|
+
"detail_z": { "zzz": "z" },
|
267
|
+
"qqq": 999,
|
268
|
+
"rrr": { "foo": "bar" }
|
269
|
+
}
|
270
|
+
JSON
|
271
|
+
|
272
|
+
instance = model_class.parse_json( json )
|
273
|
+
|
274
|
+
expect( instance.aaa ).to eq( 123 )
|
275
|
+
expect( instance.bbb ).to eq('XYZ')
|
276
|
+
expect( instance.detail_xy.xxx ).to eq('x')
|
277
|
+
expect( instance.detail_xy.yyy ).to eq('y')
|
278
|
+
expect( instance.detail_z.zzz ).to eq('z')
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
context "with extras allowed" do
|
283
|
+
before do
|
284
|
+
model_class.class_eval do
|
285
|
+
allow_extras
|
286
|
+
end
|
287
|
+
detail_xy_class.instance_eval do
|
288
|
+
allow_extras
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
it "allows name-index writing of arbitrary extra properties" do
|
293
|
+
model_instance[ 'qqq' ] = 111
|
294
|
+
model_instance[ :rrr ] = 222
|
295
|
+
expect( model_instance.sonj_content.extra.to_h ).
|
296
|
+
to eq( 'qqq' => 111, 'rrr' => 222 )
|
297
|
+
end
|
298
|
+
|
299
|
+
it "allows name-index reading of arbitrary extra properties" do
|
300
|
+
model_instance.sonj_content.extra[ 'qqq' ] = 111
|
301
|
+
model_instance.sonj_content.extra[ :rrr ] = 222
|
302
|
+
expect( model_instance[ :qqq ] ).to eq( 111 )
|
303
|
+
expect( model_instance[ 'rrr' ] ).to eq( 222 )
|
304
|
+
end
|
305
|
+
|
306
|
+
it "serializes to a JSON object representation w/ properties and extras" do
|
307
|
+
instance = detail_xy_class.new
|
308
|
+
instance.xxx, instance.yyy = 'ABC', nil
|
309
|
+
instance[:qqq] = 111
|
310
|
+
instance[:rrr] = 222
|
311
|
+
|
312
|
+
actual_json = instance.to_json
|
313
|
+
|
314
|
+
actual_data = JSON.parse( actual_json)
|
315
|
+
expected_data = {
|
316
|
+
'xxx' => 'ABC',
|
317
|
+
'yyy' => nil,
|
318
|
+
'qqq' => 111,
|
319
|
+
'rrr' => 222
|
320
|
+
}
|
321
|
+
expect( actual_data ).to eq( expected_data )
|
322
|
+
end
|
323
|
+
|
324
|
+
it "parses from JSON to an instance with properties and extras filled in" do
|
325
|
+
json = <<-JSON
|
326
|
+
{
|
327
|
+
"aaa": 123 ,
|
328
|
+
"bbb": "XYZ" ,
|
329
|
+
"detail_xy": { "xxx": "x", "yyy": "y" } ,
|
330
|
+
"detail_z": { "zzz": "z" },
|
331
|
+
"qqq": 999,
|
332
|
+
"rrr": { "foo": "bar" }
|
333
|
+
}
|
334
|
+
JSON
|
335
|
+
|
336
|
+
instance = model_class.parse_json( json )
|
337
|
+
|
338
|
+
expect( instance.aaa ).to eq( 123 )
|
339
|
+
expect( instance.bbb ).to eq('XYZ')
|
340
|
+
expect( instance.detail_xy.xxx ).to eq('x')
|
341
|
+
expect( instance.detail_xy.yyy ).to eq('y')
|
342
|
+
expect( instance.detail_z.zzz ).to eq('z')
|
343
|
+
expect( instance['qqq'] ).to eq( 999 )
|
344
|
+
expect( instance['rrr'] ).to eq( 'foo' => 'bar' )
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
158
348
|
end
|
159
349
|
|
160
350
|
describe "a subclass with a directly self-referential property specification" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: son_jay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steve Jorgensen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -123,7 +123,11 @@ files:
|
|
123
123
|
- lib/son_jay/acts_as_model.rb
|
124
124
|
- lib/son_jay/model_array.rb
|
125
125
|
- lib/son_jay/object_model.rb
|
126
|
+
- lib/son_jay/object_model/extra_data.rb
|
126
127
|
- lib/son_jay/object_model/properties.rb
|
128
|
+
- lib/son_jay/object_model/properties/abstract.rb
|
129
|
+
- lib/son_jay/object_model/properties/properties_with_extra.rb
|
130
|
+
- lib/son_jay/object_model/properties/properties_without_extra.rb
|
127
131
|
- lib/son_jay/object_model/properties_definer.rb
|
128
132
|
- lib/son_jay/object_model/property_definition.rb
|
129
133
|
- lib/son_jay/value_array.rb
|
@@ -131,6 +135,7 @@ files:
|
|
131
135
|
- son_jay.gemspec
|
132
136
|
- spec/acts_as_model_spec.rb
|
133
137
|
- spec/model_array_spec.rb
|
138
|
+
- spec/object_model/extra_data_spec.rb
|
134
139
|
- spec/object_model/property_definition_spec.rb
|
135
140
|
- spec/object_model_spec.rb
|
136
141
|
- spec/son_jay_spec.rb
|
@@ -172,6 +177,7 @@ test_files:
|
|
172
177
|
- features/support/env.rb
|
173
178
|
- spec/acts_as_model_spec.rb
|
174
179
|
- spec/model_array_spec.rb
|
180
|
+
- spec/object_model/extra_data_spec.rb
|
175
181
|
- spec/object_model/property_definition_spec.rb
|
176
182
|
- spec/object_model_spec.rb
|
177
183
|
- spec/son_jay_spec.rb
|