disposable 0.3.0 → 0.3.1

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: ac8377b25f70507f4f8864eeb8305ece5340f560
4
- data.tar.gz: 14c1b2c3dfd514fb4d0fdd26e827dfea2cecfb83
3
+ metadata.gz: 3da86b891ceddbf906a835e6a9ef6a697b56f367
4
+ data.tar.gz: ae72874647302013e6f5ce630ed2966db1aceb79
5
5
  SHA512:
6
- metadata.gz: 5102f0279af1bed8b3a614833dc066b2e29e1ddff062896dfa2519bf31c67709119461399b8a6d03bf27b277428e503290e5e7f58d70d645e3c2ea14803a3af7
7
- data.tar.gz: 3a752d1130a9f3b391534d968bdffe5661d362ca40db1e1b3adf018948003c805b56b6b5ef34efb29c874ee9b559ba5af501f446f3eb74e43126ac60ef7a8a6e
6
+ metadata.gz: 2742ca894ddafed28b2f3ca3206d9d1e318ebbd023013ccb1677d5e0b31bce388c4b5d903c3c1dd44c0b7183fde9d6f28c8587b64b0942d72a5398a30f039a05
7
+ data.tar.gz: 7457ab52dce3383eb1cb4946bf7e163c8259f1ca44c1135aaa37a0345a153c1ec17d10b4715beedfb45c5a19b4d85bb113a5da83ae0dfb998f834e34f630ac1e
data/CHANGES.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 0.3.1
2
+
3
+ * Introduce `Twin::JSONB` for easy access to hash fields using `Struct`.
4
+
1
5
  # 0.3.0
2
6
 
3
7
  * Use [dry-types](https://github.com/dry-rb/dry-types) as a replacement for the deprecated virtus. You have to change to dry-types' constants.
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
4
  # gem "representable", path: "../representable"
5
- gem "representable", "2.4.0"
5
+ gem "representable", "3.0.0"
6
6
  # gem "representable", github: "apotonick/representable"
7
7
  # gem "declarative", path: "../declarative"
8
8
  # gem "declarative", github: "apotonick/declarative"
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib"]
19
19
 
20
20
  spec.add_dependency "uber"
21
- spec.add_dependency "declarative", "~> 0.0.6"
21
+ spec.add_dependency "declarative", ">= 0.0.8", "< 1.0.0"
22
22
  spec.add_dependency "representable", ">= 2.4.0", "<= 3.1.0"
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.3"
@@ -0,0 +1,45 @@
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
@@ -51,10 +51,11 @@ class Disposable::Twin
51
51
  next
52
52
  end
53
53
 
54
+ # First, call sync! on nested model(s).
54
55
  nested_model = PropertyProcessor.new(dfn, self, property_value).() { |twin| twin.sync!({}) }
55
-
56
56
  next if nested_model.nil?
57
57
 
58
+ # Then, write nested model to parent model, e.g. model.songs = [<Song>]
58
59
  mapper.send(dfn.setter, nested_model) # @model.artist = <Artist>
59
60
  end
60
61
 
@@ -1,3 +1,3 @@
1
1
  module Disposable
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -63,42 +63,3 @@ class DefaultAndVirtualTest < Minitest::Spec
63
63
  end
64
64
  end
65
65
 
66
-
67
- require "disposable/twin/struct"
68
- class DefaultWithStructTest < Minitest::Spec
69
- Song = Struct.new(:settings)
70
-
71
- class Twin < Disposable::Twin
72
- feature Default
73
- feature Sync
74
-
75
- property :settings, default: Hash.new do
76
- include Struct
77
-
78
- property :enabled, default: "yes"
79
- property :roles, default: Hash.new do
80
- include Struct
81
- property :admin, default: "maybe"
82
- end
83
- end
84
- end
85
-
86
- # all given.
87
- it do
88
- twin = Twin.new(Song.new({enabled: true, roles: {admin: false}}))
89
- twin.settings.enabled.must_equal true
90
- twin.settings.roles.admin.must_equal false
91
- end
92
-
93
- # defaults, please.
94
- it do
95
- song = Song.new
96
- twin = Twin.new(song)
97
- twin.settings.enabled.must_equal "yes"
98
- twin.settings.roles.admin.must_equal "maybe"
99
-
100
- twin.sync
101
-
102
- song.settings.must_equal({"enabled"=>"yes", "roles"=>{"admin"=>"maybe"}})
103
- end
104
- end
@@ -0,0 +1,118 @@
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.
@@ -66,18 +66,23 @@ class TwinWithNestedStructTest < MiniTest::Spec
66
66
  property :show_image
67
67
  property :play_teaser
68
68
  end
69
+
70
+ collection :roles do
71
+ include Struct
72
+ property :name
73
+ end
69
74
  end
70
75
  end
71
76
 
72
77
  # FIXME: test with missing hash properties, e.g. without released and with released:false.
73
78
  let (:model) { OpenStruct.new(title: "Seed of Fear and Anger", options: {recorded: true, released: 1,
74
- preferences: {show_image: true, play_teaser: 2}}) }
79
+ preferences: {show_image: true, play_teaser: 2}, roles: [{name: "user"}]}) }
75
80
 
