disposable 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +5 -0
- data/README.md +43 -1
- data/lib/disposable/twin.rb +18 -12
- data/lib/disposable/twin/collection.rb +2 -2
- data/lib/disposable/twin/parent.rb +10 -0
- data/lib/disposable/version.rb +1 -1
- data/test/twin/benchmarking.rb +12 -13
- data/test/twin/collection_test.rb +2 -1
- data/test/twin/option_test.rb +18 -6
- data/test/twin/parent_test.rb +41 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b938f2252d58d5182b64346aa82ec1f16567837
|
4
|
+
data.tar.gz: bf6f9fcbd1cb340498f2764036b3dc9d738b4a28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d974e955ee6f3d0d39c6c83868c67c2c817a41ac5771e913e4311ec9b3933a6ce17e75dfe4493fc1fe6d15e91aba36e054a68305fde740d833520c2bda305ce2
|
7
|
+
data.tar.gz: 41485aeda7974a7986c350b47c7bfec88cf4de551929c68931b8fdfe6af17e974a25df74e28427dae20e49fdea8789eb77c623f69428f8b07981091aab7ac316
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# 0.2.4
|
2
|
+
|
3
|
+
* Add `Twin::Parent` to access a nested's parent twin.
|
4
|
+
* Introduce `Twin#build_for` and `Twin#build_twin`. This is a private API change.
|
5
|
+
|
1
6
|
# 0.2.3
|
2
7
|
|
3
8
|
* Add `Collection#find_by` for easier traversal/querying of twin collections: `album.songs.find_by(id: 1)`.
|
data/README.md
CHANGED
@@ -581,9 +581,51 @@ Properties can have various access settings.
|
|
581
581
|
* `writeable: false` won't write to model in `Sync`.
|
582
582
|
* `virtual: true` is both settings above combined.
|
583
583
|
|
584
|
+
## Options
|
585
|
+
|
586
|
+
To inject context data into a twin that is not part of any model, you can simply use `:virtual` properties.
|
587
|
+
|
588
|
+
```ruby
|
589
|
+
class AlbumTwin < Disposable::Twin
|
590
|
+
property :title
|
591
|
+
property :current_user, virtual: true
|
592
|
+
end
|
593
|
+
```
|
594
|
+
|
595
|
+
You can now pass the `current_user` as an option into the constructor and then access it via the reader.
|
596
|
+
|
597
|
+
```ruby
|
598
|
+
twin = AlbumTwin.new(album, current_user: User.find(1))
|
599
|
+
twin.current_user #=> <User id:1>
|
600
|
+
```
|
601
|
+
|
602
|
+
## Parent
|
603
|
+
|
604
|
+
By using the `Parent` feature you can access the parent twin of a nested one.
|
605
|
+
|
606
|
+
```ruby
|
607
|
+
class AlbumTwin < Disposable::Twin
|
608
|
+
feature Parent
|
609
|
+
|
610
|
+
property :artist do
|
611
|
+
property :name
|
612
|
+
end
|
613
|
+
end
|
614
|
+
```
|
615
|
+
|
616
|
+
Use `parent` to grab the nested's container twin.
|
617
|
+
|
618
|
+
```ruby
|
619
|
+
twin = AlbumTwin.new(Album.new(artist: Artist.new))
|
620
|
+
|
621
|
+
twin.artist.parent #=> twin
|
622
|
+
```
|
623
|
+
|
624
|
+
Note that this will internally add a `parent` property.
|
625
|
+
|
584
626
|
## Builders
|
585
627
|
|
586
628
|
## Used In
|
587
629
|
|
588
630
|
* [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.
|
589
|
-
* [Trailblazer](https://github.com/apotonick/trailblazer) uses twins as decorators and callbacks in operations to structure business logic.
|
631
|
+
* [Trailblazer](https://github.com/apotonick/trailblazer) uses twins as decorators and callbacks in operations to structure business logic.
|
data/lib/disposable/twin.rb
CHANGED
@@ -53,11 +53,13 @@ module Disposable
|
|
53
53
|
property(name, options.merge(collection: true), &block)
|
54
54
|
end
|
55
55
|
|
56
|
+
# TODO: remove.
|
56
57
|
def from_collection(collection)
|
57
58
|
collection.collect { |model| new(model) }
|
58
59
|
end
|
59
60
|
|
60
61
|
private
|
62
|
+
|
61
63
|
def create_accessors(name, definition)
|
62
64
|
mod = Module.new do
|
63
65
|
define_method(name) { @fields[name.to_s] }
|
@@ -75,9 +77,7 @@ module Disposable
|
|
75
77
|
private
|
76
78
|
# assumption: collections are always initialized from Setup since we assume an empty [] for "nil"/uninitialized collections.
|
77
79
|
def write_property(name, value, dfn)
|
78
|
-
if dfn[:nested] and value
|
79
|
-
value = dfn[:collection] ? wrap_collection(dfn, value) : wrap_scalar(dfn, value)
|
80
|
-
end
|
80
|
+
value = build_for(dfn, value) if dfn[:nested] and value
|
81
81
|
|
82
82
|
field_write(name, value)
|
83
83
|
end
|
@@ -92,27 +92,33 @@ module Disposable
|
|
92
92
|
@fields[name.to_s]
|
93
93
|
end
|
94
94
|
|
95
|
-
|
96
|
-
|
95
|
+
# Build a twin or a Twin::Collection for the value (which is a model or array of).
|
96
|
+
def build_for(dfn, *args)
|
97
|
+
dfn[:collection] ? build_collection(dfn, *args) : build_twin(dfn, *args)
|
97
98
|
end
|
98
99
|
|
99
|
-
def
|
100
|
-
|
100
|
+
def build_twin(dfn, *args)
|
101
|
+
dfn[:nested].new(*args) # Twin.new(model, options={})
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_collection(dfn, *args)
|
105
|
+
Collection.for_models(Twinner.new(self, dfn), *args)
|
101
106
|
end
|
102
107
|
end
|
103
108
|
include Accessors
|
104
109
|
|
110
|
+
# TODO: make this a function so it's faster at run-time.
|
105
111
|
class Twinner
|
106
|
-
def initialize(dfn)
|
107
|
-
@
|
112
|
+
def initialize(twin, dfn)
|
113
|
+
@twin = twin
|
114
|
+
@dfn = dfn
|
108
115
|
end
|
109
116
|
|
110
|
-
def call(
|
111
|
-
@
|
117
|
+
def call(*args)
|
118
|
+
@twin.send(:build_twin, @dfn, *args)
|
112
119
|
end
|
113
120
|
end
|
114
121
|
|
115
|
-
|
116
122
|
private
|
117
123
|
module ModelReaders
|
118
124
|
attr_reader :model # #model is a private concept.
|
@@ -3,8 +3,8 @@ module Disposable
|
|
3
3
|
# Provides collection semantics like add, delete, and more for twin collections.
|
4
4
|
# Tracks additions and deletions in #added and #deleted.
|
5
5
|
class Collection < Array
|
6
|
-
def self.for_models(twinner, models)
|
7
|
-
new(twinner, models.collect { |model| twinner.(model) })
|
6
|
+
def self.for_models(twinner, models, *options)
|
7
|
+
new(twinner, models.collect { |model| twinner.(model, *options) })
|
8
8
|
end
|
9
9
|
|
10
10
|
def initialize(twinner, items)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Disposable::Twin::Parent
|
2
|
+
def self.included(includer)
|
3
|
+
includer.property(:parent, virtual: true)
|
4
|
+
end
|
5
|
+
|
6
|
+
# FIXME: for collections, this will merge options for every element.
|
7
|
+
def build_twin(dfn, value, options={})
|
8
|
+
super(dfn, value, options.merge(parent: self))
|
9
|
+
end
|
10
|
+
end
|
data/lib/disposable/version.rb
CHANGED
data/test/twin/benchmarking.rb
CHANGED
@@ -1,26 +1,21 @@
|
|
1
1
|
require "disposable/twin"
|
2
|
-
require 'reform'
|
3
2
|
require 'ostruct'
|
4
3
|
require 'benchmark'
|
5
4
|
|
6
|
-
class
|
7
|
-
property :name
|
5
|
+
class Band < Disposable::Twin
|
6
|
+
property :name
|
8
7
|
|
9
8
|
collection :songs do
|
10
|
-
property :title
|
9
|
+
property :title
|
11
10
|
end
|
12
11
|
end
|
13
12
|
|
14
|
-
songs = 50.times.collect {
|
15
|
-
band =
|
16
|
-
|
17
|
-
songs_params = 50.times.collect { {title: "Commando"} }
|
13
|
+
songs = 50.times.collect { Struct.new(:title).new("Be Stag") }
|
14
|
+
band = Struct.new(:name, :songs).new("Teenage Bottlerock", songs)
|
18
15
|
|
19
16
|
time = Benchmark.measure do
|
20
|
-
|
21
|
-
|
22
|
-
form.validate("name" => "Ramones", "songs" => songs_params)
|
23
|
-
form.save
|
17
|
+
1000.times do
|
18
|
+
Band.new(band)
|
24
19
|
end
|
25
20
|
end
|
26
21
|
|
@@ -30,4 +25,8 @@ puts time
|
|
30
25
|
# 4.200000
|
31
26
|
# 20%
|
32
27
|
# with setup and new(fields).from_object(twin) instead of Fields.new(to_hash)
|
33
|
-
# 3.680000 0.000000 3.680000 ( 3.685796)
|
28
|
+
# 3.680000 0.000000 3.680000 ( 3.685796)
|
29
|
+
|
30
|
+
|
31
|
+
# 06/01
|
32
|
+
# 0.300000 0.000000 0.300000 ( 0.298956)
|
@@ -218,7 +218,8 @@ class CollectionUnitTest < MiniTest::Spec
|
|
218
218
|
Album = Struct.new(:id, :name, :songs, :artist)
|
219
219
|
end
|
220
220
|
|
221
|
-
|
221
|
+
# THIS is why private tests suck!
|
222
|
+
let(:collection) { Disposable::Twin::Collection.new(Disposable::Twin::Twinner.new(Twin::Song.new(OpenStruct.new), Twin::Song.definitions.get(:album)), []) }
|
222
223
|
|
223
224
|
# #insert(index, model)
|
224
225
|
it do
|
data/test/twin/option_test.rb
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
# require "test_helper"
|
2
2
|
|
3
|
-
# class TwinOptionTest <
|
3
|
+
# class TwinOptionTest < Minitest::Spec
|
4
|
+
# module Model
|
5
|
+
# Song = Struct.new(:id, :title, :album)
|
6
|
+
# end
|
7
|
+
|
4
8
|
# class Song < Disposable::Twin
|
5
|
-
# property :id
|
9
|
+
# property :id
|
6
10
|
# property :title
|
7
11
|
|
12
|
+
# def self.option(name, options={})
|
13
|
+
# property(name, options.merge(virtual: true))
|
14
|
+
# end
|
15
|
+
|
8
16
|
# option :preview?
|
9
|
-
# option :
|
17
|
+
# option :current_user
|
10
18
|
# end
|
11
19
|
|
12
20
|
# let (:song) { Model::Song.new(1, "Broken") }
|
@@ -20,8 +28,12 @@
|
|
20
28
|
# # option is not delegated to model.
|
21
29
|
# it { twin.preview?.must_equal false }
|
22
30
|
# # not passing option means zero.
|
23
|
-
# it { twin.
|
31
|
+
# it { twin.current_user.must_equal nil }
|
24
32
|
|
25
|
-
#
|
26
|
-
#
|
33
|
+
# describe "passing all options" do
|
34
|
+
# let (:twin) { Song.new(song, :preview? => false, current_user: Object) }
|
35
|
+
|
36
|
+
# it { twin.preview?.must_equal false }
|
37
|
+
# it { twin.current_user.must_equal Object }
|
38
|
+
# end
|
27
39
|
# end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "disposable/twin/parent.rb"
|
3
|
+
|
4
|
+
class TwinParentTest < MiniTest::Spec
|
5
|
+
module Model
|
6
|
+
Album = Struct.new(:id, :artist, :songs)
|
7
|
+
Artist = Struct.new(:name)
|
8
|
+
Song = Struct.new(:title, :composer)
|
9
|
+
end
|
10
|
+
|
11
|
+
class Album < Disposable::Twin
|
12
|
+
feature Parent
|
13
|
+
|
14
|
+
property :id
|
15
|
+
|
16
|
+
property :artist do
|
17
|
+
property :name
|
18
|
+
end
|
19
|
+
|
20
|
+
collection :songs do
|
21
|
+
property :title
|
22
|
+
property :composer do
|
23
|
+
property :name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
let (:album) { Album.new(Model::Album.new(1, Model::Artist.new("Helloween"), [Model::Song.new("I'm Alive", Model::Artist.new("Kai Hansen"))])) }
|
29
|
+
|
30
|
+
it { album.parent.must_equal nil }
|
31
|
+
it { album.artist.parent.must_equal album }
|
32
|
+
it { album.songs[0].parent.must_equal album }
|
33
|
+
it { album.songs[0].composer.parent.must_equal album.songs[0] }
|
34
|
+
|
35
|
+
describe "Collection#append" do
|
36
|
+
it do
|
37
|
+
album.songs.append(song = Model::Song.new)
|
38
|
+
album.songs[1].parent.must_equal album
|
39
|
+
end
|
40
|
+
end
|
41
|
+
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.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uber
|
@@ -175,6 +175,7 @@ files:
|
|
175
175
|
- lib/disposable/twin/composition.rb
|
176
176
|
- lib/disposable/twin/default.rb
|
177
177
|
- lib/disposable/twin/definitions.rb
|
178
|
+
- lib/disposable/twin/parent.rb
|
178
179
|
- lib/disposable/twin/persisted.rb
|
179
180
|
- lib/disposable/twin/property_processor.rb
|
180
181
|
- lib/disposable/twin/save.rb
|
@@ -204,6 +205,7 @@ files:
|
|
204
205
|
- test/twin/from_test.rb
|
205
206
|
- test/twin/inherit_test.rb
|
206
207
|
- test/twin/option_test.rb
|
208
|
+
- test/twin/parent_test.rb
|
207
209
|
- test/twin/process_inline_test.rb
|
208
210
|
- test/twin/readable_test.rb
|
209
211
|
- test/twin/save_test.rb
|
@@ -263,6 +265,7 @@ test_files:
|
|
263
265
|
- test/twin/from_test.rb
|
264
266
|
- test/twin/inherit_test.rb
|
265
267
|
- test/twin/option_test.rb
|
268
|
+
- test/twin/parent_test.rb
|
266
269
|
- test/twin/process_inline_test.rb
|
267
270
|
- test/twin/readable_test.rb
|
268
271
|
- test/twin/save_test.rb
|