representable 2.1.8 → 2.2.0

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: 1474ed4afb6124795809e52491554dd58a2e13aa
4
- data.tar.gz: 60f358588f53073142fd022959f6f5994b64a3cc
3
+ metadata.gz: 887d742815c848ac3b63287c8858f2988a2de41d
4
+ data.tar.gz: 78fbbf862aed5b2de60a9bc2ed7375b327a3e058
5
5
  SHA512:
6
- metadata.gz: 1c242eac1d5b71200bfe7c67be8bacb9ef5f40431af318e2ce65d4b04a8626ce2f6f1fd2dbdf64844b96588adbd341b1d398ff4e4297190cf74b8db82590d46e
7
- data.tar.gz: a812ee6f07deff92f515d41fa185c40a45f00e83c37ff5df71c517aeddc834ec643ab79ff4a2e974fba22014cd60744545b8c2a38590521f19a1f6cef62d4959
6
+ metadata.gz: a4e93c1474e69fb743ca1072fcee054f2e075e4eaf2902b9c885ee7c86b4156db93900889820a5375d61e97a3585c14b2e70931c91722791aec7cf21606226f6
7
+ data.tar.gz: 93f25a5475e27863649981e0cb5bd553c81b5c02cfe1e16c4c630c368ec972938c4453aae7660ce0e1caf1e67e640a2ff590c5f58d17e7597e0b1abcbc0644a9
@@ -5,6 +5,8 @@ matrix:
5
5
  - rvm: 1.9.3
6
6
  - rvm: 2.0.0
7
7
  - rvm: 2.1.1
8
- - rvm: rbx-2.2.10
9
- - rvm: jruby-19mode
8
+ - rvm: 2.2.0
9
+ # FIXME: add rbx and jruby, again, fix ruby-prof.
10
+ # - rvm: rbx-2.2.10
11
+ # - rvm: jruby-19mode
10
12
 
data/CHANGES.md CHANGED
@@ -1,3 +1,34 @@
1
+ # 2.2.0
2
+
3
+ ## New Stuff
4
+
5
+ * Introduce `Representable::Cached` that will keep the mapper, which in turn will keep the bindings, which in turn will keep their representer, in case they're nested. You have to include this feature manually and you can expect a 50% and more speed-up for rendering and parsing. Not to speak about the reduced memory footprint.
6
+
7
+ ``ruby
8
+ class SongDecorator < Representable::Decorator
9
+ include Representable::JSON
10
+ feature Representable::Cached
11
+
12
+ # ..
13
+ end
14
+ ```
15
+ * Introduced `Decorator#update!` to re-use a decorator instance between requests. This will inject the represented object, only.
16
+
17
+ ```ruby
18
+ decorator = SongDecorator.new(song)
19
+ decorator.to_json(..)
20
+
21
+ decorator.update!(louder_song)
22
+ decorator.to_json(..)
23
+ ```
24
+
25
+ This is quite awesome.
26
+
27
+ ## API change.
28
+
29
+ * The `:extend` option only accepts one module. `extend: [Module, Module]` does no longer work and it actually didn't work in former versions of 2.x, anyway, it just included the first element of an array.
30
+ * Remove `Binding#representer_module`.
31
+
1
32
  # 2.1.8
2
33
 
3
34
  * API change: features are now included into inline representers in the order they were specified. This used to be the other way round and is, of course, wrong, in case a sub-feature wants to override an existing method introduced by an earlier feature.
@@ -8,6 +8,10 @@ require 'representable/for_collection'
8
8
  require 'representable/represent'
9
9
  require 'representable/declarative'
10
10
  require 'representable/apply'
11
+ require "representable/populator"
12
+ require "representable/deserializer"
13
+ require "representable/serializer"
14
+ require "representable/cached"
11
15
 
12
16
 
13
17
  require 'uber/callable'
@@ -32,28 +36,33 @@ module Representable
32
36
  private
33
37
  # Reads values from +doc+ and sets properties accordingly.
34
38
  def update_properties_from(doc, options, format)
35
- # deserialize_for(bindings, mapper ? , options)
36
- representable_mapper(format, options).deserialize(doc, options)
39
+ private_options = normalize_options!(options)
40
+
41
+ representable_mapper(format, options).deserialize(represented, doc, options, private_options)
37
42
  end
38
43
 
39
44
  # Compiles the document going through all properties.
40
45
  def create_representation_with(doc, options, format)
41
- representable_mapper(format, options).serialize(doc, options)
46
+ private_options = normalize_options!(options)
47
+
48
+ representable_mapper(format, options).serialize(represented, doc, options, private_options)
42
49
  end
43
50
 
44
51
  def representable_bindings_for(format, options)
45
- options = cleanup_options(options) # FIXME: make representable-options and user-options two different hashes.
46
-
47
- representable_attrs.collect {|attr| representable_binding_for(attr, format, options) }
48
- # representable_attrs.binding_cache[format] ||= representable_attrs.collect {|attr| representable_binding_for(attr, format, options) }
52
+ representable_attrs.collect {|definition| representable_binding_for(definition, format, options) }
49
53
  end
50
54
 
51
- def representable_binding_for(attribute, format, options)
52
- format.build(attribute, represented, self, options)
55
+ def representable_binding_for(definition, format, options)
56
+ format.build(definition, self)
53
57
  end
54
58
 
55
- def cleanup_options(options) # TODO: remove me. this clearly belongs in Representable.
56
- options.reject { |k,v| [:include, :exclude].include?(k) }
59
+ def normalize_options!(options)
60
+ # TODO: ideally, private_options would be nil if none set or so, so we could save a lot of time in nested objects.
61
+ private_options = {}
62
+ # return private_options if options.size == 0
63
+ private_options[:include] = options.delete(:include) if options[:include]
64
+ private_options[:exclude] = options.delete(:exclude) if options[:exclude]
65
+ private_options
57
66
  end
58
67
 
59
68
  def representable_attrs
@@ -62,11 +71,11 @@ private
62
71
 
