representable 3.0.4 → 3.1.1
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 +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"
|