representable 3.0.0 → 3.1.0

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 (105) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +17 -0
  3. data/CHANGES.md +25 -0
  4. data/Gemfile +4 -12
  5. data/LICENSE +1 -1
  6. data/README.md +6 -6
  7. data/Rakefile +1 -6
  8. data/TODO +1 -3
  9. data/TODO-4.0.md +72 -0
  10. data/lib/representable.rb +19 -25
  11. data/lib/representable/binding.rb +32 -12
  12. data/lib/representable/cached.rb +1 -1
  13. data/lib/representable/coercion.rb +8 -6
  14. data/lib/representable/config.rb +13 -3
  15. data/lib/representable/debug.rb +23 -15
  16. data/lib/representable/declarative.rb +12 -7
  17. data/lib/representable/decorator.rb +1 -1
  18. data/lib/representable/definition.rb +7 -3
  19. data/lib/representable/deserializer.rb +5 -4
  20. data/lib/representable/for_collection.rb +1 -1
  21. data/lib/representable/hash.rb +9 -2
  22. data/lib/representable/hash/allow_symbols.rb +9 -11
  23. data/lib/representable/hash/binding.rb +1 -0
  24. data/lib/representable/hash/collection.rb +4 -2
  25. data/lib/representable/hash_methods.rb +3 -2
  26. data/lib/representable/insert.rb +1 -1
  27. data/lib/representable/json.rb +8 -7
  28. data/lib/representable/json/collection.rb +3 -0
  29. data/lib/representable/object.rb +1 -1
  30. data/lib/representable/object/binding.rb +5 -1
  31. data/lib/representable/option.rb +19 -0
  32. data/lib/representable/pipeline.rb +3 -2
  33. data/lib/representable/pipeline_factories.rb +4 -2
  34. data/lib/representable/populator.rb +1 -1
  35. data/lib/representable/represent.rb +1 -0
  36. data/lib/representable/serializer.rb +3 -2
  37. data/lib/representable/version.rb +1 -1
  38. data/lib/representable/virtus_coercion.rb +38 -0
  39. data/lib/representable/xml.rb +12 -10
  40. data/lib/representable/xml/binding.rb +19 -13
  41. data/lib/representable/xml/namespace.rb +122 -0
  42. data/lib/representable/yaml.rb +6 -2
  43. data/lib/representable/yaml/binding.rb +1 -0
  44. data/representable.gemspec +8 -9
  45. data/test/as_test.rb +7 -7
  46. data/test/binding_test.rb +14 -14
  47. data/test/cached_test.rb +59 -49
  48. data/test/class_test.rb +9 -9
  49. data/test/coercion_test.rb +33 -22
  50. data/test/config/inherit_test.rb +14 -14
  51. data/test/config_test.rb +20 -20
  52. data/test/decorator_scope_test.rb +4 -4
  53. data/test/decorator_test.rb +33 -20
  54. data/test/default_test.rb +8 -8
  55. data/test/defaults_options_test.rb +3 -3
  56. data/test/definition_test.rb +38 -40
  57. data/test/{example.rb → examples/example.rb} +0 -1
  58. data/test/examples/object.rb +1 -5
  59. data/test/exec_context_test.rb +8 -8
  60. data/test/features_test.rb +6 -6
  61. data/test/filter_test.rb +8 -8
  62. data/test/for_collection_test.rb +10 -10
  63. data/test/generic_test.rb +13 -13
  64. data/test/getter_setter_test.rb +5 -5
  65. data/test/hash_bindings_test.rb +1 -1
  66. data/test/hash_test.rb +45 -23
  67. data/test/heritage_test.rb +16 -13
  68. data/test/if_test.rb +9 -9
  69. data/test/include_exclude_test.rb +14 -14
  70. data/test/inherit_test.rb +18 -18
  71. data/test/inline_test.rb +24 -24
  72. data/test/instance_test.rb +31 -31
  73. data/test/is_representable_test.rb +10 -10
  74. data/test/json_test.rb +29 -7
  75. data/test/lonely_test.rb +31 -31
  76. data/test/nested_test.rb +13 -13
  77. data/test/object_test.rb +9 -9
  78. data/test/option_test.rb +36 -0
  79. data/test/parse_pipeline_test.rb +3 -5
  80. data/test/pipeline_test.rb +50 -50
  81. data/test/populator_test.rb +18 -18
  82. data/test/prepare_test.rb +4 -4
  83. data/test/private_options_test.rb +2 -2
  84. data/test/reader_writer_test.rb +2 -2
  85. data/test/render_nil_test.rb +2 -2
  86. data/test/represent_test.rb +14 -14
  87. data/test/representable_test.rb +34 -36
  88. data/test/schema_test.rb +8 -11
  89. data/test/serialize_deserialize_test.rb +2 -2
  90. data/test/skip_test.rb +14 -14
  91. data/test/stringify_hash_test.rb +3 -3
  92. data/test/test_helper.rb +26 -14
  93. data/test/uncategorized_test.rb +10 -10
  94. data/test/user_options_test.rb +4 -4
  95. data/test/virtus_coercion_test.rb +52 -0
  96. data/test/wrap_test.rb +19 -19
  97. data/test/xml_bindings_test.rb +0 -4
  98. data/test/xml_namespace_test.rb +186 -0
  99. data/test/xml_test.rb +103 -43
  100. data/test/yaml_test.rb +51 -26
  101. metadata +101 -39
  102. data/.travis.yml +0 -7
  103. data/lib/representable/TODO.getting_serious +0 -11
  104. data/lib/representable/autoload.rb +0 -10
  105. data/test/mongoid_test.rb +0 -31
