disposable 0.2.3 → 0.2.4
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 +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
|