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.
Files changed (98) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +17 -0
  3. data/CHANGES.md +10 -0
  4. data/Gemfile +4 -7
  5. data/LICENSE +1 -1
  6. data/README.md +4 -3
  7. data/Rakefile +1 -6
  8. data/lib/representable.rb +18 -25
  9. data/lib/representable/binding.rb +32 -12
  10. data/lib/representable/cached.rb +1 -1
  11. data/lib/representable/coercion.rb +8 -6
  12. data/lib/representable/config.rb +8 -3
  13. data/lib/representable/debug.rb +23 -15
  14. data/lib/representable/declarative.rb +8 -3
  15. data/lib/representable/decorator.rb +1 -1
  16. data/lib/representable/definition.rb +7 -2
  17. data/lib/representable/deserializer.rb +4 -3
  18. data/lib/representable/for_collection.rb +1 -1
  19. data/lib/representable/hash.rb +6 -2
  20. data/lib/representable/hash/allow_symbols.rb +9 -11
  21. data/lib/representable/hash/binding.rb +1 -0
  22. data/lib/representable/hash/collection.rb +4 -2
  23. data/lib/representable/hash_methods.rb +3 -2
  24. data/lib/representable/insert.rb +1 -1
  25. data/lib/representable/json.rb +5 -7
  26. data/lib/representable/json/collection.rb +3 -0
  27. data/lib/representable/json/hash.rb +1 -0
  28. data/lib/representable/object.rb +1 -1
  29. data/lib/representable/object/binding.rb +5 -1
  30. data/lib/representable/option.rb +19 -0
  31. data/lib/representable/pipeline.rb +3 -2
  32. data/lib/representable/pipeline_factories.rb +4 -2
  33. data/lib/representable/populator.rb +1 -1
  34. data/lib/representable/represent.rb +1 -0
  35. data/lib/representable/serializer.rb +2 -1
  36. data/lib/representable/version.rb +1 -1
  37. data/lib/representable/virtus_coercion.rb +38 -0
  38. data/lib/representable/xml.rb +7 -10
  39. data/lib/representable/xml/binding.rb +2 -3
  40. data/lib/representable/yaml.rb +3 -3
  41. data/lib/representable/yaml/binding.rb +1 -0
  42. data/representable.gemspec +3 -3
  43. data/test/as_test.rb +4 -4
  44. data/test/binding_test.rb +10 -10
  45. data/test/cached_test.rb +19 -19
  46. data/test/class_test.rb +7 -7
  47. data/test/coercion_test.rb +33 -22
  48. data/test/config/inherit_test.rb +14 -14
  49. data/test/config_test.rb +18 -18
  50. data/test/decorator_scope_test.rb +3 -3
  51. data/test/decorator_test.rb +17 -17
  52. data/test/default_test.rb +7 -7
  53. data/test/definition_test.rb +32 -32
  54. data/test/{example.rb → examples/example.rb} +0 -0
  55. data/test/exec_context_test.rb +6 -6
  56. data/test/features_test.rb +3 -3
  57. data/test/filter_test.rb +6 -6
  58. data/test/for_collection_test.rb +2 -2
  59. data/test/generic_test.rb +3 -3
  60. data/test/getter_setter_test.rb +5 -5
  61. data/test/hash_test.rb +19 -19
  62. data/test/heritage_test.rb +4 -4
  63. data/test/if_test.rb +6 -6
  64. data/test/include_exclude_test.rb +12 -12
  65. data/test/inherit_test.rb +15 -15
  66. data/test/inline_test.rb +11 -11
  67. data/test/instance_test.rb +29 -29
  68. data/test/is_representable_test.rb +10 -10
  69. data/test/json_test.rb +7 -7
  70. data/test/lonely_test.rb +16 -16
  71. data/test/nested_test.rb +7 -7
  72. data/test/object_test.rb +7 -7
  73. data/test/option_test.rb +36 -0
  74. data/test/parse_pipeline_test.rb +3 -3
  75. data/test/pipeline_test.rb +43 -43
  76. data/test/populator_test.rb +15 -15
  77. data/test/prepare_test.rb +2 -2
  78. data/test/private_options_test.rb +2 -2
  79. data/test/reader_writer_test.rb +2 -2
  80. data/test/render_nil_test.rb +2 -2
  81. data/test/represent_test.rb +4 -4
  82. data/test/representable_test.rb +27 -27
  83. data/test/schema_test.rb +5 -5
  84. data/test/serialize_deserialize_test.rb +2 -2
  85. data/test/skip_test.rb +10 -10
  86. data/test/stringify_hash_test.rb +3 -3
  87. data/test/test_helper.rb +4 -2
  88. data/test/uncategorized_test.rb +10 -10
  89. data/test/user_options_test.rb +4 -4
  90. data/test/virtus_coercion_test.rb +52 -0
  91. data/test/wrap_test.rb +11 -11
  92. data/test/xml_namespace_test.rb +1 -1
  93. data/test/xml_test.rb +6 -6
  94. data/test/yaml_test.rb +20 -20
  95. metadata +81 -15
  96. data/.travis.yml +0 -16
  97. data/lib/representable/autoload.rb +0 -14
  98. 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: ->(opts) { self },
