representable 3.0.4 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +17 -0
- data/CHANGES.md +10 -0
- data/Gemfile +4 -7
- data/LICENSE +1 -1
- data/README.md +4 -3
- data/Rakefile +1 -6
- data/lib/representable.rb +18 -25
- data/lib/representable/binding.rb +32 -12
- data/lib/representable/cached.rb +1 -1
- data/lib/representable/coercion.rb +8 -6
- data/lib/representable/config.rb +8 -3
- data/lib/representable/debug.rb +23 -15
- data/lib/representable/declarative.rb +8 -3
- data/lib/representable/decorator.rb +1 -1
- data/lib/representable/definition.rb +7 -2
- data/lib/representable/deserializer.rb +4 -3
- data/lib/representable/for_collection.rb +1 -1
- data/lib/representable/hash.rb +6 -2
- data/lib/representable/hash/allow_symbols.rb +9 -11
- data/lib/representable/hash/binding.rb +1 -0
- data/lib/representable/hash/collection.rb +4 -2
- data/lib/representable/hash_methods.rb +3 -2
- data/lib/representable/insert.rb +1 -1
- data/lib/representable/json.rb +5 -7
- data/lib/representable/json/collection.rb +3 -0
- data/lib/representable/json/hash.rb +1 -0
- data/lib/representable/object.rb +1 -1
- data/lib/representable/object/binding.rb +5 -1
- data/lib/representable/option.rb +19 -0
- data/lib/representable/pipeline.rb +3 -2
- data/lib/representable/pipeline_factories.rb +4 -2
- data/lib/representable/populator.rb +1 -1
- data/lib/representable/represent.rb +1 -0
- data/lib/representable/serializer.rb +2 -1
- data/lib/representable/version.rb +1 -1
- data/lib/representable/virtus_coercion.rb +38 -0
- data/lib/representable/xml.rb +7 -10
- data/lib/representable/xml/binding.rb +2 -3
- data/lib/representable/yaml.rb +3 -3
- data/lib/representable/yaml/binding.rb +1 -0
- data/representable.gemspec +3 -3
- data/test/as_test.rb +4 -4
- data/test/binding_test.rb +10 -10
- data/test/cached_test.rb +19 -19
- data/test/class_test.rb +7 -7
- data/test/coercion_test.rb +33 -22
- data/test/config/inherit_test.rb +14 -14
- data/test/config_test.rb +18 -18
- data/test/decorator_scope_test.rb +3 -3
- data/test/decorator_test.rb +17 -17
- data/test/default_test.rb +7 -7
- data/test/definition_test.rb +32 -32
- data/test/{example.rb → examples/example.rb} +0 -0
- data/test/exec_context_test.rb +6 -6
- data/test/features_test.rb +3 -3
- data/test/filter_test.rb +6 -6
- data/test/for_collection_test.rb +2 -2
- data/test/generic_test.rb +3 -3
- data/test/getter_setter_test.rb +5 -5
- data/test/hash_test.rb +19 -19
- data/test/heritage_test.rb +4 -4
- data/test/if_test.rb +6 -6
- data/test/include_exclude_test.rb +12 -12
- data/test/inherit_test.rb +15 -15
- data/test/inline_test.rb +11 -11
- data/test/instance_test.rb +29 -29
- data/test/is_representable_test.rb +10 -10
- data/test/json_test.rb +7 -7
- data/test/lonely_test.rb +16 -16
- data/test/nested_test.rb +7 -7
- data/test/object_test.rb +7 -7
- data/test/option_test.rb +36 -0
- data/test/parse_pipeline_test.rb +3 -3
- data/test/pipeline_test.rb +43 -43
- data/test/populator_test.rb +15 -15
- data/test/prepare_test.rb +2 -2
- data/test/private_options_test.rb +2 -2
- data/test/reader_writer_test.rb +2 -2
- data/test/render_nil_test.rb +2 -2
- data/test/represent_test.rb +4 -4
- data/test/representable_test.rb +27 -27
- data/test/schema_test.rb +5 -5
- data/test/serialize_deserialize_test.rb +2 -2
- data/test/skip_test.rb +10 -10
- data/test/stringify_hash_test.rb +3 -3
- data/test/test_helper.rb +4 -2
- data/test/uncategorized_test.rb +10 -10
- data/test/user_options_test.rb +4 -4
- data/test/virtus_coercion_test.rb +52 -0
- data/test/wrap_test.rb +11 -11
- data/test/xml_namespace_test.rb +1 -1
- data/test/xml_test.rb +6 -6
- data/test/yaml_test.rb +20 -20
- metadata +81 -15
- data/.travis.yml +0 -16
- data/lib/representable/autoload.rb +0 -14
- data/test/mongoid_test.rb +0 -31
@@ -1,4 +1,9 @@
|
|
1
|
+
require "declarative/schema"
|
2
|
+
|
1
3
|
module Representable
|
4
|
+
autoload :Decorator, "representation/decorator"
|
5
|
+
autoload :Definition, "representation/definition"
|
6
|
+
|
2
7
|
module Declarative
|
3
8
|
def representation_wrap=(name)
|
4
9
|
heritage.record(:representation_wrap=, name)
|
@@ -21,9 +26,9 @@ module Representable
|
|
21
26
|
# them to the original object.
|
22
27
|
def nested(name, options={}, &block)
|
23
28
|
options = options.merge(
|
24
|
-
getter: ->(
|
29
|
+
getter: ->(_opts) { self },
|
25
30
|
setter: ->(opts) { },
|
26
|
-
instance: ->(
|
31
|
+
instance: ->(_opts) { self },
|
27
32
|
)
|
28
33
|
|
29
34
|
if block
|
@@ -64,4 +69,4 @@ module Representable
|
|
64
69
|
|
65
70
|
alias_method :representable_attrs, :definitions
|
66
71
|
end
|
67
|
-
end
|
72
|
+
end
|
@@ -1,6 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require 'declarative/definitions'
|
2
2
|
|
3
3
|
module Representable
|
4
|
+
autoload :Pipeline, "representable/pipeline"
|
5
|
+
autoload :Populator, "representable/populator"
|
6
|
+
autoload :Option, "representable/option"
|
7
|
+
|
4
8
|
# Created at class compile time. Keeps configuration options for one property.
|
5
9
|
class Definition < ::Declarative::Definitions::Definition
|
6
10
|
|
@@ -53,6 +57,7 @@ module Representable
|
|
53
57
|
|
54
58
|
def representable?
|
55
59
|
return if self[:representable] == false
|
60
|
+
|
56
61
|
self[:representable] or typed?
|
57
62
|
end
|
58
63
|
|
@@ -101,7 +106,7 @@ module Representable
|
|
101
106
|
@runtime_options = {}
|
102
107
|
|
103
108
|
for name, value in options
|
104
|
-
value = ::
|
109
|
+
value = ::Representable::Option(value) if dynamic_options.include?(name)
|
105
110
|
@runtime_options[name] = value
|
106
111
|
end
|
107
112
|
end
|
@@ -11,11 +11,11 @@ module Representable
|
|
11
11
|
ReadFragment = ->(input, options) { options[:binding].read(input, options[:as]) }
|
12
12
|
Reader = ->(input, options) { options[:binding].evaluate_option(:reader, input, options) }
|
13
13
|
|
14
|
-
StopOnNotFound = ->(input,
|
14
|
+
StopOnNotFound = ->(input, _options) do
|
15
15
|
Binding::FragmentNotFound == input ? Pipeline::Stop : input
|
16
16
|
end
|
17
17
|
|
18
|
-
StopOnNil = ->(input,
|
18
|
+
StopOnNil = ->(input, _options) do # DISCUSS: Not tested/used, yet.
|
19
19
|
input.nil? ? Pipeline::Stop : input
|
20
20
|
end
|
21
21
|
|
@@ -103,6 +103,7 @@ module Representable
|
|
103
103
|
|
104
104
|
return input if options[:options][:include]&&res
|
105
105
|
return input if options[:options][:exclude]&&!res
|
106
|
+
|
106
107
|
Pipeline::Stop
|
107
108
|
end
|
108
|
-
end
|
109
|
+
end
|
data/lib/representable/hash.rb
CHANGED
@@ -6,6 +6,9 @@ module Representable
|
|
6
6
|
# If you plan to write your own representer for a new media type, try to use this module (e.g., check how JSON reuses Hash's internal
|
7
7
|
# architecture).
|
8
8
|
module Hash
|
9
|
+
autoload :Collection, 'representable/hash/collection'
|
10
|
+
autoload :AllowSymbols, 'representable/hash/allow_symbols'
|
11
|
+
|
9
12
|
def self.included(base)
|
10
13
|
base.class_eval do
|
11
14
|
include Representable # either in Hero or HeroRepresentation.
|
@@ -34,7 +37,7 @@ module Representable
|
|
34
37
|
hash = create_representation_with({}, options, binding_builder)
|
35
38
|
|
36
39
|
return hash if options[:wrap] == false
|
37
|
-
return hash unless wrap = options[:wrap] || representation_wrap(options)
|
40
|
+
return hash unless (wrap = options[:wrap] || representation_wrap(options))
|
38
41
|
|
39
42
|
{wrap => hash}
|
40
43
|
end
|
@@ -45,7 +48,8 @@ module Representable
|
|
45
48
|
private
|
46
49
|
def filter_wrap(data, options)
|
47
50
|
return data if options[:wrap] == false
|
48
|
-
return data unless wrap = options[:wrap] || representation_wrap(options)
|
51
|
+
return data unless (wrap = options[:wrap] || representation_wrap(options))
|
52
|
+
|
49
53
|
filter_wrap_for(data, wrap)
|
50
54
|
end
|
51
55
|
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Representable
|
2
4
|
module Hash
|
3
5
|
module AllowSymbols
|
4
|
-
|
6
|
+
private
|
7
|
+
|
5
8
|
def filter_wrap_for(data, *args)
|
6
9
|
super(Conversion.stringify_keys(data), *args)
|
7
10
|
end
|
@@ -11,17 +14,12 @@ module Representable
|
|
11
14
|
end
|
12
15
|
end
|
13
16
|
|
14
|
-
|
15
|
-
# DISCUSS: we could think about mixin in IndifferentAccess here (either hashie or ActiveSupport).
|
16
|
-
# or decorating the hash.
|
17
|
+
module Conversion
|
17
18
|
def self.stringify_keys(hash)
|
18
|
-
hash
|
19
|
-
|
20
|
-
|
21
|
-
hash[k.to_s] = hash.delete(k)
|
22
|
-
end
|
23
|
-
hash
|
19
|
+
hash.keys.collect do |key|
|
20
|
+
[ key.to_s, hash[key] ]
|
21
|
+
end.to_h
|
24
22
|
end
|
25
23
|
end
|
26
24
|
end
|
27
|
-
end
|
25
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'representable/hash'
|
2
|
+
|
1
3
|
module Representable::Hash
|
2
4
|
module Collection
|
3
5
|
include Representable::Hash
|
@@ -20,7 +22,7 @@ module Representable::Hash
|
|
20
22
|
# TODO: revise lonely collection and build separate pipeline where we just use Serialize, etc.
|
21
23
|
|
22
24
|
def create_representation_with(doc, options, format)
|
23
|
-
options = normalize_options(options)
|
25
|
+
options = normalize_options(**options)
|
24
26
|
options[:_self] = options
|
25
27
|
|
26
28
|
bin = representable_bindings_for(format, options).first
|
@@ -30,7 +32,7 @@ module Representable::Hash
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def update_properties_from(doc, options, format)
|
33
|
-
options = normalize_options(options)
|
35
|
+
options = normalize_options(**options)
|
34
36
|
options[:_self] = options
|
35
37
|
|
36
38
|
bin = representable_bindings_for(format, options).first
|
@@ -20,8 +20,9 @@ module Representable
|
|
20
20
|
def filter_keys_for!(hash, options)
|
21
21
|
excluding = options[:exclude]
|
22
22
|
# TODO: use same filtering method as in normal representer in Representable#create_representation_with.
|
23
|
-
return hash unless props = options.delete(:exclude) || options.delete(:include)
|
24
|
-
|
23
|
+
return hash unless (props = (options.delete(:exclude) || options.delete(:include)))
|
24
|
+
|
25
|
+
hash.select { |k, _v| excluding ? !props.include?(k.to_sym) : props.include?(k.to_sym) }
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
data/lib/representable/insert.rb
CHANGED
data/lib/representable/json.rb
CHANGED
@@ -1,15 +1,13 @@
|
|
1
|
-
|
2
|
-
require "
|
1
|
+
gem "multi_json", '>= 1.14.1'
|
2
|
+
require "multi_json"
|
3
3
|
|
4
|
-
|
5
|
-
require "multi_json"
|
6
|
-
rescue LoadError => _
|
7
|
-
abort "Missing dependency 'multi_json' for Representable::JSON. See dependencies section in README.md for details."
|
8
|
-
end
|
4
|
+
require "representable"
|
9
5
|
|
10
6
|
module Representable
|
11
7
|
# Brings #to_json and #from_json to your object.
|
12
8
|
module JSON
|
9
|
+
autoload :Collection, "representable/json/collection"
|
10
|
+
|
13
11
|
extend Hash::ClassMethods
|
14
12
|
include Hash
|
15
13
|
|
data/lib/representable/object.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
require 'representable/binding'
|
2
|
+
|
1
3
|
module Representable
|
2
4
|
module Object
|
3
5
|
class Binding < Representable::Binding
|
4
6
|
def self.build_for(definition) # TODO: remove default arg.
|
5
7
|
return Collection.new(definition) if definition.array?
|
8
|
+
|
6
9
|
new(definition)
|
7
10
|
end
|
8
11
|
|
@@ -10,6 +13,7 @@ module Representable
|
|
10
13
|
fragment = hash.send(as) # :getter? no, that's for parsing!
|
11
14
|
|
12
15
|
return FragmentNotFound if fragment.nil? and typed?
|
16
|
+
|
13
17
|
fragment
|
14
18
|
end
|
15
19
|
|
@@ -31,4 +35,4 @@ module Representable
|
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|
34
|
-
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "trailblazer/option"
|
2
|
+
|
3
|
+
module Representable
|
4
|
+
# Extend `Trailblazer::Option` to support static values as callables too.
|
5
|
+
class Option < ::Trailblazer::Option
|
6
|
+
def self.callable?(value)
|
7
|
+
[Proc, Symbol, Uber::Callable].any?{ |kind| value.is_a?(kind) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.build(value)
|
11
|
+
return ->(*) { value } unless callable?(value) # Wrap static `value` into a proc.
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.Option(value)
|
17
|
+
::Representable::Option.build(value)
|
18
|
+
end
|
19
|
+
end
|
@@ -4,11 +4,12 @@ module Representable
|
|
4
4
|
class Pipeline < Array
|
5
5
|
Stop = Class.new
|
6
6
|
|
7
|
-
# options is
|
7
|
+
# options is mutable.
|
8
8
|
def call(input, options)
|
9
9
|
inject(input) do |memo, block|
|
10
10
|
res = evaluate(block, memo, options)
|
11
|
-
return(Stop)if Stop == res
|
11
|
+
return(Stop) if Stop == res
|
12
|
+
|
12
13
|
res
|
13
14
|
end
|
14
15
|
end
|
@@ -2,7 +2,8 @@
|
|
2
2
|
module Representable
|
3
3
|
module Binding::Factories
|
4
4
|
def pipeline_for(name, input, options)
|
5
|
-
return yield unless proc = @definition[name]
|
5
|
+
return yield unless (proc = @definition[name])
|
6
|
+
|
6
7
|
# proc.(self, options)
|
7
8
|
instance_exec(input, options, &proc)
|
8
9
|
end
|
@@ -11,6 +12,7 @@ module Representable
|
|
11
12
|
def collect_for(item_functions)
|
12
13
|
return [Collect[*item_functions]] if array?
|
13
14
|
return [Collect::Hash[*item_functions]] if self[:hash]
|
15
|
+
|
14
16
|
item_functions
|
15
17
|
end
|
16
18
|
|
@@ -92,4 +94,4 @@ module Representable
|
|
92
94
|
funcs << (self[:setter] ? Setter : SetValue)
|
93
95
|
end
|
94
96
|
end
|
95
|
-
end
|
97
|
+
end
|
@@ -21,7 +21,7 @@ module Representable
|
|
21
21
|
def self.apply!(options)
|
22
22
|
return unless populator = options[:populator]
|
23
23
|
|
24
|
-
options[:parse_pipeline] = ->(
|
24
|
+
options[:parse_pipeline] = ->(_input, _opts) do
|
25
25
|
pipeline = Pipeline[*parse_functions] # TODO: AssignFragment
|
26
26
|
pipeline = Pipeline::Insert.(pipeline, SetValue, delete: true) # remove the setter function.
|
27
27
|
pipeline = Pipeline::Insert.(pipeline, populator, replace: CreateObject::Populator) # let the actual populator do the job.
|
@@ -3,7 +3,7 @@ module Representable
|
|
3
3
|
options[:binding].evaluate_option(:getter, input, options)
|
4
4
|
end
|
5
5
|
|
6
|
-
GetValue = ->(
|
6
|
+
GetValue = ->(_input, options) { options[:binding].send(:exec_context, options).public_send(options[:binding].getter) }
|
7
7
|
|
8
8
|
Writer = ->(input, options) do
|
9
9
|
options[:binding].evaluate_option(:writer, input, options)
|
@@ -37,6 +37,7 @@ module Representable
|
|
37
37
|
|
38
38
|
Serialize = ->(input, options) do
|
39
39
|
return if input.nil? # DISCUSS: how can we prevent that?
|
40
|
+
|
40
41
|
binding, options = options[:binding], options[:options] # FIXME: rename to :local_options.
|
41
42
|
|
42
43
|
options_for_nested = OptionsForNested.(options, binding)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
gem 'virtus'
|
2
|
+
require "virtus"
|
3
|
+
|
4
|
+
module Representable
|
5
|
+
module VirtusCoercion
|
6
|
+
class Coercer
|
7
|
+
def initialize(type)
|
8
|
+
@type = type
|
9
|
+
end
|
10
|
+
|
11
|
+
# This gets called when the :render_filter or :parse_filter option is evaluated.
|
12
|
+
# Usually the Coercer instance is an element in a Pipeline to allow >1 filters per property.
|
13
|
+
def call(input, options)
|
14
|
+
Virtus::Attribute.build(@type).coerce(input)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
extend ClassMethods
|
22
|
+
register_feature VirtusCoercion
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def property(name, options={}, &block)
|
29
|
+
super.tap do |definition|
|
30
|
+
return definition unless type = options[:type]
|
31
|
+
|
32
|
+
definition.merge!(render_filter: coercer = Coercer.new(type))
|
33
|
+
definition.merge!(parse_filter: coercer)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/representable/xml.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
|
1
|
+
gem 'nokogiri', '> 1.10.8'
|
2
|
+
require 'nokogiri'
|
2
3
|
|
3
|
-
|
4
|
-
require 'nokogiri'
|
5
|
-
rescue LoadError => _
|
6
|
-
abort "Missing dependency 'nokogiri' for Representable::XML. See dependencies section in README.md for details."
|
7
|
-
end
|
4
|
+
require 'representable'
|
8
5
|
|
9
6
|
module Representable
|
10
7
|
module XML
|
8
|
+
autoload :Binding, 'representable/xml/binding'
|
9
|
+
autoload :Collection, 'representable/xml/collection'
|
10
|
+
autoload :Namespace, 'representable/xml/namespace'
|
11
|
+
|
11
12
|
def self.included(base)
|
12
13
|
base.class_eval do
|
13
14
|
include Representable
|
@@ -71,7 +72,3 @@ module Representable
|
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
74
|
-
|
75
|
-
require "representable/xml/binding"
|
76
|
-
require "representable/xml/collection"
|
77
|
-
require "representable/xml/namespace"
|