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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDg5ODk2NGI0ZTU2MTc4ZjdjMzEzNWNiMjY2OTJmMjNkNmVhOTNlYw==
4
+ ZDJkNWY1ZGNjOTVhYWZlOTA0ODc0NDY5MzFlMjIzYjg1YzIzNDkzOQ==
5
5
  data.tar.gz: !binary |-
6
- MzgwYWExZDQ4YTc2MDlhNGQ1NTkyNDgxOWVjMmFjNDRkYzgxZmJkZg==
6
+ ZDI3OTM3Y2YyMGVmZjc0YzI2MzJlMDZlZDQ0NTI0NzQyMDVmMWI2YQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YzFlNjQ5MDEwYWEyN2M3OGE1NmJlNDFlZWMwMDc2NjM3ZjkzZTBmMDg1Zjc5
10
- M2FjMmQ1ODk4NjlkYzA4OGExYzU2ZmNmNzc3ZjM3MmJjZmZiZWMzMzJiYWYy
11
- YTdmY2E5MGQ0YjJlOTUwZGZmZDU4ZGJlMTFhZDRiNTRhYTZiMWU=
9
+ MDQ0YTUxYjY1ZGZjMWQzNDg4Yzk4YWZlZjViYzIyOWIzZmI1OGY4NjM3ODQ2
10
+ MTljZjc3NjExODkyNDNhMjU4OGZkNGJkZjYzZjI5ZWEzMWUwZGFjNjYyMjM2
11
+ NjYwZTMyYjMzZDNlMTI4NDE1YTEzZjE4ZTA4NDI5MjM3ZWNhZjI=
12
12
  data.tar.gz: !binary |-
13
- NzNjYTZkMGQ4ZThmZTU2M2QyZDVlMmJmZjFjYTc3OTIyZDRmZDA5OGQ5YTMw
14
- YzFlMDk1ZDBiNDEyYTQ4NDdlZGMzZjM0NzNkZGM3MDM2MjZhMTA2N2ZiMWU0
15
- NmQyZmFkMDY2OTJlZWFkYWUxODgzY2NjNmRiNjQ0ZjBmYjQyOTI=
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| eval( "obj.#{expr}" ) }
7
- expecteds = expected_exprs .map{ |expr| eval( expr ) }
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 'forwardable'
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
- class NameError < KeyError
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 []=(name, value)
38
- name = "#{name}"
39
- raise NameError.new(name) unless @data.has_key?(name)
40
- @data[name] = value
41
- end
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
@@ -5,7 +5,7 @@ module SonJay
5
5
  attr_reader :name, :model_class
6
6
 
7
7
  def initialize(name, instruction = nil)
8
- @name = name
8
+ @name = "#{name}".freeze
9
9
  @model_class = model_class_for_instruction( instruction )
10
10
  end
11
11
 
@@ -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( definitions )
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 properties(&property_initializations)
24
- @property_initializations = property_initializations
55
+ def extras_allowed?
56
+ @extras_allowed ||= false
25
57
  end
26
58
 
27
59
  def property_definitions
28
- @property_definitions ||= nil
29
- return @property_definitions if @property_definitions
60
+ @property_definitions ||= _evaluate_property_definitions
61
+ end
62
+
63
+ private
30
64
 
31
- definitions = []
65
+ def properties(&property_initializations)
66
+ _property_initializations << property_initializations
67
+ end
32
68
 
33
- definer = PropertiesDefiner.new( definitions )
34
- definer.instance_eval &@property_initializations
35
- @property_definitions = definitions
69
+ def allow_extras(allowed = true)
70
+ @extras_allowed = allowed
71
+ end
36
72
 
37
- validate_model_dependencies!
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
- private
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
- hard_model_dependencies.each do |d|
56
- next unless d.respond_to?( :validate_model_dependencies!, true )
57
- d.send :validate_model_dependencies!, dependants
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 hard_model_dependencies
109
+ def _hard_model_dependencies
62
110
  property_definitions.map( &:model_class ).compact.uniq
63
111
  end
64
112
  end
@@ -1,3 +1,3 @@
1
1
  module SonJay
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
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
@@ -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 access to an undefined property" do
93
- expect{ sonj_content['qq'] }.to raise_exception(
94
- described_class::Properties::NameError
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.3.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: 2014-05-24 00:00:00.000000000 Z
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