63
72
  def representable_mapper(format, options)
64
73
  bindings = representable_bindings_for(format, options)
65
- Mapper.new(bindings, represented, options) # TODO: remove self, or do we need it? and also represented!
74
+ Mapper.new(bindings)
66
75
  end
67
76
 
68
77
  def representation_wrap(*args)
69
- representable_attrs.wrap_for(self.class.name, represented, *args)
78
+ representable_attrs.wrap_for(nil, represented, *args) { self.class.name }
70
79
  end
71
80
 
72
81
  def represented
@@ -1,7 +1,3 @@
1
- require "representable/populator"
2
- require "representable/deserializer"
3
- require "representable/serializer"
4
-
5
1
  module Representable
6
2
  # The Binding wraps the Definition instance for this property and provides methods to read/write fragments.
7
3
 
@@ -19,18 +15,41 @@ module Representable
19
15
  build_for(definition, *args)
20
16
  end
21
17
 
22
- def initialize(definition, represented, decorator, user_options={}) # TODO: remove default arg for user options.
23
- @definition = definition
18
+ def initialize(definition, parent_decorator)
19
+ @definition = definition
20
+ @parent_decorator = parent_decorator # DISCUSS: where's this needed?
24
21
 
25
- setup!(represented, decorator, user_options) # this can be used in #compile_fragment/#uncompile_fragment in case we wanna reuse the Binding instance.
22
+ # static options. do this once.
23
+ @representable = @definition.representable?
24
+ @name = @definition.name
25
+ @skip_filters = self[:readable]==false || self[:writeable]==false || self[:if] # Does this binding contain :if, :readable or :writeable settings?
26
+ @getter = @definition.getter
27
+ @setter = @definition.setter
28
+ @array = @definition.array?
29
+ @typed = @definition.typed?
30
+ @has_default = @definition.has_default?
26
31
  end
27
32
 
28
33
  attr_reader :user_options, :represented # TODO: make private/remove.
29
34
 
35
+ # DISCUSS: an overall strategy to speed up option reads will come around 3.0.
36
+ attr_reader :representable, :name, :getter, :setter, :array, :typed, :skip_filters, :has_default
37
+ alias_method :representable?, :representable
38
+ alias_method :array?, :array
39
+ alias_method :typed?, :typed
40
+ alias_method :skip_filters?, :skip_filters
41
+ alias_method :has_default?, :has_default
42
+
30
43
  def as # DISCUSS: private?
31
44
  @as ||= evaluate_option(:as)
32
45
  end
33
46
 
47
+ # DISCUSS:
48
+ # currently, we need to call B#update! before compile_fragment/uncompile_fragment.
49
+ # this will change to B#renderer(represented, options).call
50
+ # B#parser (represented, options).call
51
+ # goal is to have two objects for 2 entirely different tasks.
52
+
34
53
  # Retrieve value and write fragment to the doc.
35
54
  def compile_fragment(doc)
36
55
  evaluate_option(:writer, doc) do
@@ -55,7 +74,6 @@ module Representable
55
74
  end
56
75
 
57
76
  def render_fragment(value, doc)
58
- # DISCUSS: should we return a Skip object instead of this block trick? (same in Populator?)
59
77
  fragment = serialize(value) { return } # render fragments of hash, xml, yaml.
60
78
 
61
79
  write(doc, fragment)
@@ -75,7 +93,6 @@ module Representable
75
93
  evaluate_option(:parse_filter, value, doc) { value }
76
94
  end
77
95
 
78
-
79
96
  def get
80
97
  evaluate_option(:getter) do
81
98
  exec_context.send(getter)
@@ -89,20 +106,23 @@ module Representable
89
106
  end
90
107
 
91
108
  # DISCUSS: do we really need that?
109
+ # 1.38 0.104 0.021 0.000 0.083 40001 Representable::Binding#representer_module_for
110
+ # 1.13 0.044 0.017 0.000 0.027 40001 Representable::Binding#representer_module_for (with memoize).
92
111
  def representer_module_for(object, *args)
112
+ # TODO: cache this!
93
113
  evaluate_option(:extend, object) # TODO: pass args? do we actually have args at the time this is called (compile-time)?
94
114
  end
95
115
 
96
116
  # Evaluate the option (either nil, static, a block or an instance method call) or
97
117
  # executes passed block when option not defined.
98
118
  def evaluate_option(name, *args)
99
- unless proc = self[name]
119
+ unless proc = @definition[name] # TODO: this could dispatch directly to the @definition using #send?
100
120
  return yield if block_given?
101
121
  return
102
122
  end
103
123
 
104
124
  # TODO: it would be better if user_options was nil per default and then we just don't pass it into lambdas.
105
- options = self[:pass_options] ? Options.new(self, user_options, represented, decorator) : user_options
125
+ options = self[:pass_options] ? Options.new(self, user_options, represented, parent_decorator) : user_options
106
126
 
107
127
  proc.evaluate(exec_context, *(args<<options)) # from Uber::Options::Value.
108
128
  end
@@ -110,39 +130,12 @@ module Representable
110
130
  def [](name)
111
131
  @definition[name]
112
132
  end
113
- # TODO: i don't want to define all methods here, but it is faster!
114
- # TODO: test public interface.
115
- def getter
116
- @definition.getter
117
- end
118
- def setter
119
- @definition.setter
120
- end
121
- def typed?
122
- @definition.typed?
123
- end
124
- def representable?
125
- @definition.representable?
126
- end
127
- def has_default?(*args)
128
- @definition.has_default?(*args)
129
- end
130
- def name
131
- @definition.name
132
- end
133
- def representer_module
134
- @definition.representer_module
135
- end
136
- # perf : 1.7-1.9
137
- #extend Forwardable
138
- #def_delegators :@definition, *%w([] getter setter typed? representable? has_default? name representer_module)
139
- # perf : 1.7-1.9
140
- # %w([] getter setter typed? representable? has_default? name representer_module).each do |name|
141
- # define_method(name.to_sym) { |*args| @definition.send(name, *args) }
142
- # end
143
133
 
