disposable 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -5
- data/CHANGES.md +4 -0
- data/Gemfile +1 -1
- data/README.md +154 -1
- data/database.sqlite3 +0 -0
- data/disposable.gemspec +7 -7
- data/gemfiles/Gemfile.rails-3.0.lock +10 -8
- data/gemfiles/Gemfile.rails-3.2.lock +9 -7
- data/gemfiles/Gemfile.rails-4.0.lock +9 -7
- data/gemfiles/Gemfile.rails-4.1.lock +10 -8
- data/lib/disposable.rb +6 -7
- data/lib/disposable/callback.rb +174 -0
- data/lib/disposable/composition.rb +21 -58
- data/lib/disposable/expose.rb +49 -0
- data/lib/disposable/twin.rb +85 -38
- data/lib/disposable/twin/builder.rb +12 -30
- data/lib/disposable/twin/changed.rb +50 -0
- data/lib/disposable/twin/collection.rb +95 -0
- data/lib/disposable/twin/composition.rb +43 -15
- data/lib/disposable/twin/option.rb +1 -1
- data/lib/disposable/twin/persisted.rb +20 -0
- data/lib/disposable/twin/property_processor.rb +29 -0
- data/lib/disposable/twin/representer.rb +42 -14
- data/lib/disposable/twin/save.rb +19 -34
- data/lib/disposable/twin/schema.rb +31 -0
- data/lib/disposable/twin/setup.rb +38 -0
- data/lib/disposable/twin/sync.rb +114 -0
- data/lib/disposable/version.rb +1 -1
- data/test/api_semantics_test.rb +263 -0
- data/test/callback_group_test.rb +222 -0
- data/test/callbacks_test.rb +450 -0
- data/test/example.rb +40 -0
- data/test/expose_test.rb +92 -0
- data/test/persisted_test.rb +101 -0
- data/test/test_helper.rb +64 -0
- data/test/twin/benchmarking.rb +33 -0
- data/test/twin/builder_test.rb +32 -0
- data/test/twin/changed_test.rb +108 -0
- data/test/twin/collection_test.rb +223 -0
- data/test/twin/composition_test.rb +56 -25
- data/test/twin/expose_test.rb +73 -0
- data/test/twin/feature_test.rb +61 -0
- data/test/twin/from_test.rb +37 -0
- data/test/twin/inherit_test.rb +57 -0
- data/test/twin/option_test.rb +27 -0
- data/test/twin/readable_test.rb +57 -0
- data/test/twin/save_test.rb +192 -0
- data/test/twin/schema_test.rb +69 -0
- data/test/twin/setup_test.rb +139 -0
- data/test/twin/skip_unchanged_test.rb +64 -0
- data/test/twin/struct_test.rb +168 -0
- data/test/twin/sync_option_test.rb +228 -0
- data/test/twin/sync_test.rb +128 -0
- data/test/twin/twin_test.rb +49 -128
- data/test/twin/writeable_test.rb +56 -0
- metadata +106 -20
- data/STUFF +0 -4
- data/lib/disposable/twin/finders.rb +0 -29
- data/lib/disposable/twin/new.rb +0 -30
- data/lib/disposable/twin/save_.rb +0 -21
- data/test/composition_test.rb +0 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8eb1cfd5be42eb0cf007c4b9d84aee95b7a4275
|
4
|
+
data.tar.gz: 76ab1e429806470d007164fd17bdf80d0abeab1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dba5e116162e6fe0f33ff944ef1dbadb9fb8a5208c790fb92c5c0d68091a7edf83e91b9fca49029d8df00db458fb131e1be6c554293fbfcef23730034ada8602
|
7
|
+
data.tar.gz: e3d4ac5b51ea771c2444bbdb6b34bcfa05e5b2ff9513050915a470920fd2238e5a639a8ce69225a93cc2a876412759335a0cda068c4b11c0f61aab38ba8603b9
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -20,6 +20,8 @@ Twins may contain validations, nevertheless, in Trailblazer, validations (or "Co
|
|
20
20
|
|
21
21
|
## Twin
|
22
22
|
|
23
|
+
Twins are only # FIXME % slower than AR alone.
|
24
|
+
|
23
25
|
Twins implement light-weight decorators objects with a unified interface. They map objects, hashes, and compositions of objects, along with optional hashes to inject additional options.
|
24
26
|
|
25
27
|
Let me show you what I mean.
|
@@ -48,6 +50,8 @@ You need to pass model and the optional options to the twin constructor.
|
|
48
50
|
twin = Song::Twin.new(song, good?: true)
|
49
51
|
```
|
50
52
|
|
53
|
+
++++++ builders
|
54
|
+
|
51
55
|
## Reading
|
52
56
|
|
53
57
|
This will create a composition object of the actual model and the hash.
|
@@ -93,8 +97,157 @@ twin = Song::Twin.new(title: "Savior", good?: true)
|
|
93
97
|
|
94
98
|
## Compositions
|
95
99
|
|
100
|
+
## With Representers
|
101
|
+
|
102
|
+
they indirect data, the twin's attributes get assigned without writing to the persistence layer, yet.
|
103
|
+
|
104
|
+
## With Contracts
|
105
|
+
|
106
|
+
## Collections
|
107
|
+
|
108
|
+
Define collections using `::collection`.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class AlbumTwin < Disposable::Twin
|
112
|
+
collection :songs do
|
113
|
+
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
### API
|
118
|
+
|
119
|
+
The API is identical to `Array` with the following additions.
|
120
|
+
|
121
|
+
* `#<<(model)` adds item, wraps it in twin and tracks it via `#added`.
|
122
|
+
* `#insert(i, model)`, see `#<<`.
|
123
|
+
* `#delete(twin)`, removes twin from collection and tracks via `#deleted`.
|
124
|
+
* `#destroy(twin)`, removes twin from collection and tracks via `#deleted` and `#to_destroy` for destruction in `#save`.
|
125
|
+
|
126
|
+
### Semantics
|
127
|
+
|
128
|
+
Include `Twin::Collection::Semantics`.
|
129
|
+
|
130
|
+
Semantics are extensions to the pure Ruby array behavior and designed to deal with persistence layers like ActiveRecord or ROM.
|
131
|
+
|
132
|
+
* `#save` will call `destroy` on all models marked for destruction in `to_destroy`. Tracks destruction via `#destroyed`.
|
133
|
+
|
134
|
+
|
135
|
+
## Imperative Callbacks
|
136
|
+
|
137
|
+
Note: [Chapter 8 of the Trailblazer](http://leanpub.com/trailblazer) book is dedicated to callbacks and discusses them in great detail.
|
138
|
+
|
139
|
+
Callbacks use the fact that twins track state changes. This allows to execute callbacks on certain conditions.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
Callback.new(twin).on_create { |twin| .. }
|
143
|
+
Callback.new(twin.songs).on_add { |twin| .. }
|
144
|
+
Callback.new(twin.songs).on_add { |twin| .. }
|
145
|
+
```
|
146
|
+
|
147
|
+
It works as follows.
|
148
|
+
|
149
|
+
1. Twins track state changes, like _"item added to collection (`on_add`)"_ or _"property changed (`on_change`)"_.
|
150
|
+
2. You decide when to invoke one or a group of callbacks. This is why there's no `before_save` and the like anymore.
|
151
|
+
3. You also decide _what_ events to consider by calling the respective events only, like `on_add`.
|
152
|
+
4. The `Callback` will now find out which properties of the twin are affected and exectue your passed code for each of them.
|
153
|
+
|
154
|
+
This is called _Imperative Callback_ and the opposite of what you've learned from Rails.
|
155
|
+
|
156
|
+
By inversing the control, we don't need `before_` or `after_`. This is in your hands now and depends on where you invoke your callbacks.
|
157
|
+
|
158
|
+
## Events
|
159
|
+
|
160
|
+
The following events are available in `Callback`.
|
161
|
+
|
162
|
+
Don't confuse that with event triggering, though! Callbacks are passive, calling an event method means the callback will look for twins that have tracked the respective event (e.g. an twin has `change`d).
|
163
|
+
|
164
|
+
* `on_update`: Invoked when the underlying model was persisted, yet, at twin initialization and attributes have changed since then.
|
165
|
+
* `on_add`: For every twin that has been added to a collection.
|
166
|
+
* `on_add(:create)`: For every twin that has been added to a collection and got persisted. This will only pick up collection items after `sync` or `save`.
|
167
|
+
|
168
|
+
* `on_delete`: For every item that has been deleted from a collection.
|
169
|
+
* `on_destroy`: For every item that has been removed from a collection and physically destroyed.
|
170
|
+
|
171
|
+
* `on_change`: For every item that has changed attributes. When `persisted?` has flippend, this will be triggered, too.
|
172
|
+
* `on_change(:email)`: When the scalar field changed.
|
173
|
+
|
174
|
+
|
175
|
+
## Callback Groups
|
176
|
+
|
177
|
+
`Callback::Group` simplifies grouping callbacks and allows nesting.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class AfterSave < Disposable::Callback::Group
|
181
|
+
on_change :expire_cache!
|
182
|
+
|
183
|
+
collection :songs do
|
184
|
+
on_add :notify_album!
|
185
|
+
on_add :reset_song!
|
186
|
+
end
|
187
|
+
|
188
|
+
on_update :rehash_name!, property: :title
|
189
|
+
|
190
|
+
property :artist do
|
191
|
+
on_change :sing!
|
192
|
+
end
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
Calling that group on a twin will invoke all callbacks that apply, in the order they were added.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
AfterSave.new(twin).(context: self)
|
200
|
+
```
|
201
|
+
|
202
|
+
Methods like `:sing!` will be invoked on the `:context` object. Likewise, nested properties will be retrieved by simply calling the getter on the twin, like `twin.songs`.
|
203
|
+
|
204
|
+
Again, only the events that match will be invoked. If the top level twin hasn't changed, `expire_cache!` won't be invoked. This works by simply using `Callback` under the hood.
|
205
|
+
|
206
|
+
## Callback Inheritance
|
207
|
+
|
208
|
+
You can inherit groups, add and remove callbacks.
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
class EnhancedAfterSave < AfterSave
|
212
|
+
on_change :redo!
|
213
|
+
|
214
|
+
collection :songs do
|
215
|
+
on_add :rewind!
|
216
|
+
end
|
217
|
+
|
218
|
+
remove! :on_change, :expire_cache!
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
The callbacks will be _appended_ to the existing chain.
|
223
|
+
|
224
|
+
Instead of appending, you may also refine existing callbacks.
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
class EnhancedAfterSave < AfterSave
|
228
|
+
collection :songs, inherit: true do
|
229
|
+
on_delete :rewind!
|
230
|
+
end
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
This will add the `rewind!` callback to the `songs` property, resulting in the following chain.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
collection :songs do
|
238
|
+
on_add :notify_album!
|
239
|
+
on_add :reset_song!
|
240
|
+
on_delete :rewind!
|
241
|
+
end
|
242
|
+
```
|
243
|
+
|
244
|
+
## Builders
|
245
|
+
|
96
246
|
## Overriding Accessors
|
97
247
|
|
98
248
|
super
|
99
249
|
|
100
|
-
## Used In
|
250
|
+
## Used In
|
251
|
+
|
252
|
+
* [Reform](https://github.com/apotonick/reform) forms are based on twins and add a little bit of form decoration on top. Every nested form is a twin.
|
253
|
+
* [Trailblazer](https://github.com/apotonick/trailblazer) uses twins as decorators and callbacks in operations to structure business logic.
|
data/database.sqlite3
CHANGED
Binary file
|
data/disposable.gemspec
CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Disposable::VERSION
|
9
9
|
spec.authors = ["Nick Sutterer"]
|
10
10
|
spec.email = ["apotonick@gmail.com"]
|
11
|
-
spec.description = %q{
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.homepage = ""
|
11
|
+
spec.description = %q{Decorators on top of your ORM layer.}
|
12
|
+
spec.summary = %q{Decorators on top of your ORM layer with change tracking, collection semantics and nesting.}
|
13
|
+
spec.homepage = "https://github.com/apotonick/disposable"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
@@ -19,12 +19,12 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "uber"
|
22
|
-
spec.add_dependency "representable", "
|
22
|
+
spec.add_dependency "representable", ">= 2.2.3", "<= 2.3.0"
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
25
25
|
spec.add_development_dependency "rake"
|
26
|
-
spec.add_development_dependency "minitest"
|
27
|
-
|
28
|
-
|
26
|
+
spec.add_development_dependency "minitest"
|
27
|
+
spec.add_development_dependency "activerecord"
|
28
|
+
spec.add_development_dependency "sqlite3"
|
29
29
|
# spec.add_development_dependency "database_cleaner"
|
30
30
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
disposable (0.0.
|
5
|
-
representable (~> 2.0
|
4
|
+
disposable (0.0.8)
|
5
|
+
representable (~> 2.0)
|
6
6
|
uber
|
7
7
|
|
8
8
|
GEM
|
@@ -35,11 +35,11 @@ GEM
|
|
35
35
|
abstract (>= 1.0.0)
|
36
36
|
i18n (0.5.4)
|
37
37
|
json (1.8.1)
|
38
|
-
mini_portile (0.6.
|
38
|
+
mini_portile (0.6.2)
|
39
39
|
minitest (5.4.1)
|
40
|
-
multi_json (1.
|
41
|
-
nokogiri (1.6.
|
42
|
-
mini_portile (
|
40
|
+
multi_json (1.11.0)
|
41
|
+
nokogiri (1.6.6.2)
|
42
|
+
mini_portile (~> 0.6.0)
|
43
43
|
rack (1.2.8)
|
44
44
|
rack-mount (0.6.14)
|
45
45
|
rack (>= 1.0.0)
|
@@ -54,13 +54,14 @@ GEM
|
|
54
54
|
rake (10.3.2)
|
55
55
|
rdoc (3.12.2)
|
56
56
|
json (~> 1.4)
|
57
|
-
representable (2.
|
57
|
+
representable (2.1.5)
|
58
58
|
multi_json
|
59
59
|
nokogiri
|
60
60
|
uber (~> 0.0.7)
|
61
|
+
sqlite3 (1.3.10)
|
61
62
|
thor (0.14.6)
|
62
63
|
tzinfo (0.3.41)
|
63
|
-
uber (0.0.
|
64
|
+
uber (0.0.13)
|
64
65
|
|
65
66
|
PLATFORMS
|
66
67
|
ruby
|
@@ -72,3 +73,4 @@ DEPENDENCIES
|
|
72
73
|
minitest (= 5.4.1)
|
73
74
|
railties (~> 3.0.11)
|
74
75
|
rake
|
76
|
+
sqlite3
|
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
disposable (0.0.
|
5
|
-
representable (~> 2.0
|
4
|
+
disposable (0.0.8)
|
5
|
+
representable (~> 2.0)
|
6
6
|
uber
|
7
7
|
|
8
8
|
GEM
|
@@ -36,11 +36,11 @@ GEM
|
|
36
36
|
i18n (0.6.11)
|
37
37
|
journey (1.0.4)
|
38
38
|
json (1.8.1)
|
39
|
-
mini_portile (0.6.
|
39
|
+
mini_portile (0.6.2)
|
40
40
|
minitest (5.4.1)
|
41
41
|
multi_json (1.10.1)
|
42
|
-
nokogiri (1.6.
|
43
|
-
mini_portile (
|
42
|
+
nokogiri (1.6.6.2)
|
43
|
+
mini_portile (~> 0.6.0)
|
44
44
|
rack (1.4.5)
|
45
45
|
rack-cache (1.2)
|
46
46
|
rack (>= 0.4)
|
@@ -58,7 +58,7 @@ GEM
|
|
58
58
|
rake (10.3.2)
|
59
59
|
rdoc (3.12.2)
|
60
60
|
json (~> 1.4)
|
61
|
-
representable (2.
|
61
|
+
representable (2.1.5)
|
62
62
|
multi_json
|
63
63
|
nokogiri
|
64
64
|
uber (~> 0.0.7)
|
@@ -67,10 +67,11 @@ GEM
|
|
67
67
|
multi_json (~> 1.0)
|
68
68
|
rack (~> 1.0)
|
69
69
|
tilt (~> 1.1, != 1.3.0)
|
70
|
+
sqlite3 (1.3.10)
|
70
71
|
thor (0.19.1)
|
71
72
|
tilt (1.4.1)
|
72
73
|
tzinfo (0.3.41)
|
73
|
-
uber (0.0.
|
74
|
+
uber (0.0.13)
|
74
75
|
|
75
76
|
PLATFORMS
|
76
77
|
ruby
|
@@ -82,3 +83,4 @@ DEPENDENCIES
|
|
82
83
|
minitest (= 5.4.1)
|
83
84
|
railties (~> 3.2.0)
|
84
85
|
rake
|
86
|
+
sqlite3
|
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
disposable (0.0.
|
5
|
-
representable (~> 2.0
|
4
|
+
disposable (0.0.8)
|
5
|
+
representable (~> 2.0)
|
6
6
|
uber
|
7
7
|
|
8
8
|
GEM
|
@@ -33,11 +33,11 @@ GEM
|
|
33
33
|
builder (3.1.4)
|
34
34
|
erubis (2.7.0)
|
35
35
|
i18n (0.6.11)
|
36
|
-
mini_portile (0.6.
|
36
|
+
mini_portile (0.6.2)
|
37
37
|
minitest (4.2.0)
|
38
38
|
multi_json (1.10.1)
|
39
|
-
nokogiri (1.6.
|
40
|
-
mini_portile (
|
39
|
+
nokogiri (1.6.6.2)
|
40
|
+
mini_portile (~> 0.6.0)
|
41
41
|
rack (1.5.2)
|
42
42
|
rack-test (0.6.2)
|
43
43
|
rack (>= 1.0)
|
@@ -47,14 +47,15 @@ GEM
|
|
47
47
|
rake (>= 0.8.7)
|
48
48
|
thor (>= 0.18.1, < 2.0)
|
49
49
|
rake (10.3.2)
|
50
|
-
representable (2.
|
50
|
+
representable (2.1.5)
|
51
51
|
multi_json
|
52
52
|
nokogiri
|
53
53
|
uber (~> 0.0.7)
|
54
|
+
sqlite3 (1.3.10)
|
54
55
|
thor (0.19.1)
|
55
56
|
thread_safe (0.3.4)
|
56
57
|
tzinfo (0.3.41)
|
57
|
-
uber (0.0.
|
58
|
+
uber (0.0.13)
|
58
59
|
|
59
60
|
PLATFORMS
|
60
61
|
ruby
|
@@ -66,3 +67,4 @@ DEPENDENCIES
|
|
66
67
|
minitest (~> 4.2.0)
|
67
68
|
railties (~> 4.0.0)
|
68
69
|
rake
|
70
|
+
sqlite3
|
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
disposable (0.0.
|
5
|
-
representable (~> 2.0
|
4
|
+
disposable (0.0.8)
|
5
|
+
representable (~> 2.0)
|
6
6
|
uber
|
7
7
|
|
8
8
|
GEM
|
@@ -35,11 +35,11 @@ GEM
|
|
35
35
|
erubis (2.7.0)
|
36
36
|
i18n (0.6.11)
|
37
37
|
json (1.8.1)
|
38
|
-
mini_portile (0.6.
|
38
|
+
mini_portile (0.6.2)
|
39
39
|
minitest (5.4.1)
|
40
|
-
multi_json (1.
|
41
|
-
nokogiri (1.6.
|
42
|
-
mini_portile (
|
40
|
+
multi_json (1.11.0)
|
41
|
+
nokogiri (1.6.6.2)
|
42
|
+
mini_portile (~> 0.6.0)
|
43
43
|
rack (1.5.2)
|
44
44
|
rack-test (0.6.2)
|
45
45
|
rack (>= 1.0)
|
@@ -49,15 +49,16 @@ GEM
|
|
49
49
|
rake (>= 0.8.7)
|
50
50
|
thor (>= 0.18.1, < 2.0)
|
51
51
|
rake (10.3.2)
|
52
|
-
representable (2.
|
52
|
+
representable (2.1.5)
|
53
53
|
multi_json
|
54
54
|
nokogiri
|
55
55
|
uber (~> 0.0.7)
|
56
|
+
sqlite3 (1.3.10)
|
56
57
|
thor (0.19.1)
|
57
58
|
thread_safe (0.3.4)
|
58
59
|
tzinfo (1.2.2)
|
59
60
|
thread_safe (~> 0.1)
|
60
|
-
uber (0.0.
|
61
|
+
uber (0.0.13)
|
61
62
|
|
62
63
|
PLATFORMS
|
63
64
|
ruby
|
@@ -69,3 +70,4 @@ DEPENDENCIES
|
|
69
70
|
minitest (= 5.4.1)
|
70
71
|
railties (~> 4.1.0)
|
71
72
|
rake
|
73
|
+
sqlite3
|