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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGES.md +36 -0
- data/Gemfile +3 -3
- data/README.md +62 -2
- data/Rakefile +1 -1
- data/lib/representable.rb +32 -109
- data/lib/representable/TODO.getting_serious +7 -1
- data/lib/representable/autoload.rb +10 -0
- data/lib/representable/binding.rb +20 -16
- data/lib/representable/bindings/xml_bindings.rb +0 -1
- data/lib/representable/coercion.rb +23 -31
- data/lib/representable/config.rb +45 -46
- data/lib/representable/declarative.rb +78 -0
- data/lib/representable/decorator.rb +39 -10
- data/lib/representable/definition.rb +40 -33
- data/lib/representable/deserializer.rb +2 -0
- data/lib/representable/for_collection.rb +25 -0
- data/lib/representable/hash.rb +4 -8
- data/lib/representable/hash/collection.rb +2 -9
- data/lib/representable/hash_methods.rb +0 -7
- data/lib/representable/inheritable.rb +50 -0
- data/lib/representable/json.rb +3 -9
- data/lib/representable/json/collection.rb +1 -3
- data/lib/representable/json/hash.rb +4 -9
- data/lib/representable/mapper.rb +8 -5
- data/lib/representable/parse_strategies.rb +1 -0
- data/lib/representable/pipeline.rb +14 -0
- data/lib/representable/represent.rb +6 -0
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +3 -18
- data/lib/representable/xml/collection.rb +2 -4
- data/lib/representable/xml/hash.rb +2 -10
- data/lib/representable/yaml.rb +1 -20
- data/representable.gemspec +2 -2
- data/test/class_test.rb +5 -10
- data/test/coercion_test.rb +31 -92
- data/test/config/inherit_test.rb +128 -0
- data/test/config_test.rb +114 -80
- data/test/definition_test.rb +107 -64
- data/test/features_test.rb +41 -0
- data/test/filter_test.rb +59 -0
- data/test/for_collection_test.rb +74 -0
- data/test/inherit_test.rb +44 -3
- data/test/inheritable_test.rb +97 -0
- data/test/inline_test.rb +0 -18
- data/test/instance_test.rb +0 -19
- data/test/json_test.rb +9 -44
- data/test/lonely_test.rb +1 -0
- data/test/parse_strategy_test.rb +30 -0
- data/test/represent_test.rb +88 -0
- data/test/representable_test.rb +3 -50
- data/test/schema_test.rb +123 -0
- data/test/test_helper.rb +1 -1
- data/test/xml_test.rb +34 -38
- metadata +25 -15
- data/lib/representable/decorator/coercion.rb +0 -4
- data/lib/representable/readable_writeable.rb +0 -29
- data/test/inheritance_test.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c06c246f8a877ca22b35b7e5f409be0f5b1d6922
|
4
|
+
data.tar.gz: 57183b1d4597321d6ed84278407032b04ac3eb8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed0821431bd7bee01cf48dc470211a78cdaa80a6de27df4961bf9028f6824a5914bf16bf86ffd59ca4698f60c56362226421aa2ea011933de82aa3072c28d081
|
7
|
+
data.tar.gz: df87389a3a4b6e7841915dbf9b5ae999942f2640e70c27d199cc1ded97f1d2d1f9d6df3c400a3c76110176b5cdc55acdb5c73bb97da6b0a6986321e41a8dfb0a
|
data/.travis.yml
CHANGED
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
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
data/lib/representable.rb
CHANGED
@@ -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/
|
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
|
13
|
-
extend
|
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.
|
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
|
-
#
|
83
|
-
def
|
84
|
-
|
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)
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
172
|
-
|
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
|
@@ -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
|
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
|
-
|
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
|
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
|
-
|
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
|