disposable 0.1.9 → 0.1.11

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: 8921eb326e4ef806db082f3a43372b32b2e76fc8
4
- data.tar.gz: 98ae84211f0b25a33ee0035237683d647d50a579
3
+ metadata.gz: 458a517ee02a889864487c957c7ca81d44b443d8
4
+ data.tar.gz: 3b491fa53805ca4f1c4540526d671310e0f34996
5
5
  SHA512:
6
- metadata.gz: daad1b6f0cd51e44c4d57fad2960070bb14cf33bf13028f378151648b421b72c9c29c2dd57d3f9d47b5641b337b8dcbd22d599f35a264082f1de5a68a86035d2
7
- data.tar.gz: 154096ab8f26fcacc523eb8569a7c0d73213b931a658e91492a51c947febc0282d114926ae32d892d2611b24dcedd1aafa039b9cf490abc75867adeb1a251edc
6
+ metadata.gz: 16d6c5a1231d4f85e5f42ab7d2ccac03ae9f2f3bc46a75ddb3f4b49260a7fff3814c2ab3e337a61d06e7733fd5c9d42f263c5f21b010457091864c3e1f8c2e03
7
+ data.tar.gz: 3b840942d484cff296d4495bda82eeb9268aff52565e8e385acf675152ffddc47b95d23b6ce6bc1da7015133a748de0654c4e50569ffc91a489d5e714c784ac4
data/CHANGES.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 0.1.11
2
+
3
+ * `:default` now accepts lambdas, too. Thanks to @johndagostino for implementing this.
4
+
5
+ # 0.1.10
6
+
7
+ * yanked.
8
+
1
9
  # 0.1.9
2
10
 
3
11
  * The `:twin` option is no longer evaluated at compile time, only inline twins are run through `::process_inline!`. This allows specifying twin classes in lambdas for lazy-loading, and recursive twins.
data/Gemfile CHANGED
@@ -4,3 +4,4 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  # gem 'representable', path: '../representable'
7
+ # gem "uber", path: "../uber"
data/README.md CHANGED
@@ -217,15 +217,14 @@ class AlbumTwin < Disposable::Twin
217
217
 
218
218
  property :title, default: "The Greatest Songs Ever Written"
219
219
  property :composer, default: Composer.new do
220
- property :name
220
+ property :name, default: -> { "Object-#{id}" }
221
221
  end
222
222
  end
