representable 2.3.0 → 2.4.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +47 -0
- data/Gemfile +5 -0
- data/README.md +33 -0
- data/lib/representable.rb +60 -73
- data/lib/representable/binding.rb +37 -194
- data/lib/representable/cached.rb +10 -46
- data/lib/representable/coercion.rb +8 -8
- data/lib/representable/config.rb +15 -75
- data/lib/representable/debug.rb +41 -59
- data/lib/representable/declarative.rb +34 -53
- data/lib/representable/decorator.rb +11 -40
- data/lib/representable/definition.rb +14 -15
- data/lib/representable/deprecations.rb +90 -0
- data/lib/representable/deserializer.rb +87 -82
- data/lib/representable/for_collection.rb +5 -3
- data/lib/representable/hash.rb +5 -3
- data/lib/representable/hash/binding.rb +6 -15
- data/lib/representable/hash/collection.rb +10 -6
- data/lib/representable/hash_methods.rb +5 -5
- data/lib/representable/insert.rb +31 -0
- data/lib/representable/json.rb +7 -3
- data/lib/representable/json/hash.rb +1 -1
- data/lib/representable/object/binding.rb +5 -5
- data/lib/representable/parse_strategies.rb +37 -3
- data/lib/representable/pipeline.rb +37 -5
- data/lib/representable/pipeline_factories.rb +88 -0
- data/lib/representable/serializer.rb +38 -44
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +4 -0
- data/lib/representable/xml/binding.rb +25 -31
- data/lib/representable/xml/collection.rb +5 -3
- data/lib/representable/xml/hash.rb +7 -2
- data/lib/representable/yaml.rb +6 -3
- data/lib/representable/yaml/binding.rb +4 -4
- data/representable.gemspec +3 -3
- data/test/---deserialize-pipeline_test.rb +37 -0
- data/test/binding_test.rb +7 -7
- data/test/cached_test.rb +31 -19
- data/test/coercion_test.rb +2 -2
- data/test/config/inherit_test.rb +13 -12
- data/test/config_test.rb +12 -67
- data/test/decorator_test.rb +4 -5
- data/test/default_test.rb +34 -0
- data/test/defaults_options_test.rb +93 -0
- data/test/definition_test.rb +19 -39
- data/test/exec_context_test.rb +1 -1
- data/test/filter_test.rb +18 -20
- data/test/getter_setter_test.rb +1 -8
- data/test/hash_bindings_test.rb +13 -13
- data/test/heritage_test.rb +62 -0
- data/test/if_test.rb +1 -0
- data/test/inherit_test.rb +5 -3
- data/test/instance_test.rb +3 -4
- data/test/json_test.rb +3 -59
- data/test/lonely_test.rb +47 -3
- data/test/nested_test.rb +8 -2
- data/test/pipeline_test.rb +259 -0
- data/test/populator_test.rb +76 -0
- data/test/realistic_benchmark.rb +39 -7
- data/test/render_nil_test.rb +21 -0
- data/test/represent_test.rb +2 -2
- data/test/representable_test.rb +33 -103
- data/test/schema_test.rb +5 -15
- data/test/serialize_deserialize_test.rb +2 -2
- data/test/skip_test.rb +1 -1
- data/test/test_helper.rb +6 -0
- data/test/uncategorized_test.rb +67 -0
- data/test/xml_bindings_test.rb +6 -6
- data/test/xml_test.rb +6 -6
- metadata +33 -13
- data/lib/representable/apply.rb +0 -13
- data/lib/representable/inheritable.rb +0 -71
- data/lib/representable/mapper.rb +0 -83
- data/lib/representable/populator.rb +0 -56
- data/test/inheritable_test.rb +0 -97
@@ -1,11 +1,8 @@
|
|
1
1
|
require "representable"
|
2
|
+
require "uber/inheritable_attr"
|
2
3
|
|
3
4
|
module Representable
|
4
5
|
class Decorator
|
5
|
-
class << self
|
6
|
-
include Cloneable
|
7
|
-
end
|
8
|
-
|
9
6
|
attr_reader :represented
|
10
7
|
alias_method :decorated, :represented
|
11
8
|
|
@@ -14,57 +11,31 @@ module Representable
|
|
14
11
|
new(represented)
|
15
12
|
end
|
16
13
|
|
17
|
-
def self.
|
14
|
+
def self.default_nested_class #FIXME. SHOULD we move that into NestedBuilder?
|
18
15
|
Representable::Decorator
|
19
16
|
end
|
20
17
|
|
18
|
+
# extend ::Declarative::Heritage::Inherited # DISCUSS: currently, that is handled via Representable::inherited.
|
19
|
+
|
21
20
|
# This is called from inheritable_attr when inheriting a decorator class to a subclass.
|
22
21
|
# Explicitly subclassing the Decorator makes sure representable_attrs is a clean version.
|
23
22
|
def self.clone
|
24
|
-
Class.new(self)
|
23
|
+
Class.new(self)
|
25
24
|
end
|
26
25
|
|
27
26
|
include Representable # include after class methods so Decorator::prepare can't be overwritten by Representable::prepare.
|
27
|
+
include Cached
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
inherited_attrs = parent.representable_attrs[:definitions].keys
|
33
|
-
|
34
|
-
super # in Representable, calls representable_attrs.inherit!(parent.representable_attrs).
|
35
|
-
# now, inline representers are still modules, which is wrong.
|
36
|
-
manifest!(inherited_attrs)
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
# one level deep manifesting modules into Decorators.
|
41
|
-
def manifest!(names)
|
42
|
-
names.each do |name| # only definitions.
|
43
|
-
definition = representable_attrs.get(name)
|
44
|
-
next unless definition[:_inline] and mod = definition.representer_module # only inline representers.
|
45
|
-
|
46
|
-
# here, we can include Decorator features.
|
47
|
-
inline_representer = build_inline(nil, representable_attrs.features, definition.name, {}) {
|
48
|
-
include mod
|
49
|
-
} # the includer controls what "wraps" the module.
|
50
|
-
|
51
|
-
definition.merge!(:extend => inline_representer)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
extend InheritModule
|
56
|
-
|
29
|
+
extend Uber::InheritableAttr
|
30
|
+
inheritable_attr :map
|
31
|
+
self.map = Binding::Map.new
|
57
32
|
|
58
33
|
def initialize(represented)
|
59
34
|
@represented = represented
|
60
35
|
end
|
61
36
|
|
62
|
-
|
63
|
-
|
64
|
-
Class.new(base || default_inline_class) do
|
65
|
-
feature *features
|
66
|
-
class_eval &block
|
67
|
-
end
|
37
|
+
def self.nested_builder
|
38
|
+
::Declarative::Schema::DSL::NestedBuilder
|
68
39
|
end
|
69
40
|
end
|
70
41
|
end
|
@@ -1,27 +1,27 @@
|
|
1
|
-
require
|
1
|
+
require "uber/options"
|
2
2
|
require "representable/parse_strategies"
|
3
3
|
|
4
4
|
module Representable
|
5
5
|
# Created at class compile time. Keeps configuration options for one property.
|
6
|
-
class Definition
|
7
|
-
include Representable::Cloneable
|
8
|
-
|
9
|
-
attr_reader :name
|
10
|
-
alias_method :getter, :name
|
6
|
+
class Definition < ::Declarative::Definitions::Definition
|
11
7
|
|
12
8
|
def initialize(sym, options={}, &block)
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
options[:extend] = options[:nested] if options[:nested]
|
10
|
+
|
11
|
+
super
|
16
12
|
|
17
13
|
# defaults:
|
18
14
|
options[:parse_filter] = Pipeline[*options[:parse_filter]]
|
19
15
|
options[:render_filter] = Pipeline[*options[:render_filter]]
|
20
|
-
options[:as] ||= @name
|
21
16
|
|
22
17
|
setup!(options, &block)
|
23
18
|
end
|
24
19
|
|
20
|
+
def name
|
21
|
+
self[:name]
|
22
|
+
end
|
23
|
+
alias_method :getter, :name
|
24
|
+
|
25
25
|
def merge!(options, &block)
|
26
26
|
options = options.clone
|
27
27
|
|
@@ -39,13 +39,11 @@ module Representable
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def [](name)
|
42
|
+
# return nil if name==:extend && self[:nested].nil?
|
43
|
+
# return Uber::Options::Value.new(self[:nested]) if name==:extend
|
42
44
|
@runtime_options[name]
|
43
45
|
end
|
44
46
|
|
45
|
-
def clone
|
46
|
-
self.class.new(name, @options.clone)
|
47
|
-
end
|
48
|
-
|
49
47
|
def setter
|
50
48
|
:"#{name}="
|
51
49
|
end
|
@@ -91,6 +89,7 @@ module Representable
|
|
91
89
|
|
92
90
|
# DISCUSS: we could call more macros here (e.g. for :nested).
|
93
91
|
Representable::ParseStrategy.apply!(options)
|
92
|
+
Representable::Populator.apply!(options)
|
94
93
|
|
95
94
|
yield options if block_given?
|
96
95
|
@options.merge!(options)
|
@@ -110,7 +109,7 @@ module Representable
|
|
110
109
|
end
|
111
110
|
|
112
111
|
def dynamic_options
|
113
|
-
[:as, :getter, :setter, :class, :instance, :reader, :writer, :extend, :prepare, :if, :deserialize, :serialize, :
|
112
|
+
[:as, :getter, :setter, :class, :instance, :reader, :writer, :extend, :prepare, :if, :deserialize, :serialize, :skip_parse, :skip_render]
|
114
113
|
end
|
115
114
|
|
116
115
|
def handle_extend!(options)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# WARNING: this will be removed in 3.0.
|
2
|
+
module Representable::Binding::Deprecation
|
3
|
+
Options = Struct.new(:binding, :user_options, :represented, :decorator)
|
4
|
+
|
5
|
+
module EvaluateOption
|
6
|
+
def evaluate_option(name, input=nil, options={})
|
7
|
+
return evaluate_option_with_deprecation(name, input, options, :user_options) if name==:as
|
8
|
+
return evaluate_option_with_deprecation(name, input, options, :user_options) if name==:if
|
9
|
+
return evaluate_option_with_deprecation(name, input, options, :user_options) if name==:getter
|
10
|
+
return evaluate_option_with_deprecation(name, input, options, :doc, :user_options) if name==:writer
|
11
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :user_options) if name==:skip_render
|
12
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :user_options) if name==:skip_parse
|
13
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :user_options) if name==:serialize
|
14
|
+
return evaluate_option_with_deprecation(name, input, options, :doc, :user_options) if name==:reader
|
15
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :index, :user_options) if name==:instance
|
16
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :index, :user_options) if name==:class
|
17
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :fragment, :user_options) if name==:deserialize
|
18
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :user_options) if name==:prepare
|
19
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :user_options) if name==:extend
|
20
|
+
return evaluate_option_with_deprecation(name, input, options, :input, :user_options) if name==:setter
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def evaluate_option_with_deprecation(name, input, options, *positional_arguments)
|
25
|
+
unless proc = self[name]
|
26
|
+
return # FIXME: why do we need this?
|
27
|
+
end
|
28
|
+
|
29
|
+
options[:input] = input
|
30
|
+
|
31
|
+
|
32
|
+
__options = if self[:pass_options]
|
33
|
+
warn %{[Representable] The :pass_options option is deprecated. Please access environment objects via options[:binding].
|
34
|
+
Learn more here: http://trailblazerb.org/gems/representable/upgrading-guide.html#pass-options}
|
35
|
+
Options.new(self, options[:user_options], options[:represented], options[:decorator])
|
36
|
+
else
|
37
|
+
# user_options
|
38
|
+
options[:user_options]
|
39
|
+
end
|
40
|
+
# options[:user_options] = __options # TODO: always make this user_options in Representable 3.0.
|
41
|
+
|
42
|
+
if proc.send(:proc?) or proc.send(:method?)
|
43
|
+
arity = proc.instance_variable_get(:@value).arity if proc.send(:proc?)
|
44
|
+
arity = send(:exec_context, options).method(proc.instance_variable_get(:@value)).arity if proc.send(:method?)
|
45
|
+
if arity != 1 or name==:getter or name==:if or name==:as
|
46
|
+
warn %{[Representable] Positional arguments for `:#{name}` are deprecated. Please use options or keyword arguments.
|
47
|
+
#{name}: ->(options) { options[:#{positional_arguments.join(" | :")}] } or
|
48
|
+
#{name}: ->(#{positional_arguments.join(":, ")}:) { }
|
49
|
+
Learn more here: http://trailblazerb.org/gems/representable/upgrading-guide.html#positional-arguments
|
50
|
+
}
|
51
|
+
deprecated_args = []
|
52
|
+
positional_arguments.each do |arg|
|
53
|
+
next if arg == :index && options[:index].nil?
|
54
|
+
deprecated_args << __options and next if arg == :user_options# either hash or Options object.
|
55
|
+
deprecated_args << options[arg]
|
56
|
+
end
|
57
|
+
|
58
|
+
return proc.(send(:exec_context, options), *deprecated_args)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
proc.(send(:exec_context, options), options)
|
63
|
+
end
|
64
|
+
private :evaluate_option_with_deprecation
|
65
|
+
|
66
|
+
|
67
|
+
def represented
|
68
|
+
warn "[Representable] Binding#represented is deprecated. Use options[:represented] instead."
|
69
|
+
@represented
|
70
|
+
end
|
71
|
+
|
72
|
+
def compile_fragment(options)
|
73
|
+
@represented = options[:represented]
|
74
|
+
@parent_decorator = options[:decorator]
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
# Parse value from doc and update the model property.
|
79
|
+
def uncompile_fragment(options)
|
80
|
+
@represented = options[:represented]
|
81
|
+
@parent_decorator = options[:decorator]
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
def get(options={}) # DISCUSS: evluate if we really need this.
|
86
|
+
warn "[Representable] Binding#get is deprecated."
|
87
|
+
self[:getter] ? Representable::Getter.(nil, options.merge(binding: self)) : Representable::Get.(nil, options.merge(binding: self))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -1,115 +1,120 @@
|
|
1
1
|
module Representable
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# It respects :deserialize, :prepare, :class, :instance
|
6
|
-
#
|
7
|
-
# Collection bindings return an array of parsed fragment items (still native format, e.g. Nokogiri node, for nested objects).
|
8
|
-
#
|
9
|
-
# Workflow
|
10
|
-
# call -> instance/class -> prepare -> deserialize -> from_json.
|
11
|
-
class Deserializer
|
12
|
-
def initialize(binding)
|
13
|
-
@binding = binding
|
14
|
-
end
|
2
|
+
# we don't use keyword args, because i didn't want to discriminate 1.9 users, yet.
|
3
|
+
# this will soon get introduces and remove constructs like options[:binding][:default].
|
15
4
|
|
16
|
-
|
17
|
-
|
18
|
-
|
5
|
+
# Deprecation strategy:
|
6
|
+
# binding.evaluate_option_with_deprecation(:reader, options, :doc)
|
7
|
+
# => binding.evaluate_option(:reader, options) # always pass in options.
|
19
8
|
|
20
|
-
|
21
|
-
object = create_object(fragment, *args) # customize with :instance and :class.
|
22
|
-
representable = prepare(object) # customize with :prepare and :extend.
|
9
|
+
AssignFragment = ->(input, options) { options[:fragment] = input }
|
23
10
|
|
24
|
-
|
25
|
-
|
11
|
+
ReadFragment = ->(input, options) { options[:binding].read(input, options[:as]) }
|
12
|
+
Reader = ->(input, options) { options[:binding].evaluate_option(:reader, input, options) }
|
26
13
|
|
27
|
-
|
28
|
-
|
29
|
-
|
14
|
+
StopOnNotFound = ->(input, options) do
|
15
|
+
Binding::FragmentNotFound == input ? Pipeline::Stop : input
|
16
|
+
end
|
30
17
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
18
|
+
StopOnNil = ->(input, options) do # DISCUSS: Not tested/used, yet.
|
19
|
+
input.nil? ? Pipeline::Stop : input
|
20
|
+
end
|
35
21
|
|
36
|
-
|
37
|
-
|
38
|
-
|
22
|
+
OverwriteOnNil = ->(input, options) do
|
23
|
+
input.nil? ? (Set.(input, options); Pipeline::Stop) : input
|
24
|
+
end
|
39
25
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
prepare!(object)
|
44
|
-
end
|
45
|
-
end
|
26
|
+
Default = ->(input, options) do
|
27
|
+
Binding::FragmentNotFound == input ? options[:binding][:default] : input
|
28
|
+
end
|
46
29
|
|
47
|
-
|
48
|
-
|
30
|
+
SkipParse = ->(input, options) do
|
31
|
+
options[:binding].evaluate_option(:skip_parse, input, options) ? Pipeline::Stop : input
|
32
|
+
end
|
33
|
+
|
34
|
+
Instance = ->(input, options) do
|
35
|
+
options[:binding].evaluate_option(:instance, input, options)
|
36
|
+
end
|
49
37
|
|
50
|
-
|
38
|
+
module Function
|
39
|
+
class CreateObject
|
40
|
+
def call(input, options)
|
41
|
+
AssignFragment.(input, options)
|
51
42
|
|
52
|
-
|
43
|
+
instance_for(input, options) || class_for(input, options)
|
53
44
|
end
|
54
45
|
|
55
|
-
|
56
|
-
|
46
|
+
private
|
47
|
+
def class_for(input, options)
|
48
|
+
item_class = class_from(input, options) or raise DeserializeError.new(":class did not return class constant for `#{options[:binding].name}`.")
|
49
|
+
item_class.new
|
57
50
|
end
|
58
|
-
end
|
59
|
-
include Prepare
|
60
51
|
|
61
|
-
|
62
|
-
|
63
|
-
|
52
|
+
def class_from(input, options)
|
53
|
+
options[:binding].evaluate_option(:class, input, options) # FIXME: no additional args passed here, yet.
|
54
|
+
end
|
64
55
|
|
65
|
-
|
66
|
-
|
67
|
-
|
56
|
+
def instance_for(input, options)
|
57
|
+
Instance.(input, options)
|
58
|
+
end
|
68
59
|
end
|
69
60
|
|
70
|
-
|
71
|
-
|
61
|
+
class Prepare
|
62
|
+
def call(input, options)
|
63
|
+
binding = options[:binding]
|
64
|
+
|
65
|
+
binding.evaluate_option(:prepare, input, options)
|
66
|
+
end
|
72
67
|
end
|
73
68
|
|
74
|
-
|
75
|
-
|
76
|
-
|
69
|
+
class Decorate
|
70
|
+
def call(object, options)
|
71
|
+
binding = options[:binding]
|
72
|
+
|
73
|
+
return object unless object # object might be nil.
|
74
|
+
|
75
|
+
mod = binding.evaluate_option(:extend, object, options)
|
76
|
+
|
77
|
+
prepare_for(mod, object, binding)
|
78
|
+
end
|
79
|
+
|
80
|
+
def prepare_for(mod, object, binding)
|
81
|
+
mod.prepare(object)
|
82
|
+
end
|
77
83
|
end
|
84
|
+
end
|
78
85
|
|
86
|
+
CreateObject = Function::CreateObject.new
|
87
|
+
Prepare = Function::Prepare.new
|
88
|
+
Decorate = Function::Decorate.new
|
89
|
+
Deserializer = ->(input, options) { options[:binding].evaluate_option(:deserialize, input, options) }
|
79
90
|
|
91
|
+
Deserialize = ->(input, options) do
|
92
|
+
binding, fragment, user_options = options[:binding], options[:fragment], options[:user_options]
|
80
93
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
collection = [] # this can be replaced, e.g. AR::Collection or whatever.
|
94
|
+
user_options = user_options.merge(wrap: binding[:wrap]) unless binding[:wrap].nil? # DISCUSS: can we leave that here?
|
95
|
+
input.send(binding.deserialize_method, fragment, user_options)
|
96
|
+
end
|
85
97
|
|
86
|
-
|
87
|
-
|
88
|
-
|
98
|
+
ParseFilter = ->(input, options) do
|
99
|
+
options[:binding][:parse_filter].(input, options)
|
100
|
+
end
|
89
101
|
|
90
|
-
|
91
|
-
|
102
|
+
Setter = ->(input, options) { options[:binding].evaluate_option(:setter, input, options) }
|
103
|
+
Set = ->(input, options) { options[:binding].send(:exec_context, options).send(options[:binding].setter, input) }
|
92
104
|
|
93
|
-
collection # with parse_strategy: :sync, this is ignored.
|
94
|
-
end
|
95
105
|
|
96
|
-
|
97
|
-
def deserialize!(*args)
|
98
|
-
item_deserializer.call(*args)
|
99
|
-
end
|
106
|
+
Stop = ->(*) { Pipeline::Stop }
|
100
107
|
|
101
|
-
|
102
|
-
@item_deserializer = Deserializer.new(@binding)
|
103
|
-
end
|
104
|
-
end
|
108
|
+
If = ->(input, options) { options[:binding].evaluate_option(:if, nil, options) ? input : Pipeline::Stop }
|
105
109
|
|
110
|
+
StopOnExcluded = ->(input, options) do
|
111
|
+
return input unless private = options[:_private]
|
112
|
+
return input unless props = (private[:exclude] || private[:include])
|
106
113
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
end
|
114
|
+
res = props.include?(options[:binding].name.to_sym) # false with include: Stop. false with exclude: go!
|
115
|
+
|
116
|
+
return input if private[:include]&&res
|
117
|
+
return input if private[:exclude]&&!res
|
118
|
+
Pipeline::Stop
|
114
119
|
end
|
115
120
|
end
|
@@ -13,9 +13,11 @@ module Representable
|
|
13
13
|
|
14
14
|
# what happens here is basically
|
15
15
|
# Module.new { include Representable::JSON::Collection; ... }
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
nested_builder.(
|
17
|
+
_base: default_nested_class,
|
18
|
+
_features: [singular.collection_representer_class],
|
19
|
+
_block: ->(*) { items options.merge(:extend => singular) }
|
20
|
+
)
|
19
21
|
end
|
20
22
|
|
21
23
|
def collection_representer(options={})
|