134
+ # 1.55 0.031 0.022 0.000 0.009 60004 Representable::Binding#skipable_empty_value?
135
+ # 1.51 0.030 0.022 0.000 0.008 60004 Representable::Binding#skipable_empty_value?
136
+ # after polymorphism:
137
+ # 1.44 0.031 0.022 0.000 0.009 60002 Representable::Binding#skipable_empty_value?
144
138
  def skipable_empty_value?(value)
145
- return true if array? and self[:render_empty] == false and value and value.size == 0 # TODO: change in 2.0, don't render emtpy.
146
139
  value.nil? and not self[:render_nil]
147
140
  end
148
141
 
@@ -151,48 +144,57 @@ module Representable
151
144
  value
152
145
  end
153
146
 
154
- def array?
155
- @definition.array?
156
- end
157
-
158
- private
159
- def setup!(represented, decorator, user_options)
160
- @represented = represented
161
- @decorator = decorator
162
- @user_options = user_options
147
+ # Note: this method is experimental.
148
+ def update!(represented, user_options)
149
+ @represented = represented
150
+ @user_options = user_options
163
151
 
164
152
  setup_exec_context!
165
153
  end
166
154
 
167
- def setup_exec_context!
168
- context = represented
169
- context = self if self[:exec_context] == :binding
170
- context = decorator if self[:exec_context] == :decorator
155
+ attr_accessor :cached_representer
171
156
 
172
- @exec_context = context
157
+ private
158
+ # 1.80 0.066 0.027 0.000 0.039 30002 Representable::Binding#setup_exec_context!
159
+ # 0.98 0.034 0.014 0.000 0.020 30002 Representable::Binding#setup_exec_context!
160
+ def setup_exec_context!
161
+ return @exec_context = @represented unless self[:exec_context]
162
+ @exec_context = self if self[:exec_context] == :binding
163
+ @exec_context = parent_decorator if self[:exec_context] == :decorator
173
164
  end
174
165
 
175
- attr_reader :exec_context, :decorator
166
+ attr_reader :exec_context, :parent_decorator
176
167
 
177
168
  def serialize(object, &block)
178
169
  serializer.call(object, &block)
179
170
  end
180
171
 
181
- def serializer_class
182
- Serializer
183
- end
172
+ module Factories
173
+ def serializer_class
174
+ Serializer
175
+ end
184
176
 
185
- def serializer
186
- serializer_class.new(self)
187
- end
177
+ def serializer
178
+ @serializer ||= serializer_class.new(self)
179
+ end
188
180
 
189
- def populator
190
- populator_class.new(self)
191
- end
181
+ def populator
182
+ @populator ||= populator_class.new(self)
183
+ end
184
+
185
+ def populator_class
186
+ Populator
187
+ end
192
188
 
193
- def populator_class
194
- Populator
189
+ def deserializer_class
190
+ Deserializer
191
+ end
192
+
193
+ def deserializer
194
+ @deserializer ||= deserializer_class.new(self)
195
+ end
195
196
  end
197
+ include Factories
196
198
 
197
199
 
198
200
  # Options instance gets passed to lambdas when pass_options: true.
@@ -210,6 +212,16 @@ module Representable
210
212
  def serializer_class
211
213
  Serializer::Collection
212
214
  end
215
+
216
+ def deserializer_class
217
+ Deserializer::Collection
218
+ end
219
+
220
+ def skipable_empty_value?(value)
221
+ # TODO: this can be optimized, again.
222
+ return true if value.nil? and not self[:render_nil] # FIXME: test this without the "and"
223
+ return true if self[:render_empty] == false and value and value.size == 0 # TODO: change in 2.0, don't render emtpy.
224
+ end
213
225
  end
214
226
 
215
227
  # and the same for hashes.
@@ -222,6 +234,10 @@ module Representable
222
234
  def serializer_class
223
235
  Serializer::Hash
224
236
  end
237
+
238
+ def deserializer_class
239
+ Deserializer::Hash
240
+ end
225
241
  end
226
242
  end
227
243
 
@@ -0,0 +1,58 @@
1
+ module Representable
2
+ # Using this module only makes sense with Decorator representers.
3
+ module Cached
4
+ # The main point here is that the decorator instance simply saves its mapper. Since the mapper
5
+ # in turn stores the bindings, we have a straight-forward way of "caching" the bindings without
6
+ # having to mess around on the class level: this all happens in the decorator _instance_.
7
+ #
8
+ # Every binding in turn stores its nested representer (if it has one), implementing a recursive caching.
9
+ #
10
+ # Decorator -> Mapper -> [Binding->Decorator, Binding]
11
+ def representable_mapper(format, options)
12
+ @mapper ||= super.tap do |mapper|
13
+ mapper.bindings(represented, options).each { |binding| binding.extend(Binding) }
14
+ end
15
+ end
16
+
17
+ # replace represented for each property in this representer.
18
+ def update!(represented)
19
+ @represented = represented
20
+ self
21
+ end
22
+
23
+ # TODO: also for deserializer.
24
+ # TODO: create Populator in Binding, too (easier to override).
25
+ module Binding
26
+ def serializer
27
+ @__serializer ||= super.tap do |serializer|
28
+ serializer.extend(Serializer)
29
+ end
30
+ end
31
+
32
+ def deserializer
33
+ @__deserializer ||= super.tap do |deserializer|
34
+ deserializer.extend(Serializer)
35
+ end
36
+ end
37
+ end
38
+
39
+ module Serializer
40
+ def prepare_for(mod, object)
41
+ if representer = @binding.cached_representer
42
+ return representer.update!(object)
43
+ end
44
+
45
+ # puts "--------> caching representer for #{object} in #{@binding.object_id}"
46
+ @binding.cached_representer = super(mod, object)
47
+ end
48
+
49
+ # for Deserializer::Collection.
50
+ # TODO: this is a temporary solution.
51
+ def item_deserializer
52
+ @__item_deserializer ||= super.tap do |deserializer|
53
+ deserializer.extend(Serializer)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -77,11 +77,13 @@ module Representable
77
77
  end