223
223
  ```
224
224
 
225
- Default value is applied when the model's getter returns `nil`.
226
-
227
- Note that `:default` also works with `:virtual` and `readable: false`.
225
+ Default value is applied when the model's getter returns `nil` when _initializing_ the twin.
228
226
 
227
+ Note that `:default` also works with `:virtual` and `readable: false`. `:default` can also be a lambda which is then executed in twin context.
229
228
 
230
229
  ## Collections
231
230
 
data/database.sqlite3 CHANGED
Binary file
@@ -7,6 +7,20 @@ module Disposable::Twin::Default
7
7
  end
8
8
 
9
9
  def default_for(dfn, options)
10
- dfn[:default]
10
+ # TODO: introduce Null object in Declarative::Definition#[].
11
+ # dfn[:default].(self) # dfn#[] should return a Null object here if empty.
12
+ return unless dfn[:default]
13
+ dfn[:default].evaluate(self) # TODO: use .()
14
+ end
15
+
16
+ module ClassMethods
17
+ def property(name, options={}, &block)
18
+ options[:default] = Uber::Options::Value.new(options[:default]) if options[:default]
19
+ super
20
+ end
21
+ end
22
+
23
+ def self.included(includer)
24
+ includer.extend ClassMethods
11
25
  end
12
26
  end
@@ -1,31 +1,69 @@
1
- # TODO: this needs tests and should probably go to Representable. we can move tests from Reform for that.
1
+ # Schema::from allows to copy a representer structure. This will create "fresh" inline representers instead
2
+ # of inheriting/copying the original classes, making it a replication of the structure, only.
3
+ #
4
+ # Options allow to customize the copied representer.
5
+ #
6
+ # +:exclude+: ignore options from original Definition when copying.
7
+ #
8
+ # Provided block is run per newly created Definition.
9
+ # Schema.from(...) { |dfn| dfn[:readable] = true }
2
10
  class Disposable::Twin::Schema
3
- def self.from(source_class, options) # TODO: can we re-use this for all the decorator logic in #validate, etc?
4
- representer = Class.new(options[:superclass])
5
- representer.send :include, *options[:include]
11
+ def self.from(*args, &block)
12
+ new.from(*args, &block)
13
+ end
14
+
15
+ # Builds a new representer (structure only) from source_class.
16
+ def from(source_class, options, &block) # TODO: can we re-use this for all the decorator logic in #validate, etc?
17
+ representer = build_representer(options)
6
18
 
7
19
  source_representer = options[:representer_from].call(source_class)
8
20
 
9
21
  source_representer.representable_attrs.each do |dfn|
10
- local_options = dfn[options[:options_from]] || {} # e.g. deserializer: {..}.
11
- new_options = dfn.instance_variable_get(:@options).merge(local_options)
12
-
13
- from_scalar!(options, dfn, new_options, representer) && next unless dfn[:extend]
14
- from_inline!(options, dfn, new_options, representer)
22
+ dfn = build_definition!(options, dfn, representer, &block)
23
+ evaluate_block!(options, dfn, &block)
15
24
  end
16
25
 
17
26
  representer
18
27
  end
19
28
 
20
29
  private
21
- def self.from_scalar!(options, dfn, new_options, representer)
30
+ def build_representer(options)
31
+ Class.new(options[:superclass]) do
32
+ include *options[:include]
33
+ end
34
+ end
35
+
36
+ def build_definition!(options, source_dfn, representer, &block)
37
+ local_options = source_dfn[options[:options_from]] || {} # e.g. deserializer: {..}.
38
+
39
+ new_options = source_dfn.instance_variable_get(:@options).dup # copy original options.
40
+ exclude!(options, new_options)
41
+ new_options.merge!(local_options)
42
+
43
+ return from_scalar!(options, source_dfn, new_options, representer) if options[:recursive]==false
44
+ return from_scalar!(options, source_dfn, new_options, representer) unless source_dfn[:extend]
45
+ from_inline!(options, source_dfn, new_options, representer, &block)
46
+ end
47
+
48
+ def exclude!(options, dfn_options)
49
+ (options[:exclude_options] || []).each do |excluded|
50
+ dfn_options.delete(excluded)
51
+ end
52
+ end
53
+
54
+ def from_scalar!(options, dfn, new_options, representer)
22
55
  representer.property(dfn.name, new_options)
23
56
  end
24
57
 
25
- def self.from_inline!(options, dfn, new_options, representer)
58
+ def from_inline!(options, dfn, new_options, representer, &block)
26
59
  nested = dfn[:extend].evaluate(nil) # nested now can be a Decorator, a representer module, a Form, a Twin.
27
- dfn_options = new_options.merge(extend: from(nested, options))
60
+ dfn_options = new_options.merge(extend: from(nested, options, &block))
28
61
 
29
62
  representer.property(dfn.name, dfn_options)
30
63
  end
64
+
65
+ def evaluate_block!(options, definition)
66
+ return unless block_given?
67
+ yield definition
68
+ end
31
69
  end
@@ -4,33 +4,18 @@
4
4
  #
5
5
  # Twin.new(id: 1)
6
6
  module Struct
7
- def setup_properties!(options={})
8
- hash_representer.new(self).from_hash(@model.merge(options))
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.
9
10
  end
10
11
 
11
- def hash_representer
12
- Class.new(schema) do
13
- include Representable::Hash
14
- include Representable::Hash::AllowSymbols
15
-
16
- representable_attrs.each do |dfn|
17
- dfn.merge!(
18
- prepare: lambda { |model, *| model },
19
- instance: lambda { |model, *| model }, # FIXME: this is because Representable thinks this is typed? in Deserializer.
20
- representable: false,
21
- ) if dfn[:twin]
22
- end
23
- end
24
- end
25
-
26
- def sync_hash_representer
27
- hash_representer.clone.tap do |rpr|
28
- rpr.representable_attrs.each do |dfn|
29
- dfn.merge!(
30
- serialize: lambda { |model, *| model.sync! },
31
- representable: true
32
- ) if dfn[:twin]
33
- end
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 { |model, *| model },
16
+ serialize: lambda { |model, *| model.sync! },
17
+ representable: true
18
+ ) if dfn[:twin]
34
19
  end
35
20
  end
36
21
 
@@ -5,6 +5,19 @@
5
5
  # Note: #sync currently implicitly saves AR objects with collections
6
6
  class Disposable::Twin
7
7
  module Sync
8
+ # Creates a fresh copy of the internal representer and adds Representable::Hash.
9
+ # This is used wherever a hash transformation is needed.
10
+ def self.hash_representer(twin_class, &block)
11
+ Schema.from(twin_class,
12
+ recursive: false,
13
+ representer_from: lambda { |twin_class| twin_class.representer_class },
14
+ superclass: Representable::Decorator,
15
+ include: Representable::Hash,
16
+ exclude_options: [:default], # TODO: TEST IN default_test.
17
+ &block
18
+ )
19
+ end
20
+
8
21
  def sync_models(options={})
9
22
  return yield to_nested_hash if block_given?
10
23
 
@@ -17,6 +30,8 @@ class Disposable::Twin
17
30
 
18
31
  # Sync all scalar attributes, call sync! on nested and return model.
19
32
  def sync!(options) # semi-public.
33
+ # TODO: merge this into Sync::Run or something and use in Struct, too, so we don't
34
+ # need the representer anymore?
20
35
  options_for_sync = sync_options(Decorator::Options[options])
21
36
 
22
37
  schema.each(options_for_sync) do |dfn|
@@ -58,24 +73,22 @@ class Disposable::Twin
58
73
  module ClassMethods
59
74
  # Create a hash representer on-the-fly to serialize the form to a hash.
60
75
  def nested_hash_representer
61
- @nested_hash_representer ||= Class.new(representer_class) do
62
- include Representable::Hash
63
-
64
- representable_attrs.each do |dfn|
65
- dfn.merge!(
66
- readable: true, # the nested hash contains all fields.
67
- as: dfn[:private_name] # nested hash keys by model property names.
68
- )
69
-
70
- dfn.merge!(
71
- prepare: lambda { |model, *| model }, # TODO: why do we need that here?
72
- serialize: lambda { |form, args| form.to_nested_hash },
73
- ) if dfn[:twin]
74
-
75
- self
76
- end
77
- end
76
+ @nested_hash_representer ||= build_nested_hash_representer
78
77
  end
78
+
79
+ def build_nested_hash_representer
80
+ Sync.hash_representer(self) do |dfn|
81
+ dfn.merge!(
82
+ readable: true, # the nested hash contains all fields.
83
+ as: dfn[:private_name] # nested hash keys by model property names.
84
+ )
85
+
86
+ dfn.merge!(
87
+ prepare: lambda { |model, *| model }, # TODO: why do we need that here?
88
+ serialize: lambda { |form, args| form.to_nested_hash },
89
+ ) if dfn[:twin]
90
+ end
91
+ end # #build_nested_hash_representer
79
92
  end
80
93
  end
81
94
  include ToNestedHash
@@ -1,3 +1,3 @@
1
1
  module Disposable
2
- VERSION = "0.1.9"
2
+ VERSION = "0.1.11"
3
3
  end
@@ -1,13 +1,14 @@
1
1
  require "test_helper"
2
2
 
3
3
  class DefaultTest < Minitest::Spec
4
- Song = Struct.new(:title, :composer)
4
+ Song = Struct.new(:title, :genre, :composer)
5
5
  Composer = Struct.new(:name)
6
6
 
7
7
  class Twin < Disposable::Twin
8
8
  feature Default
9
9
 
10
- property :title, default: "Mediocore"
10
+ property :title, default: "Medio-Core"
11
+ property :genre, default: -> { "Punk Rock #{model.class}" }
11
12
  property :composer, default: Composer.new do
12
13
  property :name, default: "NOFX"
13
14
  end
@@ -15,16 +16,18 @@ class DefaultTest < Minitest::Spec
15
16
 
16
17
  # all given.
17
18
  it do
18
- twin = Twin.new(Song.new("Anarchy Camp", Composer.new("Nofx")))
19
+ twin = Twin.new(Song.new("Anarchy Camp", "Punk", Composer.new("Nofx")))
19
20
  twin.title.must_equal "Anarchy Camp"
21
+ twin.genre.must_equal "Punk"
20
22
  twin.composer.name.must_equal "Nofx"
21
23
  end
22
24
 
23
25
  # defaults, please.
24
26
  it do
25
27
  twin = Twin.new(Song.new)
26
- twin.title.must_equal "Mediocore"
28
+ twin.title.must_equal "Medio-Core"
27
29
  twin.composer.name.must_equal "NOFX"
30
+ twin.genre.must_equal "Punk Rock DefaultTest::Song"
28
31
  end
29
32
 
30
33
  # false value is not defaulted.
@@ -87,4 +90,4 @@ class DefaultWithStructTest < Minitest::Spec
87
90
 
88
91
  song.settings.must_equal({"enabled"=>"yes", "roles"=>{"admin"=>"maybe"}})
89
92
  end
90
- end
93
+ end
@@ -27,9 +27,11 @@ class ProcessInlineTest < MiniTest::Spec
27
27
  end
28
28
 
29
29
  it do
30
- twin = AlbumTwin.new(Album.new(Object, Object))
30
+ twin = AlbumTwin.new(Album.new(Object, Object, OpenStruct.new(composer: OpenStruct.new(composer: nil))))
31
31
  assert ! (twin.class < InlineTwin)
32
32
  assert (twin.artist.class < InlineTwin)
33
33
  assert ! (twin.composer.class < InlineTwin)
34
+
35
+ twin.recursive_composer.composer.composer.must_equal nil
34
36
  end
35
37
  end
@@ -66,6 +66,42 @@ class SchemaTest < MiniTest::Spec
66
66
  decorator.representable_attrs.get(:id).inspect.must_equal "#<Representable::Definition ==>id @options={:parse_filter=>[], :render_filter=>[], :as=>\"id\"}>"
67
67
  decorator.representable_attrs.get(:title).inspect.must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :parse_filter=>[], :render_filter=>[], :as=>\"title\"}>"
68
68
  end
69
+
70
+
71
+ # :exclude_options allows skipping particular options when copying.
72
+ it do
73
+ decorator = Disposable::Twin::Schema.from(Representer, superclass: Representable::Decorator,
74
+ representer_from: lambda { |nested| nested },
75
+ exclude_options: [:deserializer]
76
+ )
77
+
78
+ decorator.representable_attrs.get(:id).inspect.must_equal "#<Representable::Definition ==>id @options={:parse_filter=>[], :render_filter=>[], :as=>\"id\"}>"
79
+ decorator.representable_attrs.get(:title).inspect.must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :parse_filter=>[], :render_filter=>[], :as=>\"title\"}>"
80
+ decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :parse_filter=>[], :render_filter=>[]}>"
81
+ end
82
+
83
+
84
+ it "::from with block allows customizing every definition and returns representer" do
85
+ decorator = Disposable::Twin::Schema.from(Representer,
86
+ superclass: Representable::Decorator,
87
+ representer_from: lambda { |nested| nested },
88
+ ) { |dfn| dfn.merge!(amazing: true) }
89
+
90
+ decorator.representable_attrs.get(:id).inspect.must_equal "#<Representable::Definition ==>id @options={:parse_filter=>[], :render_filter=>[], :as=>\"id\", :amazing=>true}>"
91
+ decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :parse_filter=>[], :render_filter=>[], :amazing=>true}>"
92
+ end
93
+
94
+ it "recursive: false only copies first level" do
95
+ decorator = Disposable::Twin::Schema.from(Representer,
96
+ superclass: Representable::Decorator,
97
+ representer_from: lambda { |nested| nested },
98
+ recursive: false,
99
+ exclude_options: [:deserializer]
100
+ )
101
+
102
+ decorator.representable_attrs.get(:title).inspect.must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :parse_filter=>[], :render_filter=>[], :as=>\"title\"}>"
103
+ decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :parse_filter=>[], :render_filter=>[]}>"
104
+ end
69
105
  end
70
106
 
71
107
 
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.1.9
4
+ version: 0.1.11
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-07-21 00:00:00.000000000 Z
11
+ date: 2015-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uber
@@ -265,3 +265,4 @@ test_files:
265
265
  - test/twin/twin_test.rb
266
266
  - test/twin/virtual_test.rb
267
267
  - test/twin/writeable_test.rb
268
+ has_rdoc: