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 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