disposable 0.3.1 → 0.3.2

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