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