representable 1.8.5 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGES.md +36 -0
  4. data/Gemfile +3 -3
  5. data/README.md +62 -2
  6. data/Rakefile +1 -1
  7. data/lib/representable.rb +32 -109
  8. data/lib/representable/TODO.getting_serious +7 -1
  9. data/lib/representable/autoload.rb +10 -0
  10. data/lib/representable/binding.rb +20 -16
  11. data/lib/representable/bindings/xml_bindings.rb +0 -1
  12. data/lib/representable/coercion.rb +23 -31
  13. data/lib/representable/config.rb +45 -46
  14. data/lib/representable/declarative.rb +78 -0
  15. data/lib/representable/decorator.rb +39 -10
  16. data/lib/representable/definition.rb +40 -33
  17. data/lib/representable/deserializer.rb +2 -0
  18. data/lib/representable/for_collection.rb +25 -0
  19. data/lib/representable/hash.rb +4 -8
  20. data/lib/representable/hash/collection.rb +2 -9
  21. data/lib/representable/hash_methods.rb +0 -7
  22. data/lib/representable/inheritable.rb +50 -0
  23. data/lib/representable/json.rb +3 -9
  24. data/lib/representable/json/collection.rb +1 -3
  25. data/lib/representable/json/hash.rb +4 -9
  26. data/lib/representable/mapper.rb +8 -5
  27. data/lib/representable/parse_strategies.rb +1 -0
  28. data/lib/representable/pipeline.rb +14 -0
  29. data/lib/representable/represent.rb +6 -0
  30. data/lib/representable/version.rb +1 -1
  31. data/lib/representable/xml.rb +3 -18
  32. data/lib/representable/xml/collection.rb +2 -4
  33. data/lib/representable/xml/hash.rb +2 -10
  34. data/lib/representable/yaml.rb +1 -20
  35. data/representable.gemspec +2 -2
  36. data/test/class_test.rb +5 -10
  37. data/test/coercion_test.rb +31 -92
  38. data/test/config/inherit_test.rb +128 -0
  39. data/test/config_test.rb +114 -80
  40. data/test/definition_test.rb +107 -64
  41. data/test/features_test.rb +41 -0
  42. data/test/filter_test.rb +59 -0
  43. data/test/for_collection_test.rb +74 -0
  44. data/test/inherit_test.rb +44 -3
  45. data/test/inheritable_test.rb +97 -0
  46. data/test/inline_test.rb +0 -18
  47. data/test/instance_test.rb +0 -19
  48. data/test/json_test.rb +9 -44
  49. data/test/lonely_test.rb +1 -0
  50. data/test/parse_strategy_test.rb +30 -0
  51. data/test/represent_test.rb +88 -0
  52. data/test/representable_test.rb +3 -50
  53. data/test/schema_test.rb +123 -0
  54. data/test/test_helper.rb +1 -1
  55. data/test/xml_test.rb +34 -38
  56. metadata +25 -15
  57. data/lib/representable/decorator/coercion.rb +0 -4
  58. data/lib/representable/readable_writeable.rb +0 -29
  59. data/test/inheritance_test.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a1a2857b730f1ccca3640f6c62cab57d6249e09
4
- data.tar.gz: 52ad685bb60805ff9d8d866e356a1cd33eac1633
3
+ metadata.gz: c06c246f8a877ca22b35b7e5f409be0f5b1d6922
4
+ data.tar.gz: 57183b1d4597321d6ed84278407032b04ac3eb8a
5
5
  SHA512:
6
- metadata.gz: 7a4c5aa9d35483807234ba1fe8c9e3b466c8d7e5879f38b5a635b68a0b2c1bb3df580841cadfe97159e4dddf7f63f9888dff7e73ddcc1a856270adb4d2894ad4
7
- data.tar.gz: 93c1a493c867c4628595298bc49df0b04e62b7789d0a48fad84983093cc10ee43771c14791e917d580d94825f7b4a17bfd8a50b804b1285c0ab95ad8ce2783c0
6
+ metadata.gz: ed0821431bd7bee01cf48dc470211a78cdaa80a6de27df4961bf9028f6824a5914bf16bf86ffd59ca4698f60c56362226421aa2ea011933de82aa3072c28d081
7
+ data.tar.gz: df87389a3a4b6e7841915dbf9b5ae999942f2640e70c27d199cc1ded97f1d2d1f9d6df3c400a3c76110176b5cdc55acdb5c73bb97da6b0a6986321e41a8dfb0a
@@ -5,6 +5,6 @@ matrix:
5
5
  - rvm: 1.9.3
6
6
  - rvm: 2.0.0
7
7
  - rvm: 2.1.1
8
- - rvm: rbx-2
8
+ - rvm: rbx-2.2.10
9
9
  - rvm: jruby-19mode
10
10
 
data/CHANGES.md CHANGED
@@ -1,3 +1,39 @@
1
+ # 2.0.0
2
+
3
+ ## Relevant
4
+
5
+ * Removed class methods `::from_json`, `::from_hash`, `::from_yaml` and `::from_xml`. Please build the instance yourself and use something along `Song.new.from_json`.
6
+ * Inline representers in `Decorator` do *no longer inherit from `self`*. When defining an inline representer, they are always derived from `Representable::Decorator`. The base class can be changed by overriding `Decorator::default_inline_class` in the decorator class that defines an inline representer block.
7
+ If you need to inherit common methods to all inline decorators, include the module using `::feature`: `Representer.feature(BetterProperty)`.
8
+ * Removed behaviour for `instance: lambda { |*| nil }` which used to return `binding.get`. Simply do it yourself: `instance: lambda { |fragment, options| options.binding.get }` if you need this behaviour. If you use `:instance` and it returns `nil` it throws a `DeserializeError` now, which is way more understandable than `NoMethodError: undefined method `title=' for {"title"=>"Perpetual"}:Hash`.
9
+ * Remove behaviour for `class: lambda { nil }` which used to return the fragment. This now throws a `DeserializeError`. Do it yourself with class: lambda { |fragment,*| fragment }.
10
+ * Coercion now happens inside `:render_filter` and `:parse_filter` (new!) and doesn't block `:getter` and `:setter` anymore.
11
+ We require virtus >=1.0 now.
12
+ * `::representation_wrap=` in now properly inherited.
13
+ * Including modules with representable `property .., inherit: true` into a `Decorator` crashed. This works fine now.
14
+
15
+ ## New Stuff
16
+
17
+ * Added `::for_collection` to automatically generate a collection representer for singular one. Thanks to @timoschilling for inspiring this years ago.
18
+ * Added `::represent` that will detect collections and render the singular/collection with the respective representer.
19
+ * Callable options
20
+ * :parse_filter, :render_filter
21
+
22
+ ## Internals
23
+
24
+ * Added `Representable::feature` to include a module and register it to be included into inline representers.
25
+ * New signature: `inline_representer(base, features, name, options, &block)`.
26
+ * Removed `::representer_engine`, the module to include is just another `register_feature` now.
27
+ * `Config` no longer is a Hash, it's API is limited to a few methods like `#<<`, `#[]` etc. It still supports the `Enumberable` interface.
28
+ * Moved `Representable::ClassMethods::Declarations` to `Representable::Declarative`.
29
+ * Moved `Representable::ClassMethods` to `Representable::Declarative`.
30
+ * Fixed: Inline decorators now work with `inherit: true`.
31
+ * Remove `:extend` in combination with inline representer. The `:extend` option is no longer considered. Include the module directly into the inline block.
32
+ * Deprecated class methods `::from_json` and friends. Use the instance version on an instance.
33
+ * Use uber 0.0.7 so we can use `Uber::Callable`.
34
+ * Removed `Decorator::Coercion`.
35
+ * Removed `Definition#skipable_nil_value?`.
36
+
1
37
  # 1.8.5