78
78
 
79
79
  # Computes the wrap string or returns false.
80
- def wrap_for(name, context, *args)
80
+ def wrap_for(name, context, *args, &block)
81
81
  return unless self[:wrap]
82
82
 
83
83
  value = self[:wrap].evaluate(context, *args)
84
84
 
85
+ name = yield if block_given? # DISCUSS: deprecate/restructure the entire wrapping flow.
86
+
85
87
  return infer_name_for(name) if value === true
86
88
  value
87
89
  end
@@ -36,21 +36,26 @@ module Representable
36
36
  object.send(@binding.deserialize_method, fragment, options)
37
37
  end
38
38
 
39
- def prepare(object)
40
- @binding.evaluate_option(:prepare, object) do
41
- prepare!(object)
39
+ module Prepare
40
+ def prepare(object)
41
+ @binding.evaluate_option(:prepare, object) do
42
+ prepare!(object)
43
+ end
42
44
  end
43
- end
44
45
 
45
- def prepare!(object)
46
- mod = @binding.representer_module_for(object)
46
+ def prepare!(object)
47
+ mod = @binding.representer_module_for(object)
47
48
 
48
- return object unless mod
49
+ return object unless mod
50
+
51
+ prepare_for(mod, object)
52
+ end
49
53
 
50
- mod = mod.first if mod.is_a?(Array) # TODO: deprecate :extend => [..]
51
- mod.prepare(object)
54
+ def prepare_for(mod, object)
55
+ mod.prepare(object)
56
+ end
52
57
  end
53
- # in deserialize, we should get the original object?
58
+ include Prepare
54
59
 
55
60
  def create_object(fragment, *args)
56
61
  instance_for(fragment, *args) or class_for(fragment, *args)
@@ -89,8 +94,11 @@ module Representable
89
94
 
90
95
  private
91
96
  def deserialize!(*args)
92
- # TODO: re-use deserializer.
93
- Deserializer.new(@binding).call(*args)
97
+ item_deserializer.call(*args)
98
+ end
99
+
100
+ def item_deserializer
101
+ @item_deserializer = Deserializer.new(@binding)
94
102
  end
95
103
  end
96
104
 
@@ -4,6 +4,7 @@ module Representable
4
4
  module Hash
5
5
  class Binding < Representable::Binding
6
6
  def self.build_for(definition, *args) # TODO: remove default arg.
7
+ # puts "@@@build@@ #{definition.inspect}"
7
8
  return Collection.new(definition, *args) if definition.array?
8
9
  return Hash.new(definition, *args) if definition.hash?
9
10
  new(definition, *args)
@@ -19,12 +19,12 @@ module Representable::Hash
19
19
 
20
20
 
21
21
  def create_representation_with(doc, options, format)
22
- bin = representable_mapper(format, options).bindings.first
22
+ bin = representable_mapper(format, options).bindings(represented, options).first
23
23
  bin.render_fragment(represented, doc)
24
24
  end
25
25
 
26
26
  def update_properties_from(doc, options, format)
27
- bin = representable_mapper(format, options).bindings.first
27
+ bin = representable_mapper(format, options).bindings(represented, options).first
28
28
  #value = bin.deserialize_from(doc)
29
29
  value = Deserializer::Collection.new(bin).call(doc)
30
30
  represented.replace(value)
@@ -1,14 +1,15 @@
1
1
  module Representable
2
2
  module HashMethods
3
3
  def create_representation_with(doc, options, format)
4
- bin = representable_mapper(format, options).bindings.first
5
- hash = filter_keys_for(represented, options)
4
+ hash = filter_keys_for!(represented, options) # FIXME: this modifies options and replicates logic from Representable.
5
+ bin = representable_mapper(format, options).bindings(represented, options).first
6
+
6
7
  bin.render_fragment(hash, doc) # TODO: Use something along Populator, which does
7
8
  end
8
9
 
9
10
  def update_properties_from(doc, options, format)
10
- bin = representable_mapper(format, options).bindings.first
11
- hash = filter_keys_for(doc, options)
11
+ hash = filter_keys_for!(doc, options)
12
+ bin = representable_mapper(format, options).bindings(represented, options).first
12
13
 
13
14
  value = Deserializer::Hash.new(bin).call(hash)
14
15
  # value = bin.deserialize_from(hash)
@@ -16,9 +17,11 @@ module Representable
16
17
  end
17
18
 
18
19
  private
19
- def filter_keys_for(hash, options)
20
- return hash unless props = options[:exclude] || options[:include]
21
- hash.reject { |k,v| options[:exclude] ? props.include?(k.to_sym) : !props.include?(k.to_sym) }
20
+ def filter_keys_for!(hash, options)
21
+ excluding = options[:exclude]
22
+ # TODO: use same filtering method as in normal representer in Representable#create_representation_with.
23
+ return hash unless props = options.delete(:exclude) || options.delete(:include)
24
+ hash.reject { |k,v| excluding ? props.include?(k.to_sym) : !props.include?(k.to_sym) }
22
25
  end
23
26
  end
24
27
  end
@@ -3,50 +3,59 @@ module Representable
3
3
  # Conditionals are handled here, too.
4
4
  class Mapper
5
5
  module Methods
6
- def initialize(bindings, represented, options) # TODO: get rid of represented dependency.
7
- @represented = represented # the (extended) model.
8
- @bindings = bindings
6
+ def initialize(bindings)
7
+ @bindings = bindings
9
8
  end
10
9
 
11
- attr_reader :bindings
10
+ def bindings(represented, options)
11
+ @bindings.each do |binding|
12
+ binding.update!(represented, options)
13
+ end
14
+ end
12
15
 