@@ -1,10 +1,19 @@
1
+ require 'declarative/definitions'
2
+
1
3
  module Representable
4
+ autoload :Option, 'representable/option'
5
+
2
6
  # Stores Definitions from ::property. It preserves the adding order (1.9+).
3
7
  # Same-named properties get overridden, just like in a Hash.
4
8
  #
5
9
  # Overwrite definition_class if you need a custom Definition object (helpful when using
6
10
  # representable in other gems).
7
11
  class Config < ::Declarative::Definitions
12
+ def initialize(*)
13
+ super
14
+ @wrap = nil
15
+ end
16
+
8
17
  def remove(name)
9
18
  delete(name.to_s)
10
19
  end
@@ -15,16 +24,17 @@ module Representable
15
24
 
16
25
  def wrap=(value)
17
26
  value = value.to_s if value.is_a?(Symbol)
18
- @wrap = Uber::Options::Value.new(value)
27
+ @wrap = ::Representable::Option(value)
19
28
  end
20
29
 
21
30
  # Computes the wrap string or returns false.
22
- def wrap_for(represented, *args, &block)
31
+ def wrap_for(represented, options = {}, &block)
23
32
  return unless @wrap
24
33
 
25
- value = @wrap.evaluate(represented, *args)
34
+ value = @wrap.(exec_context: represented, keyword_arguments: options.to_hash)
26
35
 
27
36
  return value if value != true
37
+
28
38
  infer_name_for(represented.class.name)
29
39
  end
30
40
 
@@ -1,16 +1,24 @@
1
+ require 'logger'
2
+
1
3
  module Representable
2
4
  module Debug
5
+ module_function def _representable_logger
6
+ @logger ||= Logger.new(STDOUT)
7
+ end
8
+
9
+ module_function def representable_log(message)
10
+ _representable_logger.debug { message }
11
+ end
12
+
3
13
  def update_properties_from(doc, options, format)
4
- puts
5
- puts "[Deserialize]........."
6
- puts "[Deserialize] document #{doc.inspect}"
14
+ representable_log "[Deserialize]........."
15
+ representable_log "[Deserialize] document #{doc.inspect}"
7
16
  super
8
17
  end
9
18
 
10
19
  def create_representation_with(doc, options, format)
11
- puts
12
- puts "[Serialize]........."
13
- puts "[Serialize]"
20
+ representable_log "[Serialize]........."
21
+ representable_log "[Serialize]"
14
22
  super
15
23
  end
16
24
 
@@ -22,13 +30,13 @@ module Representable
22
30
 
23
31
  module Binding
24
32
  def evaluate_option(name, *args, &block)
25
- puts "=====#{self[name]}" if name ==:prepare
26
- puts (evaled = self[name]) ?
33
+ Debug.representable_log "=====#{self[name]}" if name ==:prepare
34
+ Debug.representable_log (evaled = self[name]) ?
27
35
  " #evaluate_option [#{name}]: eval!!!" :
28
36
  " #evaluate_option [#{name}]: skipping"
29
37
  value = super
30
- puts " #evaluate_option [#{name}]: --> #{value}" if evaled
31
- puts " #evaluate_option [#{name}]: -->= #{args.first}" if name == :setter
38
+ Debug.representable_log " #evaluate_option [#{name}]: --> #{value}" if evaled
39
+ Debug.representable_log " #evaluate_option [#{name}]: -->= #{args.first}" if name == :setter
32
40
  value
33
41
  end
34
42
 
@@ -45,17 +53,17 @@ module Representable
45
53
 