2
38
 
3
39
  * Binding now uses `#method_missing` instead of SimpleDelegator for a significant performance boost of many 100%s. Thanks to @0x4a616d6573 for figuring this.
data/Gemfile CHANGED
@@ -2,8 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- #gem "virtus", :path => "../virtus"
6
-
7
5
  platform :rbx do
8
- gem 'psych'
6
+ gem "psych"
7
+ gem "rubysl-irb"
8
+ gem "json_pure"
9
9
  end
data/README.md CHANGED
@@ -4,7 +4,7 @@ Representable maps Ruby objects to documents and back.
4
4
 
5
5
  In other words: Take an object and decorate it with a representer module. This will allow you to render a JSON, XML or YAML document from that object. But that's only half of it! You can also use representers to parse a document and create or populate an object.
6
6
 
7
- Representable is helpful for all kind of rendering and parsing workflows. However, it is mostly useful in API code. Are you planning to write a real REST API with representable? Then check out the [Roar](http://github.com/apotonick/roar) gem first, save work and time and make the world a better place instead.
7
+ Representable is helpful for all kind of mappings, rendering and parsing workflows. However, it is mostly useful in API code. Are you planning to write a real REST API with representable? Then check out the [Roar](http://github.com/apotonick/roar) gem first, save work and time and make the world a better place instead.
8
8
 
9
9
 
10
10
  ## Installation
@@ -267,7 +267,7 @@ module AlbumRepresenter
267
267
 
268
268
  This works both for representer modules and decorators.
269
269
 
270
- An inline representer is just a Ruby module. You can include other representer modules. This is handy when having a base representer that needs to be extended in the inline block.
270
+ An inline representer is just a Ruby module (or a `Decorator` class). You can include other representer modules. This is handy when having a base representer that needs to be extended in the inline block.
271
271
 
272
272
  ```ruby
273
273
  module AlbumRepresenter
@@ -280,6 +280,66 @@ module AlbumRepresenter
280
280
  end
281
281
  ```
282
282
 
283
+ If you need to include modules in all inline representers automatically, register it as a feature.
284
+
285
+ ```ruby
286
+ module AlbumRepresenter
287
+ include Representable::JSON
288
+ feature Link # imports ::link
289
+
290
+ link "/album/1"
291
+
292
+ property :hit do
293
+ link "/hit/1" # link method imported automatically.
294
+ end
295
+ ```
296
+
297
+
298
+ ## Representing Singular Models And Collections
299
+
300
+ You can explicitly define representers for collections of models using a ["Lonely Collection"](#lonely-collections). Or you can let representable do that for you.
301
+
302
+ Rendering a collection of objects comes for free, using `::for_collection`.
303
+
304
+ ```ruby
305
+ Song.all.extend(SongRepresenter.for_collection).to_hash
306
+ #=> [{title: "Sevens"}, {title: "Eric"}]
307
+ ```
308
+
309
+ For parsing, you need to provide the class constant to which the items should be deserialized to.
310
+
311
+ ```ruby
312
+ module SongRepresenter
313
+ include Representable::Hash
314
+ property :title
315
+
316
+ collection_representer class: Song
317
+ end
318
+ ```
319
+
320
+ You can now parse collections to `Song` instances.
321
+
322
+ ```ruby
323
+ [].extend(SongRepresenter.for_collection).from_hash([{title: "Sevens"}, {title: "Eric"}])
324
+ ```
325
+
326
+ As always, this works for decorators _and_ modules.
327
+
328
+ In case you don't want to know whether or not you're working with a collection or singular model, use `::represent`.
329
+
330
+ ```ruby
331
+ # singular
332
+ SongRepresenter.represent(Song.find(1)).to_hash #=> {title: "Sevens"}
333
+
334
+ # collection
335
+ SongRepresenter.represent(Song.all).to_hash #=> [{title: "Sevens"}, {title: "Eric"}]
336
+ ```
337
+
338
+ As you can see, `::represent` figures out the correct representer for you (works also for parsing!).
339
+
340
+ Note: the implicit collection representer internally is implemented using a lonely collection. Everything you pass to `::collection_representer` is simply provided to the `::items` call in the lonely collection. That allows you to use `:parse_strategy` and all the other goodies, too.
341
+
342
+
283
343
  ## Document Nesting
284
344
 
285
345
  Not always does the structure of the desired document map to your objects. The `::nested` method allows you to structure properties in a separate section while still mapping the properties to the outer object.
data/Rakefile CHANGED
@@ -6,6 +6,6 @@ task :default => :test
6
6
 
7
7
  Rake::TestTask.new(:test) do |test|
8
8
  test.libs << 'test'
9
- test.test_files = FileList['test/*_test.rb']
9
+ test.test_files = FileList['test/**/*_test.rb']
10
10
  test.verbose = true
11
11
  end
@@ -1,16 +1,27 @@
1
+ require 'representable/inheritable'
2
+ require 'representable/config'
1
3
  require 'representable/definition'
2
4
  require 'representable/mapper'
3
- require 'representable/config'
5
+ require 'representable/for_collection'
6
+ require 'representable/represent'
7
+ require 'representable/declarative'
8
+
9
+
10
+ require 'uber/callable'
11
+ require 'representable/pipeline'
4
12
 
5
13
  module Representable
6
14
  attr_writer :representable_attrs
7
15
 
8
16
  def self.included(base)
9
17
  base.class_eval do
18
+ extend Declarative
10
19
  extend ClassInclusions, ModuleExtensions
11
20
  extend ClassMethods
12
- extend ClassMethods::Declarations
13
- extend DSLAdditions
21
+ extend Feature
22
+ extend ForCollection
23
+ extend Represent
24
+ # register_feature Representable # Representable gets included automatically when creating inline representer.
14
25
  end
15
26
  end
16
27
 
@@ -40,7 +51,7 @@ private
40
51
  end
41
52
 
42
53
  def representable_attrs
43
- @representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not?
54
+ @representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not? what about "freezing"?
44
55
  end
45
56
 
46
57
  def representable_mapper(format, options)
@@ -60,17 +71,18 @@ private
60
71
  module ClassInclusions
61
72
  def included(base)
62
73
  super
63
- base.representable_attrs.inherit(representable_attrs)
74
+ base.inherit_module!(self)
64
75
  end
65
76
 
66
77
  def inherited(base) # DISCUSS: this could be in Decorator? but then we couldn't do B < A(include X) for non-decorators, right?
67
78
  super
68
- base.representable_attrs.inherit(representable_attrs)
79
+ base.representable_attrs.inherit!(representable_attrs) # this should be inherit_class!
69
80
  end
70
81
  end
71
82
 
72
83
  module ModuleExtensions
73
- # Copies the representable_attrs to the extended object.
84
+ # Copies the representable_attrs reference to the extended object.
85
+ # Note that changing attrs in the instance will affect the class configuration.
74
86
  def extended(object)
75
87
  super
76
88
  object.representable_attrs=(representable_attrs) # yes, we want a hard overwrite here and no inheritance.
@@ -79,119 +91,30 @@ private
79
91
 
80
92
 
81
93
  module ClassMethods
82
- # Create and yield object and options. Called in .from_json and friends.
83
- def create_represented(document, *args)
84
- new.tap do |represented|
85
- yield represented, *args if block_given?
86
- end
94
+ # Gets overridden by Decorator as inheriting representers via include in Decorator means a bit more work (manifesting).
95
+ def inherit_module!(parent)
96
+ representable_attrs.inherit!(parent.representable_attrs) # Module just inherits.
87
97
  end
88
98
 
89
99
  def prepare(represented)
90
- represented.extend(self) # was: PrepareStrategy::Extend.
100
+ represented.extend(self)
91
101
  end
92
-
93
-
94
- module Declarations
95
- def representable_attrs
96
- @representable_attrs ||= build_config
97
- end
98
-
99
- def representation_wrap=(name)
100
- representable_attrs.wrap = name
101
- end
102
-
103
- def property(name, options={}, &block)
104
- representable_attrs << definition_class.new(name, options)
105
- end
106
-
107
- def collection(name, options={}, &block)
108
- options[:collection] = true # FIXME: don't override original.
109
- property(name, options, &block)
110
- end
111
-
112
- def hash(name=nil, options={}, &block)
113
- return super() unless name # allow Object.hash.
114
-
115
- options[:hash] = true
116
- property(name, options, &block)
117
- end
118
-
119
- private
120
- def definition_class
121
- Definition
122
- end
123
-
124
- def build_config
125
- Config.new
126
- end
127
- end # Declarations
128
102
  end
129
103
 
130
- # Internal module for DSL sugar that should not go into the core library.
131
- module DSLAdditions
132
- # Allows you to nest a block of properties in a separate section while still mapping them to the outer object.
133
- def nested(name, options={}, &block)
134
- options = options.merge(
135
- :use_decorator => true,
136
- :getter => lambda { |*| self },
137
- :setter => lambda { |*| },
138
- :instance => lambda { |*| self }
139
- ) # DISCUSS: should this be a macro just as :parse_strategy?
140
-
141
- property(name, options, &block)
142
- end
143
-
144
- def property(name, options={}, &block)
145
- modules = []
146
-
147
- if options[:inherit] # TODO: move this to Definition.
148
- parent = representable_attrs[name]
149
- modules << parent[:extend].evaluate(nil) if parent[:extend]# we can savely assume this is _not_ a lambda. # DISCUSS: leave that in #representer_module?
150
- end # FIXME: can we handle this in super/Definition.new ?
151
104
 
152
- if block_given?
153
- handle_deprecated_inline_extend!(modules, options)
154
-
155
- options[:extend] = inline_representer_for(modules, name, options, &block)
156
- end
157
-
158
- return parent.merge!(options) if options.delete(:inherit)
159
-
160
- super
161
- end
162
-
163
- def inline_representer(base_module, name, options, &block) # DISCUSS: separate module?
164
- Module.new do
165
- include *base_module # Representable::JSON or similar.
166
- instance_exec &block
105
+ module Feature
106
+ def feature(*mods)
107
+ mods.each do |mod|
108
+ include mod
109
+ register_feature(mod)
167
110
  end
168
111
  end
169
112
 
170
113
  private
171
- def inline_representer_for(modules, name, options, &block)
172
- representer = options[:use_decorator] ? Decorator : self
173
- modules = [representer_engine] + modules
174
-
175
- representer.inline_representer(modules.compact.reverse, name, options, &block)
114
+ def register_feature(mod)
115
+ representable_attrs[:features][mod] = true
176
116
  end
177
-
178
- def handle_deprecated_inline_extend!(modules, options) # TODO: remove in 2.0.
179
- return unless include_module = options.delete(:extend) and not options[:inherit]
180
-
181
- warn "[Representable] Using :extend with an inline representer is deprecated. Include the module in the inline block."
182
- modules << include_module
183
- end
184
- end # DSLAdditions
185
- end
186
-
187
-
188
- module Representable
189
- autoload :Hash, 'representable/hash'
190
-
191
- module Hash
192
- autoload :AllowSymbols, 'representable/hash/allow_symbols'
193
- autoload :Collection, 'representable/hash/collection'
194
117
  end
195
-
196
- autoload :Decorator, 'representable/decorator'
197
118
  end
119
+
120
+ require 'representable/autoload'
@@ -1,5 +1,11 @@
1
+ * Cleanup the manifest part in Decorator.
2
+
1
3
  * all property objects should be extended/wrapped so we don't need the switch.
2
4
 
3
5
  # Deprecations
4
6
 
5
- * deprecate instance: { nil } which is superseded by parse_strategy: :sync
7
+ * deprecate instance: { nil } which is superseded by parse_strategy: :sync
8
+
9
+
10
+
11
+ from_hash, property :band, class: vergessen
@@ -0,0 +1,10 @@
1
+ module Representable
2
+ autoload :Hash, 'representable/hash'
3
+
4
+ module Hash
5
+ autoload :AllowSymbols, 'representable/hash/allow_symbols'
6
+ autoload :Collection, 'representable/hash/collection'
7
+ end
8
+
9
+ autoload :Decorator, 'representable/decorator'
10
+ end
@@ -14,7 +14,7 @@ module Representable
14
14
  end
15
15
 
16
16
  def initialize(definition, represented, decorator, user_options={}) # TODO: remove default arg for user options.
17
- @definition = definition
17
+ @definition = definition
18
18
  @represented = represented
19
19
  @decorator = decorator
20
20
  @user_options = user_options
@@ -31,7 +31,8 @@ module Representable
31
31
  # Retrieve value and write fragment to the doc.
32
32
  def compile_fragment(doc)
33
33
  evaluate_option(:writer, doc) do
34
- write_fragment(doc, get)
34
+ value = render_filter(get, doc)
35
+ write_fragment(doc, value)
35
36
  end
36
37
  end
37
38
 
@@ -39,6 +40,7 @@ module Representable
39
40
  def uncompile_fragment(doc)
40
41
  evaluate_option(:reader, doc) do
41
42
  read_fragment(doc) do |value|
43
+ value = parse_filter(value, doc)
42
44
  set(value)
43
45
  end
44
46
  end
@@ -70,6 +72,15 @@ module Representable
70
72
  read(doc)
71
73
  end
72
74
 
75
+ def render_filter(value, doc)
76
+ evaluate_option(:render_filter, value, doc) { value }
77
+ end
78
+
79
+ def parse_filter(value, doc)
80
+ evaluate_option(:parse_filter, value, doc) { value }
81
+ end
82
+
83
+
73
84
  def get
74
85
  evaluate_option(:getter) do
75
86
  exec_context.send(getter)
@@ -142,7 +153,7 @@ module Representable
142
153
  private
143
154
  # DISCUSS: deprecate :class in favour of :instance and simplicity?
144
155
  def class_for(fragment, *args)
145
- item_class = class_from(fragment, *args) or return handle_deprecated_class(fragment)
156
+ item_class = class_from(fragment, *args) or raise DeserializeError.new(":class did not return class constant.")
146
157
  item_class.new
147
158
  end
148
159
 
@@ -151,20 +162,13 @@ module Representable
151
162
  end
152
163
 
153
164
  def instance_for(fragment, *args)
154
- instance = evaluate_option(:instance, fragment, *args)
155
-
156
- if instance === true # TODO: remove in 2.0.
157
- warn "[Representable] `instance: lambda { true }` is deprecated. Apparently, you know what you're doing, so use `parse_strategy: :sync` instead."
158
- return get
159
- end
160
-
161
- instance
162
- end
163
-
164
- def handle_deprecated_class(fragment) # TODO: remove in 2.0.
165
- warn "[Representable] `class: lambda { nil }` is deprecated. To return the fragment from parsing, use `instance: lambda { |fragment, *args| fragment }` instead."
166
- fragment
165
+ # cool: if no :instance set, { return } will jump out of this method.
166
+ evaluate_option(:instance, fragment, *args) { return } or raise DeserializeError.new(":instance did not return object.")
167
167
  end
168
168
  end
169
169
  end
170
+
171
+
172
+ class DeserializeError < RuntimeError
173
+ end
170
174
  end