13
- def deserialize(doc, options)
14
- bindings.each do |bin|
15
- deserialize_property(bin, doc, options)
16
+ def deserialize(represented, doc, options, private_options)
17
+ bindings(represented, options).each do |bin|
18
+ deserialize_property(bin, doc, options, private_options)
16
19
  end
17
- @represented
20
+ represented
18
21
  end
19
22
 
20
- def serialize(doc, options)
21
- bindings.each do |bin|
22
- serialize_property(bin, doc, options)
23
+ def serialize(represented, doc, options, private_options)
24
+ bindings(represented, options).each do |bin|
25
+ serialize_property(bin, doc, options, private_options)
23
26
  end
24
27
  doc
25
28
  end
26
29
 
27
30
  private
28
- def serialize_property(binding, doc, options)
29
- return if skip_property?(binding, options.merge(:action => :serialize))
31
+ def serialize_property(binding, doc, options, private_options)
32
+ return if skip_property?(binding, private_options.merge(:action => :serialize))
30
33
  compile_fragment(binding, doc)
31
34
  end
32
35
 
33
- def deserialize_property(binding, doc, options)
34
- return if skip_property?(binding, options.merge(:action => :deserialize))
36
+ def deserialize_property(binding, doc, options, private_options)
37
+ return if skip_property?(binding, private_options.merge(:action => :deserialize))
35
38
  uncompile_fragment(binding, doc)
36
39
  end
37
40
 
38
41
  # Checks and returns if the property should be included.
39
- def skip_property?(binding, options)
40
- return true if skip_excluded_property?(binding, options) # no need for further evaluation when :exclude'ed
41
- return true if skip_protected_property(binding, options)
42
+ # 1.78 0.107 0.025 0.000 0.081 30002 Representable::Mapper::Methods#skip_property?
43
+ # 0.96 0.013 0.013 0.000 0.000 30002 Representable::Mapper::Methods#skip_property? hash only
44
+ # 1.15 0.025 0.016 0.000 0.009 30002 Representable::Mapper::Methods#skip_property?
45
+
46
+ def skip_property?(binding, private_options)
47
+ return unless private_options[:include] || private_options[:exclude] || binding.skip_filters?
48
+
49
+ return true if skip_excluded_property?(binding, private_options) # no need for further evaluation when :exclude'ed
50
+ return true if skip_protected_property(binding, private_options)
42
51
 
43
52
  skip_conditional_property?(binding)
44
53
  end
45
54
 
46
- def skip_excluded_property?(binding, options)
47
- return unless props = options[:exclude] || options[:include]
55
+ def skip_excluded_property?(binding, private_options)
56
+ return unless props = private_options[:exclude] || private_options[:include]
48
57
  res = props.include?(binding.name.to_sym)
49
- options[:include] ? !res : res
58
+ private_options[:include] ? !res : res
50
59
  end
51
60
 
52
61
  def skip_conditional_property?(binding)
@@ -56,8 +65,8 @@ module Representable
56
65
  end
57
66
 
58
67
  # DISCUSS: this could be just another :if option in a Pipeline?
59
- def skip_protected_property(binding, options)
60
- options[:action] == :serialize ? binding[:readable] == false : binding[:writeable] == false
68
+ def skip_protected_property(binding, private_options)
69
+ private_options[:action] == :serialize ? binding[:readable] == false : binding[:writeable] == false
61
70
  end
62
71
 
63
72
  def compile_fragment(bin, doc)
@@ -35,12 +35,8 @@ module Representable
35
35
  deserializer.call(fragment) # CollectionDeserializer/HashDeserializer/etc.
36
36
  end
37
37
 
38
- def deserializer_class
39
- Deserializer
40
- end
41
-
42
38
  def deserializer
43
- deserializer_class.new(@binding)
39
+ @binding.deserializer
44
40
  end
45
41
 
46
42
 
@@ -52,17 +48,9 @@ module Representable
52
48
  def deserialize(fragment)
53
49
  return deserializer.call(fragment)
54
50
  end
55
-
56
- def deserializer
57
- Deserializer::Collection.new(@binding)
58
- end
59
51
  end
60
52
 
61
53
  class Hash < self
62
- private
63
- def deserializer_class
64
- Deserializer::Hash
65
- end
66
54
  end
67
55
  end
68
56
  end
@@ -27,8 +27,10 @@ module Representable
27
27
  end
28
28
  end
29
29
 
30
+ # 0.33 0.004 0.004 0.000 0.000 20001 Hash#merge!
31
+ # 0.00 0.000 0.000 0.000 0.000 1 Hash#merge!
30
32
  def marshal(object, user_options)
31
- object.send(@binding.serialize_method, user_options.merge!({:wrap => false}))
33
+ object.send(@binding.serialize_method, user_options)
32
34
  end
33
35
 
34
36
 
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "2.1.8"
2
+ VERSION = "2.2.0"
3
3
  end
@@ -30,5 +30,7 @@ Gem::Specification.new do |s|
30
30
  s.add_development_dependency "mocha", ">= 0.13.0"
31
31
  s.add_development_dependency "mongoid"
32
32
  s.add_development_dependency "virtus"
33
- s.add_development_dependency "json", '~> 1.7.7'
33
+ s.add_development_dependency "json", '>= 1.7.7'
34
+
35
+ s.add_development_dependency "ruby-prof"
34
36
  end
@@ -5,7 +5,7 @@ class BindingTest < MiniTest::Spec
5
5
  let (:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) }
6
6
 
7
7
  describe "#skipable_empty_value?" do
8
- let (:binding) { Binding.new(render_nil_definition, nil, nil) }
8
+ let (:binding) { Binding.new(render_nil_definition, nil) }
9
9
 
10
10
  # don't skip when present.
11
11
  it { binding.skipable_empty_value?("Disconnect, Disconnect").must_equal false }
@@ -14,16 +14,16 @@ class BindingTest < MiniTest::Spec
14
14
  it { binding.skipable_empty_value?(nil).must_equal false }
15
15
 
16
16
  # skip when nil and :render_nil undefined.