46
54
  module Pipeline::Debug
47
55
  def call(input, options)
48
- puts "Pipeline#call: #{inspect}"
49
- puts " input: #{input.inspect}"
56
+ Debug.representable_log "Pipeline#call: #{inspect}"
57
+ Debug.representable_log " input: #{input.inspect}"
50
58
  super
51
59
  end
52
60
 
53
61
  def evaluate(block, memo, options)
54
62
  block.extend(Pipeline::Debug) if block.is_a?(Collect)
55
63
 
56
- puts " Pipeline : -> #{_inspect_function(block)} "
64
+ Debug.representable_log " Pipeline : -> #{_inspect_function(block)} "
57
65
  super.tap do |res|
58
- puts " Pipeline : result: #{res.inspect}"
66
+ Debug.representable_log " Pipeline : result: #{res.inspect}"
59
67
  end
60
68
  end
61
69
 
@@ -70,8 +78,8 @@ module Representable
70
78
  def _inspect_function(func)
71
79
  return func.extend(Pipeline::Debug).inspect if func.is_a?(Collect)
72
80
  return func unless func.is_a?(Proc)
81
+
73
82
  File.readlines(func.source_location[0])[func.source_location[1]-1].match(/^\s+(\w+)/)[1]
74
83
  end
75
84
  end
76
85
  end
77
-
@@ -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: ->(options) { self },
25
- setter: ->(options) { },
26
- instance: ->(options) { self },
29
+ getter: ->(_opts) { self },
30
+ setter: ->(opts) { },
31
+ instance: ->(_opts) { self },
27
32
  )
28
33
 
29
34
  if block
@@ -47,10 +52,10 @@ module Representable
47
52
  NestedBuilder = ->(options) do
48
53
  Module.new do
49
54
  include Representable # FIXME: do we really need this?
50
- feature *options[:_features]
51
- include *options[:_base] # base when :inherit, or in decorator.
55
+ feature(*options[:_features])
56
+ include(*options[:_base]) # base when :inherit, or in decorator.
52
57
 
53
- module_eval &options[:_block]
58
+ module_eval(&options[:_block])
54
59
  end
55
60
  end
56
61
 
@@ -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,7 +1,10 @@
1
- require "uber/options"
2
- require "representable/populator"
1
+ require 'declarative/definitions'
3
2
 
4
3
  module Representable
4
+ autoload :Pipeline, "representable/pipeline"
5
+ autoload :Populator, "representable/populator"
6
+ autoload :Option, "representable/option"
7
+
5
8
  # Created at class compile time. Keeps configuration options for one property.
6
9
  class Definition < ::Declarative::Definitions::Definition
7
10
 
@@ -54,6 +57,7 @@ module Representable
54
57
 
55
58
  def representable?
56
59
  return if self[:representable] == false
60
+
57
61
  self[:representable] or typed?
58
62
  end
59
63
 
@@ -102,7 +106,7 @@ module Representable
102
106
  @runtime_options = {}
103
107
 
104
108
  for name, value in options
105
- value = Uber::Options::Value.new(value) if dynamic_options.include?(name)
109
+ value = ::Representable::Option(value) if dynamic_options.include?(name)
106
110
  @runtime_options[name] = value
107
111
  end
108
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
 
@@ -96,13 +96,14 @@ module Representable
96
96
  If = ->(input, options) { options[:binding].evaluate_option(:if, nil, options) ? input : Pipeline::Stop }
97
97
 
98
98
  StopOnExcluded = ->(input, options) do
99
- return input unless private = options[:options]
99
+ return input unless options[:options]
100
100
  return input unless props = (options[:options][:exclude] || options[:options][:include])
101
101
 
102
102
  res = props.include?(options[:binding].name.to_sym) # false with include: Stop. false with exclude: go!
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,15 +37,19 @@ 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
41
44
 
45
+ alias_method :render, :to_hash
46
+ alias_method :parse, :from_hash
47
+
42
48
  private
43
49
  def filter_wrap(data, options)
44
50
  return data if options[:wrap] == false
45
- return data unless wrap = options[:wrap] || representation_wrap(options)
51
+ return data unless (wrap = options[:wrap] || representation_wrap(options))
52
+
46
53
  filter_wrap_for(data, wrap)
47
54
  end
48
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
 
@@ -43,5 +41,8 @@ module Representable
43
41
  def to_json(*args)
44
42
  MultiJson.dump to_hash(*args)
45
43
  end
44
+
45
+ alias_method :render, :to_json
46
+ alias_method :parse, :from_json
46
47
  end
47
48
  end
@@ -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
@@ -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