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 +4 -4
- data/CHANGES.md +6 -0
- data/Gemfile +2 -0
- data/README.md +4 -0
- data/disposable.gemspec +2 -2
- data/lib/disposable/twin.rb +3 -0
- data/lib/disposable/twin/property/hash.rb +48 -0
- data/lib/disposable/twin/property/struct.rb +32 -0
- data/lib/disposable/twin/property/unnest.rb +21 -0
- data/lib/disposable/twin/setup.rb +10 -7
- data/lib/disposable/twin/struct.rb +6 -28
- data/lib/disposable/version.rb +1 -1
- data/test/twin/hash_test.rb +211 -0
- data/test/twin/struct_test.rb +7 -7
- data/test/twin/unnest_test.rb +24 -0
- data/test/twin/virtual_test.rb +15 -1
- metadata +9 -5
- data/lib/disposable/twin/jsonb.rb +0 -45
- data/test/twin/jsonb_test.rb +0 -118
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6e1ad3c3bf2e63ee78f3109a5dbdefc4a66e752
|
4
|
+
data.tar.gz: bdab6ed057dff4c820cb7bbb0e3129c566dbd7b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 565f5cce96dad1b569ec5747d4cab583de49e8ae93eb01b01ab0620b01f828f21b5c714bea8eef7a657b14c517ff460d99b8fa3fcce2cc53b099c2a6a9392deb
|
7
|
+
data.tar.gz: 0a89a7b63ff68773aaa44caaa92c1cc8128ef8b75d95b58036989f44f3c37a14c20f0cf3b21ff33c4b6b2417abc5147cb7fa82f6a0cf14e7a61949181dc11835
|
data/CHANGES.md
CHANGED
data/Gemfile
CHANGED
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`.
|
data/disposable.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
lib = File.expand_path(
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
3
|
+
require "disposable/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "disposable"
|
data/lib/disposable/twin.rb
CHANGED
@@ -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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
data/lib/disposable/version.rb
CHANGED
@@ -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.
|
data/test/twin/struct_test.rb
CHANGED
@@ -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
|
data/test/twin/virtual_test.rb
CHANGED
@@ -22,4 +22,18 @@ class VirtualTest < MiniTest::Spec
|
|
22
22
|
|
23
23
|
hash.must_equal("credit_card_number"=> "123")
|
24
24
|
}
|
25
|
-
|
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.
|
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-
|
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
|
data/test/twin/jsonb_test.rb
DELETED
@@ -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.
|