17
- it { Binding.new(Representable::Definition.new(:song), nil, nil).skipable_empty_value?(nil).must_equal true }
17
+ it { Binding.new(Representable::Definition.new(:song), nil).skipable_empty_value?(nil).must_equal true }
18
18
 
19
19
  # don't skip when nil and :render_nil undefined.
20
- it { Binding.new(Representable::Definition.new(:song), nil, nil).skipable_empty_value?("Fatal Flu").must_equal false }
20
+ it { Binding.new(Representable::Definition.new(:song), nil).skipable_empty_value?("Fatal Flu").must_equal false }
21
21
  end
22
22
 
23
23
 
24
24
  describe "#default_for" do
25
25
  let (:definition) { Representable::Definition.new(:song, :default => "Insider") }
26
- let (:binding) { Binding.new(definition, nil, nil) }
26
+ let (:binding) { Binding.new(definition, nil) }
27
27
 
28
28
  # return value when value present.
29
29
  it { binding.default_for("Black And Blue").must_equal "Black And Blue" }
@@ -35,12 +35,12 @@ class BindingTest < MiniTest::Spec
35
35
  it { binding.default_for(nil).must_equal "Insider" }
36
36
 
37
37
  # return nil when value nil and render_nil: true.
38
- it { Binding.new(render_nil_definition, nil, nil).default_for(nil).must_equal nil }
38
+ it { Binding.new(render_nil_definition, nil).default_for(nil).must_equal nil }
39
39
 
40
40
  # return nil when value nil and render_nil: true, even when :default is set" do
41
- it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest"), nil, nil).default_for(nil).must_equal nil }
41
+ it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest"), nil).default_for(nil).must_equal nil }
42
42
 
43
43
  # return nil if no :default
44
- it { Binding.new(Representable::Definition.new(:song), nil, nil).default_for(nil).must_equal nil }
44
+ it { Binding.new(Representable::Definition.new(:song), nil).default_for(nil).must_equal nil }
45
45
  end
46
46
  end
@@ -0,0 +1,135 @@
1
+ require "test_helper"
2
+ require 'ruby-prof'
3
+
4
+ class CachedTest < MiniTest::Spec
5
+ # TODO: also test with feature(Cached)
6
+
7
+ module Model
8
+ Song = Struct.new(:title, :composer)
9
+ Album = Struct.new(:name, :songs, :artist)
10
+ Artist = Struct.new(:name, :hidden_taste)
11
+ end
12
+
13
+ class SongRepresenter < Representable::Decorator
14
+ include Representable::Hash
15
+ feature Representable::Cached
16
+
17
+ property :title, render_filter: lambda { |value, doc, options| "#{value}:#{options.user_options}" }, pass_options: true
18
+ property :composer, class: Model::Artist do
19
+ property :name
20
+ end
21
+ end
22
+
23
+ class AlbumRepresenter < Representable::Decorator
24
+ include Representable::Hash
25
+ include Representable::Cached
26
+
27
+ property :name
28
+ collection :songs, decorator: SongRepresenter, class: Model::Song
29
+ end
30
+
31
+
32
+ describe "serialization" do
33
+ let (:album_hash) { {"name"=>"Louder And Even More Dangerous", "songs"=>[{"title"=>"Southbound:{:volume=>10}"}, {"title"=>"Jailbreak:{:volume=>10}"}]} }
34
+
35
+ let (:song) { Model::Song.new("Jailbreak") }
36
+ let (:song2) { Model::Song.new("Southbound") }
37
+ let (:album) { Model::Album.new("Live And Dangerous", [song, song2, Model::Song.new("Emerald")]) }
38
+ let (:representer) { AlbumRepresenter.new(album) }
39
+
40
+ it do
41
+ album2 = Model::Album.new("Louder And Even More Dangerous", [song2, song])
42
+
43
+ # makes sure options are passed correctly.
44
+ representer.to_hash(volume: 9).must_equal({"name"=>"Live And Dangerous",
45
+ "songs"=>[{"title"=>"Jailbreak:{:volume=>9}"}, {"title"=>"Southbound:{:volume=>9}"}, {"title"=>"Emerald:{:volume=>9}"}]}) # called in Deserializer/Serializer
46
+
47
+ # representer becomes reusable as it is stateless.
48
+ representer.update!(album2)
49
+
50
+ # makes sure options are passed correctly.
51
+ representer.to_hash(volume:10).must_equal(album_hash)
52
+ end
53
+
54
+ # profiling
55
+ it "xx" do
56
+ RubyProf.start
57
+ representer.to_hash
58
+ res = RubyProf.stop
59
+
60
+ printer = RubyProf::FlatPrinter.new(res)
61
+
62
+ data = StringIO.new
63
+ printer.print(data)
64
+ data = data.string
65
+
66
+ printer.print(STDOUT)
67
+
68
+ # only 1 nested decorators are instantiated, Song.
69
+ data.must_match "1 <Class::Representable::Decorator>#prepare"
70
+ # a total of 4 properties in the object graph.
71
+ data.must_match "4 Representable::Binding#initialize"
72
+ # 2 mappers for Album, Song
73
+ data.must_match "2 Representable::Mapper::Methods#initialize"
74
+ # 6 deserializers as the songs collection uses 2.
75
+ data.must_match "3 Representable::Deserializer#initialize"
76
+ end
77
+ end
78
+
79
+
80
+ describe "deserialization" do
81
+ let (:album_hash) {
82
+ {
83
+ "name"=>"Louder And Even More Dangerous",
84
+ "songs"=>[
85
+ {"title"=>"Southbound", "composer"=>{"name"=>"Lynott"}},
86
+ {"title"=>"Jailbreak", "composer"=>{"name"=>"Phil Lynott"}},
87
+ {"title"=>"Emerald"}
88
+ ]
89
+ }
90
+ }
91
+
92
+ it do
93
+ album = Model::Album.new
94
+
95
+ AlbumRepresenter.new(album).from_hash(album_hash)
96
+
97
+ album.songs.size.must_equal 3
98
+ album.name.must_equal "Louder And Even More Dangerous"
99
+ album.songs[0].title.must_equal "Southbound"
100
+ album.songs[0].composer.name.must_equal "Lynott"
101
+ album.songs[1].title.must_equal "Jailbreak"
102
+ album.songs[1].composer.name.must_equal "Phil Lynott"
103
+ album.songs[2].title.must_equal "Emerald"
104
+ album.songs[2].composer.must_equal nil
105
+
106
+ # TODO: test options.
107
+ end
108
+
109
+ it do
110
+ representer = AlbumRepresenter.new(Model::Album.new)
111
+
112
+ RubyProf.start
113
+ representer.from_hash(album_hash)
114
+ res = RubyProf.stop
115
+
116
+ printer = RubyProf::FlatPrinter.new(res)
117
+
118
+ data = StringIO.new
119
+ printer.print(data)
120
+ data = data.string
121
+
122
+ # only 2 nested decorators are instantiated, Song, and Artist.
123
+ data.must_match "2 <Class::Representable::Decorator>#prepare"
124
+ # a total of 5 properties in the object graph.
125
+ data.must_match "5 Representable::Binding#initialize"
126
+ # three mappers for Album, Song, composer
127
+ data.must_match "3 Representable::Mapper::Methods#initialize"
128
+ # 6 deserializers as the songs collection uses 2.
129
+ data.must_match "6 Representable::Deserializer#initialize"
130
+ # one populater for every property.
131
+ data.must_match "5 Representable::Populator#initialize"
132
+ # printer.print(STDOUT)
133
+ end
134
+ end
135
+ end
@@ -214,8 +214,8 @@ class DefinitionTest < MiniTest::Spec
214
214
  describe "#create_binding" do