76
81
  # public "hash" reader
77
82
  it { Song.new(model).options.recorded.must_equal true }
78
83
 
79
84
  # public "hash" writer
80
- it ("xxx") {
85
+ it {
81
86
  song = Song.new(model)
82
87
 
83
88
  song.options.recorded = "yo"
@@ -96,8 +101,231 @@ class TwinWithNestedStructTest < MiniTest::Spec
96
101
  model.options["preferences"].must_equal({"show_image" => 9, "play_teaser"=>2})
97
102
  }
98
103
 
104
+ describe "nested writes" do
105
+ let (:song) { Song.new(model) }
106
+
107
+ # adding to collection.
108
+ it do
109
+ # note that Struct-twin's public API wants a hash!
110
+ # it kinda sucks that the user has to know there's a hash model in place. is that what we want?
111
+ role = song.options.roles.append({}) # add empty "model" to hash collection.
112
+ role.name = "admin"
113
+
114
+ song.options.roles.size.must_equal 2
115
+ song.options.roles[0].name.must_equal "user"
116
+ song.options.roles[1].name.must_equal "admin"
117
+ model.options[:roles].must_equal([{:name=>"user"}]) # model hasn't changed, of course.
118
+
119
+ song.sync
120
+
121
+ model.options.must_equal({"recorded"=>true, "released"=>1, "preferences"=>{"show_image"=>true, "play_teaser"=>2}, "roles"=>[{"name"=>"user"}, {"name"=>"admin"}]})
122
+ end
123
+
124
+ # overwriting nested property via #preferences=.
125
+ it do
126
+ song.options.preferences = {play_teaser: :maybe}
127
+ song.sync
128
+
129
+ model.options.must_equal({"recorded"=>true, "released"=>1, "preferences"=>{"play_teaser"=>:maybe}, "roles"=>[{"name"=>"user"}]})
130
+ end
131
+
132
+ # overwriting collection via #roles=.
133
+ it do
134
+ song.options.roles = [{name: "wizard"}]
135
+ song.sync
136
+
137
+ model.options.must_equal({"recorded"=>true, "released"=>1, "preferences"=>{"show_image"=>true, "play_teaser"=>2}, "roles"=>[{"name"=>"wizard"}]})
138
+ end
139
+ end
140
+
141
+ # break dance
142
+ # it do
143
+ # song = Song.new(model)
144
+
145
+ # song.options.roles<<({name: "admin"})
146
+
147
+ # song.sync
148
+
149
+ # model[:options][:roles].must_equal({ })
150
+
151
+ # pp song
152
+
153
+
154
+ # song.options.preferences.sync!
155
+ # song.options.model.must_be_instance_of Hash
156
+ # song.options.preferences.model.must_be_instance_of Hash
157
+
158
+
159
+ # # this must break!
160
+ # # song.options.preferences = OpenStruct.new(play_teaser: true) # write property object to hash fragment.
161
+ # # this must break!
162
+ # song.options.roles = [Struct.new(:name)]
163
+ # song.options.sync!
164
+ # end
165
+
99
166
 
100
167
  describe "#save" do
101
168
  it { Song.new(model).extend(Disposable::Twin::Save).save }
102
169
  end
103
170
  end
171
+
172
+ class StructReadableWriteableTest < Minitest::Spec
173
+ class Song < Disposable::Twin
174
+ include Struct
175
+ property :length
176
+ property :id, readable: false
177
+ end
178
+
179
+ it "ignores readable: false" do
180
+ song = Song.new(length: 123, id: 1)
181
+ song.length.must_equal 123
182
+ song.id.must_equal nil
183
+ end
184
+
185
+ it "ignores writeable: false" do
186
+ skip
187
+ end
188
+ end
189
+
190
+ # Default has to be included.
191
+ class DefaultWithStructTest < Minitest::Spec
192
+ Song = Struct.new(:settings)
193
+
194
+ class Twin < Disposable::Twin
195
+ feature Default
196
+ feature Sync
197
+
198
+ property :settings, default: Hash.new do
199
+ include Struct
200
+
201
+ property :enabled, default: "yes"
202
+ property :roles, default: Hash.new do
203
+ include Struct
204
+ property :admin, default: "maybe"
205
+ end
206
+ end
207
+ end
208
+
209
+ # all given.
210
+ it do
211
+ twin = Twin.new(Song.new({enabled: true, roles: {admin: false}}))
212
+ twin.settings.enabled.must_equal true
213
+ twin.settings.roles.admin.must_equal false
214
+ end
215
+
216
+ # defaults, please.
217
+ it do
218
+ song = Song.new
219
+ twin = Twin.new(song)
220
+ twin.settings.enabled.must_equal "yes"
221
+ twin.settings.roles.admin.must_equal "maybe"
222
+
223
+ twin.sync
224
+
225
+ song.settings.must_equal({"enabled"=>"yes", "roles"=>{"admin"=>"maybe"}})
226
+ end
227
+ end
228
+
229
+ class CompositionWithStructTest < Minitest::Spec
230
+ class PersistedSheet < Disposable::Twin
231
+ feature Sync
232
+
233
+ property :content do
234
+ feature Struct
235
+
236
+ property :tags
237
+ collection :notes do
238
+ property :text
239
+ property :index
240
+ end
241
+ end
242
+
243
+ def tags=(*args)
244
+ content.tags=*args
245
+ end
246
+
247
+ def notes
248
+ content.notes
249
+ end
250
+ end
251
+
252
+ it "use nested twin but delegate" do
253
+ twin = PersistedSheet.new(model)
254
+
255
+ twin.tags = "history"
256
+
257
+ twin.notes.append text: "Canberra trip", index: 2
258
+
259
+ twin.sync
260
+
261
+ model.content.must_equal({"tags"=>["history"], "notes"=>[{"text"=>"Freedom", "index"=>0}, {"text"=>"Like", "index"=>1}, {"text"=>"Canberra trip", "index"=>2}]})
262
+ end
263
+
264
+ class Sheet < Disposable::Twin
265
+ include Composition
266
+ feature Sync
267
+
268
+ property :tags, on: :content #, twin: PersistedSheet.definitions.get[:content].definitions.get(:tags)
269
+ collection :notes, on: :content do
270
+ property :text
271
+ property :index
272
+ end#, twin: PersistedSheet.definitions.get(:content)[:nested].definitions.get(:notes)[:nested]
273
+
274
+
275
+ end
276
+
277
+ class Sheeeeeeet < Disposable::Twin
278
+ feature Sync
279
+ property :content do
280
+ property :tags
281
+ collection :notes do
282
+ property :text
283
+ property :index
284
+ end
285
+ end
286
+ end
287
+
288
+ let (:model) do
289
+ OpenStruct.new(
290
+ content: {
291
+ tags: "#hashtag",
292
+ notes: [
293
+ { text: "Freedom", created_at: nil, index: 0 },
294
+ { text: "Like", created_at: nil, index: 1 },
295
+ ]
296
+ }
297
+ )
298
+ end
299
+
300
+
301
+ let (:persisted_sheet) { PersistedSheet.new(model) }
302
+ let (:sheet) { Sheet.new(content: persisted_sheet.content) }
303
+
304
+ it do
305
+ skip
306
+ # this fails because the Sheet wants to write PersistedSheet.options.notes twins to the persisted sheet in #sync,
307
+ # instead of an array of hashes.
308
+
309
+
310
+
311
+ # sheeet= Sheeeeeeet.new(p= PersistedSheet.new(model))
312
+
313
+ # p.content = sheeet.content
314
+
315
+ # raise
316
+ # sheeet.sync
317
+ # raise
318
+
319
+
320
+
321
+
322
+ sheet.tags.must_equal "#hashtag"
323
+ sheet.notes[0].text.must_equal "Freedom"
324
+ sheet.notes[0].index.must_equal 0
325
+
326
+ sheet.notes[0].index = 2
327
+
328
+ sheet.sync
329
+
330
+ end
331
+ end
@@ -97,13 +97,20 @@ class OverridingAccessorsTest < TwinTest
97
97
  # overriding accessors in Twin
98
98
  class Song < Disposable::Twin
99
99
  property :title
100
+ property :id
100
101
 
101
102
  def title
102
103
  super.downcase
103
104
  end
105
+
106
+ def id=(v)
107
+ super(v+1)
108
+ end
104
109
  end
105
110
 
106
- it { Song.new(Model::Song.new(1, "A Tale That Wasn't Right")).title.must_equal "a tale that wasn't right" }
111
+ let (:model) { Model::Song.new(1, "A Tale That Wasn't Right") }
112
+ it { Song.new(model).title.must_equal "a tale that wasn't right" }
113
+ it { Song.new(model).id.must_equal 2 }
107
114
  end
108
115
 
109
116
 
@@ -130,4 +137,4 @@ class TwinAsTest < MiniTest::Spec
130
137
  end
131
138
 
132
139
  end
133
- # TODO: test coercion!
140
+ # TODO: test coercion!
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.0
4
+ version: 0.3.1
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-04-02 00:00:00.000000000 Z
11
+ date: 2016-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uber
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: declarative
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.8
34
+ - - "<"
32
35
  - !ruby/object:Gem::Version
33
- version: 0.0.6
36
+ version: 1.0.0
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.0.8
44
+ - - "<"
39
45
  - !ruby/object:Gem::Version
40
- version: 0.0.6
46
+ version: 1.0.0
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: representable
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -175,6 +181,7 @@ files:
175
181
  - lib/disposable/twin/composition.rb
176
182
  - lib/disposable/twin/default.rb
177
183
  - lib/disposable/twin/definitions.rb
184
+ - lib/disposable/twin/jsonb.rb
178
185
  - lib/disposable/twin/parent.rb
179
186
  - lib/disposable/twin/persisted.rb
180
187
  - lib/disposable/twin/property_processor.rb
@@ -205,6 +212,7 @@ files:
205
212
  - test/twin/from_test.rb
206
213
  - test/twin/inherit_test.rb
207
214
  - test/twin/inheritance_test.rb
215
+ - test/twin/jsonb_test.rb
208
216
  - test/twin/option_test.rb
209
217
  - test/twin/parent_test.rb
210
218
  - test/twin/process_inline_test.rb
@@ -266,6 +274,7 @@ test_files:
266
274
  - test/twin/from_test.rb
267
275
  - test/twin/inherit_test.rb
268
276
  - test/twin/inheritance_test.rb
277
+ - test/twin/jsonb_test.rb
269
278
  - test/twin/option_test.rb
270
279
  - test/twin/parent_test.rb
271
280
  - test/twin/process_inline_test.rb