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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: abc930b3fee06aab580e516948996aff0e05ec45
4
- data.tar.gz: 4902d414b02e4fc94ad30ad360d99671f099b3fc
3
+ metadata.gz: 9b938f2252d58d5182b64346aa82ec1f16567837
4
+ data.tar.gz: bf6f9fcbd1cb340498f2764036b3dc9d738b4a28
5
5
  SHA512:
6
- metadata.gz: aa8ef4dae7775e779bb22de5bff27b7d503a7c9af92fef70b43ff3f679baecdbbacf9a53805882ee8368e7780adb9e5ac75a0f13bf9a4f70202f22ee75b09e1e
7
- data.tar.gz: c6c45cfcbd947aeb2a7c2cab6ea9b6d7157a2daba38e59c756f73dcb8ce7a534391c6355207e404aeb11dd89999db6703c12ba4f409f231c4668cf922d8956f0
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.
@@ -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
- def wrap_scalar(dfn, value)
96
- Twinner.new(dfn).(value)
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 wrap_collection(dfn, value)
100
- Collection.for_models(Twinner.new(dfn), value)
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
- @dfn = dfn
112
+ def initialize(twin, dfn)
113
+ @twin = twin
114
+ @dfn = dfn
108
115
  end
109
116
 
110
- def call(value)
111
- @dfn[:nested].new(value)
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
@@ -1,3 +1,3 @@
1
1
  module Disposable
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
@@ -1,26 +1,21 @@
1
1
  require "disposable/twin"
2
- require 'reform'
3
2
  require 'ostruct'
4
3
  require 'benchmark'
5
4
 
6
- class BandForm < Reform::Form
7
- property :name, validates: {presence: true}
5
+ class Band < Disposable::Twin
6
+ property :name
8
7
 
9
8
  collection :songs do
10
- property :title, validates: {presence: true}
9
+ property :title
11
10
  end
12
11
  end
13
12
 
14
- songs = 50.times.collect { OpenStruct.new(title: "Be Stag") }
15
- band = OpenStruct.new(name: "Teenage Bottlerock", songs: songs)
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
- 100.times.each do
21
- form = BandForm.new(band)
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
- let(:collection) { Disposable::Twin::Collection.new(Disposable::Twin::Twinner.new(Twin::Song.definitions.get(:album)), []) }
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
@@ -1,12 +1,20 @@
1
1
  # require "test_helper"
2
2
 
3
- # class TwinOptionTest < TwinTest
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 # DISCUSS: needed for #save.
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 :highlight?
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.highlight?.must_equal nil }
31
+ # it { twin.current_user.must_equal nil }
24
32
 
25
- # # passing both options.
26
- # it { Song.new(song, preview?: true, highlight?: false).preview?.must_equal true }
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.3
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: 2015-12-27 00:00:00.000000000 Z
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