29
+ getter: ->(_opts) { self },
25
30
  setter: ->(opts) { },
26
- instance: ->(opts) { self },
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,5 +1,5 @@
1
- require "representable"
2
1
  require "uber/inheritable_attr"
2
+ require "representable"
3
3
 
4
4
  module Representable
5
5
  class Decorator
@@ -1,6 +1,10 @@
1
- require "representable/populator"
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 = ::Declarative::Option(value, instance_exec: true, callable: Uber::Callable) if dynamic_options.include?(name)
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, options) do
14
+ StopOnNotFound = ->(input, _options) do
15
15
  Binding::FragmentNotFound == input ? Pipeline::Stop : input
16
16
  end
17
17
 
18
- StopOnNil = ->(input, options) do # DISCUSS: Not tested/used, yet.
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
@@ -24,4 +24,4 @@ module Representable
24
24
  @collection_representer = collection_representer!(options)
25
25
  end
26
26
  end
27
- end
27
+ end
@@ -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
- private
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
- class Conversion
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 = hash.dup
19
-
20
- hash.keys.each do |k|
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
@@ -5,6 +5,7 @@ module Representable
5
5
  class Binding < Representable::Binding
6
6
  def self.build_for(definition)
7
7
  return Collection.new(definition) if definition.array?
8
+
8
9
  new(definition)
9
10
  end
10
11
 
@@ -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
- hash.reject { |k,v| excluding ? props.include?(k.to_sym) : !props.include?(k.to_sym) }
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
@@ -35,4 +35,4 @@ module Representable
35
35
 
36
36
  Insert = Pipeline::Function::Insert.new
37
37
  end # Pipeline
38
- end
38
+ end
@@ -1,15 +1,13 @@
1
- require "representable/hash"
2
- require "representable/json/collection"
1
+ gem "multi_json", '>= 1.14.1'
2
+ require "multi_json"
3
3
 
4
- begin
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
 
@@ -1,3 +1,6 @@
1
+ require 'representable/json'
2
+ require 'representable/hash/collection'
3
+
1
4
  module Representable::JSON
2
5
  module Collection
3
6
  include Representable::JSON
@@ -1,3 +1,4 @@
1
+ require 'representable/json'
1
2
  require 'representable/hash_methods'
2
3
 
3
4
  module Representable::JSON
@@ -26,4 +26,4 @@ module Representable
26
26
  create_representation_with(nil, options, binding_builder)
27
27
  end
28
28
  end
29
- end
29
+ end
@@ -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 mutuable.
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] = ->(input, opts) do
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.
@@ -1,6 +1,7 @@
1
1
  module Representable::Represent
2
2
  def represent(represented, array_class=Array)
3
3
  return for_collection.prepare(represented) if represented.is_a?(array_class)
4
+
4
5
  prepare(represented)
5
6
  end
6
7
  end
@@ -3,7 +3,7 @@ module Representable
3
3
  options[:binding].evaluate_option(:getter, input, options)
4
4
  end
5
5
 
6
- GetValue = ->(input, options) { options[:binding].send(:exec_context, options).public_send(options[:binding].getter) }
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)
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "3.0.4"
2
+ VERSION = "3.1.1"
3
3
  end
@@ -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
@@ -1,13 +1,14 @@
1
- require 'representable'
1
+ gem 'nokogiri', '> 1.10.8'
2
+ require 'nokogiri'
2
3
 
3
- begin
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"