215
215
  it "executes the block (without special context)" do
216
216
  definition = Representable::Definition.new(:title, :binding => lambda { |*args| @binding = Representable::Binding.new(*args) })
217
- definition.create_binding(object=Object.new, nil, nil).must_equal @binding
218
- @binding.instance_variable_get(:@represented).must_equal object
217
+ definition.create_binding(object=Object.new).must_equal @binding
218
+ @binding.instance_variable_get(:@parent_decorator).must_equal object
219
219
  end
220
220
  end
221
221
 
@@ -15,7 +15,7 @@ class HashBindingTest < MiniTest::Spec
15
15
  describe "PropertyBinding" do
16
16
  describe "#read" do
17
17
  before do
18
- @property = Representable::Hash::Binding.new(Representable::Definition.new(:song), nil, nil)
18
+ @property = Representable::Hash::Binding.new(Representable::Definition.new(:song), nil)
19
19
  end
20
20
 
21
21
  it "returns fragment if present" do
@@ -35,7 +35,7 @@ class HashBindingTest < MiniTest::Spec
35
35
  describe "CollectionBinding" do
36
36
  describe "with plain text items" do
37
37
  before do
38
- @property = Representable::Hash::Binding::Collection.new(Representable::Definition.new(:songs, :collection => true), Album.new, nil)
38
+ @property = Representable::Hash::Binding::Collection.new(Representable::Definition.new(:songs, :collection => true), Album.new)
39
39
  end
40
40
 
41
41
  it "extracts with #read" do
@@ -56,7 +56,7 @@ class HashBindingTest < MiniTest::Spec
56
56
  describe "HashBinding" do
57
57
  describe "with plain text items" do
58
58
  before do
59
- @property = Representable::Hash::Binding::Hash.new(Representable::Definition.new(:songs, :hash => true), nil, nil)
59
+ @property = Representable::Hash::Binding::Hash.new(Representable::Definition.new(:songs, :hash => true), nil)
60
60
  end
61
61
 
62
62
  it "extracts with #read" do
@@ -72,7 +72,7 @@ class HashBindingTest < MiniTest::Spec
72
72
 
73
73
  describe "with objects" do
74
74
  before do
75
- @property = Representable::Hash::Binding::Hash.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter), nil, nil)
75
+ @property = Representable::Hash::Binding::Hash.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter), nil)
76
76
  end
77
77
 
78
78
  it "doesn't change the represented hash in #write" do
@@ -88,15 +88,15 @@ module JsonTest
88
88
 
89
89
  describe "#build_for" do
90
90
  it "returns TextBinding" do
91
- assert_kind_of Representable::Hash::Binding, Representable::Hash::Binding.build_for(Def.new(:band), nil, nil)
91
+ assert_kind_of Representable::Hash::Binding, Representable::Hash::Binding.build_for(Def.new(:band), nil)
92
92
  end
93
93
 
94
94
  it "returns HashBinding" do
95
- assert_kind_of Representable::Hash::Binding::Hash, Representable::Hash::Binding.build_for(Def.new(:band, :hash => true), nil, nil)
95
+ assert_kind_of Representable::Hash::Binding::Hash, Representable::Hash::Binding.build_for(Def.new(:band, :hash => true), nil)
96
96
  end
97
97
 
98
98
  it "returns CollectionBinding" do
99
- assert_kind_of Representable::Hash::Binding::Collection, Representable::Hash::Binding.build_for(Def.new(:band, :collection => true), nil, nil)
99
+ assert_kind_of Representable::Hash::Binding::Collection, Representable::Hash::Binding.build_for(Def.new(:band, :collection => true), nil)
100
100
  end
101
101
  end
102
102
 
@@ -119,7 +119,7 @@ module JsonTest
119
119
  module AlbumRepresenter
120
120
  include Representable::JSON
121
121
  property :best_song, :class => Song, :extend => SongRepresenter
122
- collection :songs, :class => Song, :extend => [SongRepresenter]
122
+ collection :songs, :class => Song, :extend => SongRepresenter
123
123
  end
124
124
 
125
125
 
@@ -29,7 +29,7 @@ class XMLBindingTest < MiniTest::Spec
29
29
  describe "AttributeBinding" do
