representable 1.8.5 → 2.0.0.rc1

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.
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