representable 2.3.0 → 2.4.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/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={})
|