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