disposable 0.1.9 → 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
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: