disposable 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3da86b891ceddbf906a835e6a9ef6a697b56f367
4
- data.tar.gz: ae72874647302013e6f5ce630ed2966db1aceb79
3
+ metadata.gz: d6e1ad3c3bf2e63ee78f3109a5dbdefc4a66e752
4
+ data.tar.gz: bdab6ed057dff4c820cb7bbb0e3129c566dbd7b1
5
5
  SHA512:
6
- metadata.gz: 2742ca894ddafed28b2f3ca3206d9d1e318ebbd023013ccb1677d5e0b31bce388c4b5d903c3c1dd44c0b7183fde9d6f28c8587b64b0942d72a5398a30f039a05
7
- data.tar.gz: 7457ab52dce3383eb1cb4946bf7e163c8259f1ca44c1135aaa37a0345a153c1ec17d10b4715beedfb45c5a19b4d85bb113a5da83ae0dfb998f834e34f630ac1e
6
+ metadata.gz: 565f5cce96dad1b569ec5747d4cab583de49e8ae93eb01b01ab0620b01f828f21b5c714bea8eef7a657b14c517ff460d99b8fa3fcce2cc53b099c2a6a9392deb
7
+ data.tar.gz: 0a89a7b63ff68773aaa44caaa92c1cc8128ef8b75d95b58036989f44f3c37a14c20f0cf3b21ff33c4b6b2417abc5147cb7fa82f6a0cf14e7a61949181dc11835
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.3.2
2
+
3
+ * Rename `JSONB` to `Property::Hash`.
4
+ * Fix `::unnest` so it copies delegated property options correctly.
5
+ * Deprecate `Twin::Struct`.
6
+
1
7
  # 0.3.1
2
8
 
3
9
  * Introduce `Twin::JSONB` for easy access to hash fields using `Struct`.
data/Gemfile CHANGED
@@ -7,3 +7,5 @@ gem "representable", "3.0.0"
7
7
  # gem "declarative", path: "../declarative"
8
8
  # gem "declarative", github: "apotonick/declarative"
9
9
  gem "minitest-line"
10
+
11
+ gem "activerecord", "< 5.0.0"
data/README.md CHANGED
@@ -179,6 +179,10 @@ You can also specify nested objects with an explicit class.
179
179
  property :artist, twin: TwinArtist
180
180
  ```
181
181
 
182
+ ## Unnest
183
+
184
+ #todo: document
185
+
182
186
  ## Features
183
187
 
184
188
  You can simply `include` feature modules into twins. If you want a feature to be included into all inline twins of your schema, use `::feature`.
@@ -1,6 +1,6 @@
1
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("../lib", __FILE__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'disposable/version'
3
+ require "disposable/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "disposable"
@@ -50,6 +50,9 @@ module Disposable
50
50
  property(name, options.merge(collection: true), &block)
51
51
  end
52
52
 
53
+ require "disposable/twin/property/unnest"
54
+ include Property::Unnest
55
+
53
56
  # TODO: remove.
54
57
  def from_collection(collection)
55
58
  collection.collect { |model| new(model) }
@@ -0,0 +1,48 @@
1
+ require "disposable/twin/struct"
2
+
3
+ class Disposable::Twin
4
+ module Property
5
+ # trailblazer.to/gems/disposable/api.html#hash
6
+ module Hash
7
+ def self.included(includer)
8
+ # hash: true top-level properties need :default support.
9
+ includer.feature Default
10
+
11
+ # Recursively include Struct in :hash and nested properties.
12
+ # defaults is applied to all ::property calls.
13
+ includer.defaults do |name, options|
14
+ if options[:field] == :hash
15
+ hash_options
16
+ else
17
+ {}
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+ # Note that :_features `include`s modules in this order, first to last.
24
+ def self.hash_options
25
+ { _features: [NestedDefaults, Property::Struct, Hash::Sync], default: ->(*) { ::Hash.new } }
26
+ end
27
+
28
+ # NestedDefaults for properties nested in the top :hash column.
29
+ module NestedDefaults
30
+ def self.included(includer)
31
+ includer.defaults do |name, options|
32
+ if options[:_nested_builder] # DISCUSS: any other way to figure out we're nested?
33
+ Hash.hash_options
34
+ else
35
+ { }
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ module Sync
42
+ def sync!(options={})
43
+ @model.merge(super)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ class Disposable::Twin
2
+ module Property
3
+ # Twin that uses a hash to populate.
4
+ #
5
+ # Twin.new(id: 1)
6
+ module Struct
7
+ def read_value_for(dfn, options)
8
+ name = dfn[:name]
9
+ @model[name.to_s] || @model[name.to_sym] # TODO: test sym vs. str.
10
+ end
11
+
12
+ def sync_hash_representer # TODO: make this without representable, please.
13
+ Sync.hash_representer(self.class) do |dfn|
14
+ dfn.merge!(
15
+ prepare: lambda { |options| options[:input] },
16
+ serialize: lambda { |options| options[:input].sync! },
17
+ representable: true
18
+ ) if dfn[:nested]
19
+ end
20
+ end
21
+
22
+ def sync(options={})
23
+ sync_hash_representer.new(self).to_hash
24
+ end
25
+ alias_method :sync!, :sync
26
+
27
+ # So far, hashes can't be persisted separately.
28
+ def save!
29
+ end
30
+ end
31
+ end # Property
32
+ end
@@ -0,0 +1,21 @@
1
+ require "uber/delegates"
2
+
3
+ module Disposable::Twin::Property
4
+ module Unnest
5
+ # TODO: test that nested properties options are "unnested", too, e.g. populator.
6
+ def self.included(includer)
7
+ includer.send(:include, Uber::Delegates)
8
+ end
9
+
10
+ def unnest(name, options)
11
+ from = options.delete(:from)
12
+ # needed to make reform process this field.
13
+
14
+ options = definitions.get(from)[:nested].definitions.get(name).instance_variable_get(:@options) # FIXME.
15
+ options = options.merge(virtual: true, _inherited: true, private_name: nil)
16
+
17
+ property(name, options)
18
+ delegates from, name, "#{name}="
19
+ end
20
+ end
21
+ end
@@ -23,12 +23,15 @@ module Disposable
23
23
  end
24
24
 
25
25
  def setup_property!(dfn, options)
26
- value =
27
- if options.has_key?(name = dfn[:name].to_sym)
28
- options[dfn[:name].to_sym]
29
- else
30
- setup_value_for(dfn, options)
31
- end
26
+ if options.has_key?(name = dfn[:name].to_sym)
27
+ value = options[dfn[:name].to_sym]
28
+ return setup_write!(dfn, value)
29
+ end
30
+
31
+ value = setup_value_for(dfn, options)
32
+
33
+ # this sucks and is why i introduce pipetrees in 0.4.
34
+ return if dfn[:readable] == false && dfn[:default].nil?
32
35
 
33
36
  setup_write!(dfn, value) # note: even readable: false will be written to twin as nil.
34
37
  end
@@ -55,4 +58,4 @@ module Disposable
55
58
  end
56
59
  end # Setup
57
60
  end
58
- end
61
+ end
@@ -1,32 +1,10 @@
1
- module Disposable
2
- class Twin
3
- # Twin that uses a hash to populate.
4
- #
5
- # Twin.new(id: 1)
6
- module Struct
7
- def read_value_for(dfn, options)
8
- name = dfn[:name]
9
- @model[name.to_s] || @model[name.to_sym] # TODO: test sym vs. str.
10
- end
1
+ require "disposable/twin/property/struct"
11
2
 
12
- def sync_hash_representer # TODO: make this without representable, please.
13
- Sync.hash_representer(self.class) do |dfn|
14
- dfn.merge!(
15
- prepare: lambda { |options| options[:input] },
16
- serialize: lambda { |options| options[:input].sync! },
17
- representable: true
18
- ) if dfn[:nested]
19
- end
20
- end
21
-
22
- def sync(options={})
23
- sync_hash_representer.new(self).to_hash
24
- end
25
- alias_method :sync!, :sync
26
-
27
- # So far, hashes can't be persisted separately.
28
- def save!
29
- end
3
+ class Disposable::Twin
4
+ module Struct
5
+ def self.included(includer)
6
+ warn "[Disposable] The Struct module is deprecated, please use Property::Hash."
7
+ includer.send(:include, Property::Struct)
30
8
  end
31
9
  end
32
10
  end
@@ -1,3 +1,3 @@
1
1
  module Disposable
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
@@ -0,0 +1,211 @@
1
+ require "test_helper"
2
+ require "disposable/twin/property/hash"
3
+
4
+ class HashTest < MiniTest::Spec
5
+ Model = Struct.new(:id, :content)
6
+
7
+ class Song < Disposable::Twin
8
+ feature Sync
9
+ include Property::Hash
10
+
11
+ property :id
12
+ property :content, field: :hash do
13
+ property :title
14
+ property :band do
15
+ property :name
16
+
17
+ property :label do
18
+ property :location
19
+ end
20
+ end
21
+
22
+ collection :releases do
23
+ property :version
24
+ end
25
+ end
26
+ end
27
+
28
+ # puts Song.definitions.get(:content)[:nested].definitions.get(:band).inspect
29
+
30
+ it "allows reading from existing hash" do
31
+ model = Model.new(1, {})
32
+ model.inspect.must_equal "#<struct HashTest::Model id=1, content={}>"
33
+
34
+ song = Song.new(model)
35
+ song.id.must_equal 1
36
+ song.content.title.must_equal nil
37
+ song.content.band.name.must_equal nil
38
+ song.content.band.label.location.must_equal nil
39
+ song.content.releases.must_equal []
40
+
41
+ # model's hash hasn't changed.
42
+ model.inspect.must_equal "#<struct HashTest::Model id=1, content={}>"
43
+ end
44
+
45
+ it "defaults to hash when value is nil" do
46
+ model = Model.new(1)
47
+ model.inspect.must_equal "#<struct HashTest::Model id=1, content=nil>"
48
+
49
+ song = Song.new(model)
50
+ song.id.must_equal 1
51
+ song.content.title.must_equal nil
52
+ song.content.band.name.must_equal nil
53
+ song.content.band.label.location.must_equal nil
54
+
55
+ # model's hash hasn't changed.
56
+ model.inspect.must_equal "#<struct HashTest::Model id=1, content=nil>"
57
+ end
58
+
59
+ it "#sync writes to model" do
60
+ model = Model.new
61
+
62
+ song = Song.new(model)
63
+ song.content.band.label.location = "San Francisco"
64
+
65
+ song.sync
66
+
67
+ model.inspect.must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{\"location\"=>\"San Francisco\"}}, \"releases\"=>[]}>"
68
+ end
69
+
70
+ it "#appends to collections" do
71
+ model = Model.new
72
+
73
+ song = Song.new(model)
74
+ # song.content.releases.append(version: 1) # FIXME: yes, this happens!
75
+ song.content.releases.append("version" => 1)
76
+
77
+ song.sync
78
+
79
+ model.inspect.must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{}}, \"releases\"=>[{\"version\"=>1}]}>"
80
+ end
81
+
82
+ it "doesn't erase existing, undeclared content" do
83
+ model = Model.new(nil, {"artist"=>{}})
84
+
85
+ song = Song.new(model)
86
+ song.content.band.label.location = "San Francisco"
87
+
88
+ # puts song.content.class.ancestors
89
+ song.sync
90
+
91
+ model.inspect.must_equal "#<struct HashTest::Model id=nil, content={\"artist\"=>{}, \"band\"=>{\"label\"=>{\"location\"=>\"San Francisco\"}}, \"releases\"=>[]}>"
92
+ end
93
+
94
+ it "doesn't erase existing, undeclared content in existing content" do
95
+ model = Model.new(nil, {"band"=>{ "label" => { "owner" => "Brett Gurewitz" }, "genre" => "Punkrock" }})
96
+
97
+ song = Song.new(model)
98
+ song.content.band.label.location = "San Francisco"
99
+
100
+ song.sync
101
+
102
+ model.inspect.must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{\"owner\"=>\"Brett Gurewitz\", \"location\"=>\"San Francisco\"}, \"genre\"=>\"Punkrock\"}, \"releases\"=>[]}>"
103
+ end
104
+
105
+
106
+ describe "features propagation" do
107
+ module UUID
108
+ def uuid
109
+ "1224"
110
+ end
111
+ end
112
+
113
+ class Hit < Disposable::Twin
114
+ include Property::Hash
115
+ feature UUID
116
+
117
+ property :id
118
+ property :content, field: :hash do
119
+ property :title
120
+ property :band do
121
+ property :name
122
+ end
123
+ end
124
+ end
125
+
126
+ it "includes features into all nested twins" do
127
+ song = Hit.new(Model.new)
128
+ song.uuid.must_equal "1224"
129
+ song.content.uuid.must_equal "1224"
130
+ song.content.band.uuid.must_equal "1224"
131
+ end
132
+ end
133
+
134
+ describe "coercion" do
135
+ require "disposable/twin/coercion"
136
+ class Coercing < Disposable::Twin
137
+ include Property::Hash
138
+ feature Coercion
139
+
140
+ property :id, type: Types::Coercible::Int
141
+ property :content, field: :hash do
142
+ property :title
143
+ property :band do
144
+ property :name, type: Types::Coercible::String
145
+ end
146
+ end
147
+ end
148
+
149
+ it "coerces" do
150
+ song = Coercing.new(Model.new(1))
151
+ song.id = "9"
152
+ song.id.must_equal 9
153
+ song.content.band.name = 18
154
+ song.content.band.name.must_equal "18"
155
+ end
156
+ end
157
+
158
+ describe "::unnest" do
159
+ class Unnesting < Disposable::Twin
160
+ feature Sync
161
+ include Property::Hash
162
+
163
+ property :id
164
+ content=property :content, field: :hash do
165
+ property :title
166
+ property :band do
167
+ property :name
168
+
169
+ property :label do
170
+ property :location
171
+ end
172
+ end
173
+
174
+ collection :releases do
175
+ property :version
176
+ end
177
+ end
178
+
179
+ unnest :title, from: :content
180
+ unnest :band, from: :content
181
+ # property :title, virtual: true#, _inherit: true, nested: content[:nested].definitions.get(:title)[:nested]
182
+ # def title=(v)
183
+ # raise v.inspect
184
+ # content.title=(v)
185
+ # end
186
+ end
187
+
188
+ it "exposes reader and writer" do
189
+ model = Model.new(1, {title: "Bedroom Eyes"})
190
+ song = Unnesting.new(model)
191
+
192
+ # singular scalar accessors
193
+ song.content.title.must_equal "Bedroom Eyes"
194
+ song.title.must_equal "Bedroom Eyes"
195
+
196
+ song.title = "Notorious"
197
+ song.title.must_equal "Notorious"
198
+ song.content.title.must_equal "Notorious"
199
+
200
+ # singular nested accessors
201
+ song.band.name.must_equal nil
202
+ song.content.band.name.must_equal nil
203
+ song.band.name = "Duran Duran"
204
+ song.band.name.must_equal "Duran Duran"
205
+ end
206
+ end
207
+ end
208
+
209
+ # fixme: make sure default hash is different for every invocation, and not created at compile time.
210
+
211
+ # TODO: test that config is same and nested.
@@ -5,7 +5,7 @@ require 'disposable/twin/struct'
5
5
 
6
6
  class TwinStructTest < MiniTest::Spec
7
7
  class Song < Disposable::Twin
8
- include Struct
8
+ include Property::Struct
9
9
  property :number#, default: 1 # FIXME: this should be :default_if_nil so it becomes clear with a model.
10
10
  property :cool?
11
11
  end
@@ -57,18 +57,18 @@ class TwinWithNestedStructTest < MiniTest::Spec
57
57
  include Sync
58
58
 
59
59
  property :options do # don't call #to_hash, this is triggered in the twin's constructor.
60
- include Struct
60
+ include Property::Struct
61
61
  property :recorded
62
62
  property :released
63
63
 
64
64
  property :preferences do
65
- include Struct
65
+ include Property::Struct
66
66
  property :show_image
67
67
  property :play_teaser
68
68
  end
69
69
 
70
70
  collection :roles do
71
- include Struct
71
+ include Property::Struct
72
72
  property :name
73
73
  end
74
74
  end
@@ -171,7 +171,7 @@ end
171
171
 
172
172
  class StructReadableWriteableTest < Minitest::Spec
173
173
  class Song < Disposable::Twin
174
- include Struct
174
+ include Property::Struct
175
175
  property :length
176
176
  property :id, readable: false
177
177
  end
@@ -196,11 +196,11 @@ class DefaultWithStructTest < Minitest::Spec
196
196
  feature Sync
197
197
 
198
198
  property :settings, default: Hash.new do
199
- include Struct
199
+ include Property::Struct
200
200
 
201
201
  property :enabled, default: "yes"
202
202
  property :roles, default: Hash.new do
203
- include Struct
203
+ include Property::Struct
204
204
  property :admin, default: "maybe"
205
205
  end
206
206
  end
@@ -0,0 +1,24 @@
1
+ require "test_helper"
2
+
3
+ class UnnestTest < MiniTest::Spec
4
+ class Twin < Disposable::Twin
5
+ property :content do
6
+ property :id, nice: "yes"
7
+ collection :ids, status: "healthy"
8
+
9
+ property :email do
10
+ end
11
+ end
12
+
13
+ unnest :id, from: :content
14
+ unnest :ids, from: :content
15
+ unnest :email, from: :content
16
+ end
17
+
18
+ it "copies property option" do
19
+ Twin.definitions.get(:id).extend(Declarative::Inspect).inspect.must_equal %{#<Disposable::Twin::Definition: @options={:nice=>\"yes\", :private_name=>:id, :name=>\"id\", :readable=>false, :writeable=>false}>}
20
+ Twin.definitions.get(:ids).extend(Declarative::Inspect).inspect.must_equal %{#<Disposable::Twin::Definition: @options={:status=>\"healthy\", :collection=>true, :private_name=>:ids, :name=>\"ids\", :readable=>false, :writeable=>false}>}
21
+ # also copies :nested.
22
+ Twin.definitions.get(:email).extend(Declarative::Inspect).inspect.must_equal %{#<Disposable::Twin::Definition: @options={:private_name=>:email, :nested=>#<Class:>, :name=>\"email\", :readable=>false, :writeable=>false}>}
23
+ end
24
+ end
@@ -22,4 +22,18 @@ class VirtualTest < MiniTest::Spec
22
22
 
23
23
  hash.must_equal("credit_card_number"=> "123")
24
24
  }
25
- end
25
+
26
+ describe "setter should never be called with virtual:true" do
27
+ class Raising < Disposable::Twin
28
+ property :id, virtual: true
29
+
30
+ def id=(*)
31
+ raise "i should never be called!"
32
+ end
33
+ end
34
+
35
+ it "what" do
36
+ Raising.new(Object.new).id.must_equal nil
37
+ end
38
+ end
39
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: disposable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-26 00:00:00.000000000 Z
11
+ date: 2016-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uber
@@ -181,9 +181,11 @@ files:
181
181
  - lib/disposable/twin/composition.rb
182
182
  - lib/disposable/twin/default.rb
183
183
  - lib/disposable/twin/definitions.rb
184
- - lib/disposable/twin/jsonb.rb
185
184
  - lib/disposable/twin/parent.rb
186
185
  - lib/disposable/twin/persisted.rb
186
+ - lib/disposable/twin/property/hash.rb
187
+ - lib/disposable/twin/property/struct.rb
188
+ - lib/disposable/twin/property/unnest.rb
187
189
  - lib/disposable/twin/property_processor.rb
188
190
  - lib/disposable/twin/save.rb
189
191
  - lib/disposable/twin/setup.rb
@@ -210,9 +212,9 @@ files:
210
212
  - test/twin/feature_test.rb
211
213
  - test/twin/from_collection_test.rb
212
214
  - test/twin/from_test.rb
215
+ - test/twin/hash_test.rb
213
216
  - test/twin/inherit_test.rb
214
217
  - test/twin/inheritance_test.rb
215
- - test/twin/jsonb_test.rb
216
218
  - test/twin/option_test.rb
217
219
  - test/twin/parent_test.rb
218
220
  - test/twin/process_inline_test.rb
@@ -224,6 +226,7 @@ files:
224
226
  - test/twin/sync_option_test.rb
225
227
  - test/twin/sync_test.rb
226
228
  - test/twin/twin_test.rb
229
+ - test/twin/unnest_test.rb
227
230
  - test/twin/virtual_test.rb
228
231
  - test/twin/writeable_test.rb
229
232
  homepage: https://github.com/apotonick/disposable
@@ -272,9 +275,9 @@ test_files:
272
275
  - test/twin/feature_test.rb
273
276
  - test/twin/from_collection_test.rb
274
277
  - test/twin/from_test.rb
278
+ - test/twin/hash_test.rb
275
279
  - test/twin/inherit_test.rb
276
280
  - test/twin/inheritance_test.rb
277
- - test/twin/jsonb_test.rb
278
281
  - test/twin/option_test.rb
279
282
  - test/twin/parent_test.rb
280
283
  - test/twin/process_inline_test.rb
@@ -286,5 +289,6 @@ test_files:
286
289
  - test/twin/sync_option_test.rb
287
290
  - test/twin/sync_test.rb
288
291
  - test/twin/twin_test.rb
292
+ - test/twin/unnest_test.rb
289
293
  - test/twin/virtual_test.rb
290
294
  - test/twin/writeable_test.rb
@@ -1,45 +0,0 @@
1
- require "disposable/twin/struct"
2
-
3
- class Disposable::Twin
4
- module JSONB
5
- def self.included(includer)
6
- # jsonb: true top-level properties need :default support.
7
- includer.feature Default
8
-
9
- # Recursively include Struct in :jsonb and nested properties.
10
- # defaults is applied to all ::property calls.
11
- includer.defaults do |name, options|
12
- if options[:jsonb] # only apply to `jsonb: true`.
13
- jsonb_options
14
- else
15
- {}
16
- end
17
- end
18
- end
19
-
20
- private
21
- # Note that :_features `include`s modules in this order, first to last.
22
- def self.jsonb_options
23
- { _features: [NestedDefaults, Struct, JSONB::Sync], default: ->(*) { Hash.new } }
24
- end
25
-
26
- # NestedDefaults for properties nested in the top :jsonb column.
27
- module NestedDefaults
28
- def self.included(includer)
29
- includer.defaults do |name, options|
30
- if options[:_nested_builder] # DISCUSS: any other way to figure out we're nested?
31
- JSONB.jsonb_options
32
- else
33
- { }
34
- end
35
- end
36
- end
37
- end
38
-
39
- module Sync
40
- def sync!(options={})
41
- @model.merge(super)
42
- end
43
- end
44
- end
45
- end
@@ -1,118 +0,0 @@
1
- require "test_helper"
2
- require "disposable/twin/jsonb"
3
-
4
- class JSONBTest < MiniTest::Spec
5
- Model = Struct.new(:id, :content)
6
-
7
- class Song < Disposable::Twin
8
- feature Sync
9
- include JSONB
10
-
11
- property :id
12
- property :content, jsonb: true do
13
- property :title
14
- property :band do
15
- property :name
16
-
17
- property :label do
18
- property :location
19
- end
20
- end
21
- end
22
- end
23
-
24
- # puts Song.definitions.get(:content)[:nested].definitions.get(:band).inspect
25
-
26
- it "allows reading from existing hash" do
27
- model = Model.new(1, {})
28
- model.inspect.must_equal "#<struct JSONBTest::Model id=1, content={}>"
29
-
30
- song = Song.new(model)
31
- song.id.must_equal 1
32
- song.content.title.must_equal nil
33
- song.content.band.name.must_equal nil
34
- song.content.band.label.location.must_equal nil
35
-
36
- # model's hash hasn't changed.
37
- model.inspect.must_equal "#<struct JSONBTest::Model id=1, content={}>"
38
- end
39
-
40
- it "defaults to hash when value is nil" do
41
- model = Model.new(1)
42
- model.inspect.must_equal "#<struct JSONBTest::Model id=1, content=nil>"
43
-
44
- song = Song.new(model)
45
- song.id.must_equal 1
46
- song.content.title.must_equal nil
47
- song.content.band.name.must_equal nil
48
- song.content.band.label.location.must_equal nil
49
-
50
- # model's hash hasn't changed.
51
- model.inspect.must_equal "#<struct JSONBTest::Model id=1, content=nil>"
52
- end
53
-
54
- it "#sync writes to model" do
55
- model = Model.new
56
-
57
- song = Song.new(model)
58
- song.content.band.label.location = "San Francisco"
59
-
60
- song.sync
61
-
62
- model.inspect.must_equal "#<struct JSONBTest::Model id=nil, content={\"band\"=>{\"label\"=>{\"location\"=>\"San Francisco\"}}}>"
63
- end
64
-
65
- it "doesn't erase existing, undeclared content" do
66
- model = Model.new(nil, {"artist"=>{}})
67
-
68
- song = Song.new(model)
69
- song.content.band.label.location = "San Francisco"
70
-
71
- # puts song.content.class.ancestors
72
- song.sync
73
-
74
- model.inspect.must_equal "#<struct JSONBTest::Model id=nil, content={\"artist\"=>{}, \"band\"=>{\"label\"=>{\"location\"=>\"San Francisco\"}}}>"
75
- end
76
-
77
- it "doesn't erase existing, undeclared content in existing content" do
78
- model = Model.new(nil, {"band"=>{ "label" => { "owner" => "Brett Gurewitz" }, "genre" => "Punkrock" }})
79
-
80
- song = Song.new(model)
81
- song.content.band.label.location = "San Francisco"
82
-
83
- song.sync
84
-
85
- model.inspect.must_equal "#<struct JSONBTest::Model id=nil, content={\"band\"=>{\"label\"=>{\"owner\"=>\"Brett Gurewitz\", \"location\"=>\"San Francisco\"}, \"genre\"=>\"Punkrock\"}}>"
86
- end
87
-
88
-
89
- describe "features propagation" do
90
- module UUID
91
- def uuid
92
- "1224"
93
- end
94
- end
95
-
96
- class Hit < Disposable::Twin
97
- include JSONB
98
- feature UUID
99
-
100
- property :id
101
- property :content, jsonb: true do
102
- property :title
103
- property :band do
104
- property :name
105
- end
106
- end
107
- end
108
-
109
- it "includes features into all nested twins" do
110
- song = Hit.new(Model.new)
111
- song.uuid.must_equal "1224"
112
- song.content.uuid.must_equal "1224"
113
- song.content.band.uuid.must_equal "1224"
114
- end
115
- end
116
- end
117
-
118
- # fixme: make sure default hash is different for every invocation, and not created at compile time.