30
30
  describe "with plain text items" do
31
31
  before do
32
- @property = Representable::XML::Binding::Attribute.new(Representable::Definition.new(:name, :attribute => true), nil, nil)
32
+ @property = Representable::XML::Binding::Attribute.new(Representable::Definition.new(:name, :attribute => true), nil)
33
33
  end
34
34
 
35
35
  it "extracts with #read" do
@@ -46,7 +46,7 @@ class XMLBindingTest < MiniTest::Spec
46
46
 
47
47
  describe "ContentBinding" do
48
48
  before do
49
- @property = Representable::XML::Binding::Content.new(Representable::Definition.new(:name, :content => true), nil, nil)
49
+ @property = Representable::XML::Binding::Content.new(Representable::Definition.new(:name, :content => true), nil)
50
50
  end
51
51
 
52
52
  it "extracts with #read" do
@@ -104,20 +104,20 @@ class XmlTest < MiniTest::Spec
104
104
 
105
105
  describe "XML::Binding#build_for" do
106
106
  it "returns AttributeBinding" do
107
- assert_kind_of XML::Binding::Attribute, XML::Binding.build_for(Def.new(:band, :as => "band", :attribute => true), nil, nil)
107
+ assert_kind_of XML::Binding::Attribute, XML::Binding.build_for(Def.new(:band, :as => "band", :attribute => true), nil)
108
108
  end
109
109
 
110
110
  it "returns Binding" do
111
- assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :class => Hash), nil, nil)
112
- assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :as => :content), nil, nil)
111
+ assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :class => Hash), nil)
112
+ assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :as => :content), nil)
113
113
  end
114
114
 
115
115
  it "returns CollectionBinding" do
116
- assert_kind_of XML::Binding::Collection, XML::Binding.build_for(Def.new(:band, :collection => :true), nil, nil)
116
+ assert_kind_of XML::Binding::Collection, XML::Binding.build_for(Def.new(:band, :collection => :true), nil)
117
117
  end
118
118
 
119
119
  it "returns HashBinding" do
120
- assert_kind_of XML::Binding::Hash, XML::Binding.build_for(Def.new(:band, :hash => :true), nil, nil)
120
+ assert_kind_of XML::Binding::Hash, XML::Binding.build_for(Def.new(:band, :hash => :true), nil)
121
121
  end
122
122
  end
123
123
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: representable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.8
4
+ version: 2.2.0
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-05-09 00:00:00.000000000 Z
11
+ date: 2015-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -140,16 +140,30 @@ dependencies:
140
140
  name: json
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - "~>"
143
+ - - ">="
144
144
  - !ruby/object:Gem::Version
145
145
  version: 1.7.7
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - "~>"
150
+ - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: 1.7.7
153
+ - !ruby/object:Gem::Dependency
154
+ name: ruby-prof
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
153
167
  description: Renders and parses JSON/XML/YAML documents from and to Ruby objects.
154
168
  Includes plain properties, collections, nesting, coercion and more.
155
169
  email:
@@ -171,6 +185,7 @@ files:
171
185
  - lib/representable/apply.rb
172
186
  - lib/representable/autoload.rb
173
187
  - lib/representable/binding.rb
188
+ - lib/representable/cached.rb
174
189
  - lib/representable/coercion.rb
175
190
  - lib/representable/config.rb
176
191
  - lib/representable/debug.rb
@@ -207,6 +222,7 @@ files:
207
222
  - test/as_test.rb
208
223
  - test/benchmarking.rb
209
224
  - test/binding_test.rb
225
+ - test/cached_test.rb
210
226
  - test/class_test.rb
211
227
  - test/coercion_test.rb
212
228
  - test/config/inherit_test.rb
@@ -277,4 +293,53 @@ signing_key:
277
293
  specification_version: 4
278
294
  summary: Renders and parses JSON/XML/YAML documents from and to Ruby objects. Includes
279
295
  plain properties, collections, nesting, coercion and more.
280
- test_files: []
296
+ test_files:
297
+ - test/as_test.rb
298
+ - test/benchmarking.rb
299
+ - test/binding_test.rb
300
+ - test/cached_test.rb
301
+ - test/class_test.rb
302
+ - test/coercion_test.rb
303
+ - test/config/inherit_test.rb
304
+ - test/config_test.rb
305
+ - test/decorator_scope_test.rb
306
+ - test/decorator_test.rb
307
+ - test/definition_test.rb
308
+ - test/example.rb
309
+ - test/examples/object.rb
310
+ - test/exec_context_test.rb
311
+ - test/features_test.rb
312
+ - test/filter_test.rb
313
+ - test/for_collection_test.rb
314
+ - test/generic_test.rb
315
+ - test/getter_setter_test.rb
316
+ - test/hash_bindings_test.rb
317
+ - test/hash_test.rb
318
+ - test/if_test.rb
319
+ - test/inherit_test.rb
320
+ - test/inheritable_test.rb
321
+ - test/inline_test.rb
322
+ - test/instance_test.rb
323
+ - test/is_representable_test.rb
324
+ - test/json_test.rb
325
+ - test/lonely_test.rb
326
+ - test/mongoid_test.rb
327
+ - test/nested_test.rb
328
+ - test/object_test.rb
329
+ - test/parse_strategy_test.rb
330
+ - test/pass_options_test.rb
331
+ - test/prepare_test.rb
332
+ - test/reader_writer_test.rb
333
+ - test/realistic_benchmark.rb
334
+ - test/represent_test.rb
335
+ - test/representable_test.rb
336
+ - test/schema_test.rb
337
+ - test/serialize_deserialize_test.rb
338
+ - test/skip_test.rb
339
+ - test/stringify_hash_test.rb
340
+ - test/test_helper.rb
341
+ - test/test_helper_test.rb
342
+ - test/wrap_test.rb
343
+ - test/xml_bindings_test.rb
344
+ - test/xml_test.rb
345
+ - test/yaml_test.rb