representable 1.8.5 → 2.0.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGES.md +36 -0
  4. data/Gemfile +3 -3
  5. data/README.md +62 -2
  6. data/Rakefile +1 -1
  7. data/lib/representable.rb +32 -109
  8. data/lib/representable/TODO.getting_serious +7 -1
  9. data/lib/representable/autoload.rb +10 -0
  10. data/lib/representable/binding.rb +20 -16
  11. data/lib/representable/bindings/xml_bindings.rb +0 -1
  12. data/lib/representable/coercion.rb +23 -31
  13. data/lib/representable/config.rb +45 -46
  14. data/lib/representable/declarative.rb +78 -0
  15. data/lib/representable/decorator.rb +39 -10
  16. data/lib/representable/definition.rb +40 -33
  17. data/lib/representable/deserializer.rb +2 -0
  18. data/lib/representable/for_collection.rb +25 -0
  19. data/lib/representable/hash.rb +4 -8
  20. data/lib/representable/hash/collection.rb +2 -9
  21. data/lib/representable/hash_methods.rb +0 -7
  22. data/lib/representable/inheritable.rb +50 -0
  23. data/lib/representable/json.rb +3 -9
  24. data/lib/representable/json/collection.rb +1 -3
  25. data/lib/representable/json/hash.rb +4 -9
  26. data/lib/representable/mapper.rb +8 -5
  27. data/lib/representable/parse_strategies.rb +1 -0
  28. data/lib/representable/pipeline.rb +14 -0
  29. data/lib/representable/represent.rb +6 -0
  30. data/lib/representable/version.rb +1 -1
  31. data/lib/representable/xml.rb +3 -18
  32. data/lib/representable/xml/collection.rb +2 -4
  33. data/lib/representable/xml/hash.rb +2 -10
  34. data/lib/representable/yaml.rb +1 -20
  35. data/representable.gemspec +2 -2
  36. data/test/class_test.rb +5 -10
  37. data/test/coercion_test.rb +31 -92
  38. data/test/config/inherit_test.rb +128 -0
  39. data/test/config_test.rb +114 -80
  40. data/test/definition_test.rb +107 -64
  41. data/test/features_test.rb +41 -0
  42. data/test/filter_test.rb +59 -0
  43. data/test/for_collection_test.rb +74 -0
  44. data/test/inherit_test.rb +44 -3
  45. data/test/inheritable_test.rb +97 -0
  46. data/test/inline_test.rb +0 -18
  47. data/test/instance_test.rb +0 -19
  48. data/test/json_test.rb +9 -44
  49. data/test/lonely_test.rb +1 -0
  50. data/test/parse_strategy_test.rb +30 -0
  51. data/test/represent_test.rb +88 -0
  52. data/test/representable_test.rb +3 -50
  53. data/test/schema_test.rb +123 -0
  54. data/test/test_helper.rb +1 -1
  55. data/test/xml_test.rb +34 -38
  56. metadata +25 -15
  57. data/lib/representable/decorator/coercion.rb +0 -4
  58. data/lib/representable/readable_writeable.rb +0 -29
  59. data/test/inheritance_test.rb +0 -22
@@ -5,6 +5,8 @@ module Representable
5
5
  end
6
6
 
7
7
  def deserialize(fragment)
8
+ # puts "deserialize #{@binding.name}" # TODO: introduce Representable::Debug.
9
+
8
10
  # next step: get rid of collect.
9
11
  fragment.enum_for(:each_with_index).collect do |item_fragment, i|
10
12
  @deserializer = ObjectDeserializer.new(@binding)
@@ -0,0 +1,25 @@
1
+ module Representable
2
+ # Gives us Representer::for_collection and its configuration directive
3
+ # ::collection_representer.
4
+ module ForCollection
5
+ def for_collection
6
+ # this is done at run-time, not a big fan of this. however, it saves us from inheritance/self problems.
7
+ @collection_representer ||= collection_representer!({}) # DON'T make it inheritable as it would inherit the wrong singular.
8
+ end
9
+
10
+ private
11
+ def collection_representer!(options)
12
+ singular = self
13
+
14
+ # what happens here is basically
15
+ # Module.new { include Representable::JSON::Collection; ... }
16
+ build_inline(nil, [singular.collection_representer_class], "", {}) {
17
+ items options.merge(:extend => singular)
18
+ }
19
+ end
20
+
21
+ def collection_representer(options={})
22
+ @collection_representer = collection_representer!(options)
23
+ end
24
+ end
25
+ end
@@ -9,19 +9,15 @@ module Representable
9
9
  def self.included(base)
10
10
  base.class_eval do
11
11
  include Representable # either in Hero or HeroRepresentation.
12
- extend ClassMethods # DISCUSS: do that only for classes?
12
+ extend ClassMethods
13
+ register_feature Representable::Hash
13
14
  end
14
15
  end
15
16
 
16
17
 
17
18
  module ClassMethods
18
- def from_hash(*args, &block)
19
- create_represented(*args, &block).from_hash(*args)
20
- end
21
-
22
- private
23
- def representer_engine
24
- Representable::Hash
19
+ def collection_representer_class
20
+ Collection
25
21
  end
26
22
  end
27
23
 
@@ -4,9 +4,9 @@ module Representable::Hash
4
4
 
5
5
  def self.included(base)
6
6
  base.class_eval do
7
- include Representable
7
+ include Representable::Hash
8
8
  extend ClassMethods
9
- extend Representable::Hash::ClassMethods # ::representer_engine.
9
+ representable_attrs.add(:_self, {:collection => true})
10
10
  end
11
11
  end
12
12
 
@@ -28,12 +28,5 @@ module Representable::Hash
28
28
  value = bin.deserialize_from(doc)
29
29
  represented.replace(value)
30
30
  end
31
-
32
- # FIXME: refactor Definition so we can simply add options in #items to existing definition.
33
- def representable_attrs
34
- attrs = super
35
- attrs << Definition.new(:_self, :collection => true) if attrs.size == 0
36
- attrs
37
- end
38
31
  end
39
32
  end
@@ -1,12 +1,5 @@
1
1
  module Representable
2
2
  module HashMethods
3
- # FIXME: refactor Definition so we can simply add options in #items to existing definition.
4
- def representable_attrs
5
- attrs = super
6
- attrs << Definition.new(*definition_opts) if attrs.size == 0
7
- attrs
8
- end
9
-
10
3
  def create_representation_with(doc, options, format)
11
4
  bin = representable_mapper(format, options).bindings.first
12
5
  hash = filter_keys_for(represented, options)
@@ -0,0 +1,50 @@
1
+ module Representable
2
+ # Objects marked cloneable will be cloned in #inherit!.
3
+ module Cloneable
4
+ end
5
+
6
+ # Objects marked cloneable will be inherit!ed in #inherit! when available in parent and child.
7
+ module Inheritable
8
+ include Cloneable # all Inheritable are also Cloneable since #clone is one step of our inheritance.
9
+
10
+ class Array < ::Array
11
+ include Inheritable
12
+
13
+ def inherit!(parent)
14
+ push(*parent.clone)
15
+ end
16
+ end
17
+
18
+ class Hash < ::Hash
19
+ include Inheritable
20
+
21
+ module InstanceMethods
22
+ def inherit!(parent)
23
+ #merge!(parent.clone)
24
+ for key in (parent.keys + keys).uniq
25
+ next unless parent_value = parent[key]
26
+
27
+ self[key].inherit!(parent_value) and next if self[key].is_a?(Inheritable)
28
+ self[key] = parent_value.clone and next if parent_value.is_a?(Cloneable)
29
+
30
+ self[key] = parent_value # merge! behaviour
31
+ end
32
+
33
+ self
34
+ end
35
+
36
+ def clone
37
+ self.class[ collect { |k,v| [k, clone_value(v)] } ]
38
+ end
39
+
40
+ private
41
+ def clone_value(value)
42
+ return value.clone if value.is_a?(Cloneable)
43
+ value
44
+ end
45
+ end
46
+
47
+ include InstanceMethods
48
+ end
49
+ end
50
+ end
@@ -11,20 +11,14 @@ module Representable
11
11
  base.class_eval do
12
12
  include Representable # either in Hero or HeroRepresentation.
13
13
  extend ClassMethods # DISCUSS: do that only for classes?
14
- extend Representable::Hash::ClassMethods # DISCUSS: this is only for .from_hash, remove in 2.3?
14
+ register_feature Representable::JSON
15
15
  end
16
16
  end
17
17
 
18
18
 
19
19
  module ClassMethods
20
- # Creates a new object from the passed JSON document.
21
- def from_json(*args, &block)
22
- create_represented(*args, &block).from_json(*args)
23
- end
24
-
25
- private
26
- def representer_engine
27
- Representable::JSON
20
+ def collection_representer_class
21
+ JSON::Collection
28
22
  end
29
23
  end
30
24
 
@@ -3,9 +3,7 @@ module Representable::JSON
3
3
  include Representable::JSON
4
4
 
5
5
  def self.included(base)
6
- base.class_eval do
7
- include Representable::Hash::Collection
8
- end
6
+ base.send :include, Representable::Hash::Collection
9
7
  end
10
8
  end
11
9
  end
@@ -2,15 +2,15 @@ require 'representable/json'
2
2
  require 'representable/hash_methods'
3
3
 
4
4
  module Representable::JSON
5
+ # "Lonely Hash" support.
5
6
  module Hash
6
- include Representable::JSON
7
- include Representable::HashMethods
8
-
9
7
  def self.included(base)
10
8
  base.class_eval do
11
9
  include Representable
12
10
  extend ClassMethods
13
- extend Representable::Hash::ClassMethods # ::representer_engine.
11
+ include Representable::JSON
12
+ include Representable::HashMethods
13
+ representable_attrs.add(:_self, {:hash => true})
14
14
  end
15
15
  end
16
16
 
@@ -20,10 +20,5 @@ module Representable::JSON
20
20
  hash(:_self, options, &block)
21
21
  end
22
22
  end
23
-
24
-
25
- def definition_opts
26
- [:_self, {:hash => true, :use_attributes => true}]
27
- end
28
23
  end
29
24
  end
@@ -1,5 +1,3 @@
1
- require 'representable/readable_writeable'
2
-
3
1
  module Representable
4
2
  # Render and parse by looping over the representer's properties and dispatching to bindings.
5
3
  # Conditionals are handled here, too.
@@ -28,18 +26,19 @@ module Representable
28
26
 
29
27
  private
30
28
  def serialize_property(binding, doc, options)
31
- return if skip_property?(binding, options)
29
+ return if skip_property?(binding, options.merge(:action => :serialize))
32
30
  compile_fragment(binding, doc)
33
31
  end
34
32
 
35
33
  def deserialize_property(binding, doc, options)
36
- return if skip_property?(binding, options)
34
+ return if skip_property?(binding, options.merge(:action => :deserialize))
37
35
  uncompile_fragment(binding, doc)
38
36
  end
39
37
 
40
38
  # Checks and returns if the property should be included.
41
39
  def skip_property?(binding, options)
42
40
  return true if skip_excluded_property?(binding, options) # no need for further evaluation when :exclude'ed
41
+ return true if skip_protected_property(binding, options)
43
42
 
44
43
  skip_conditional_property?(binding)
45
44
  end
@@ -56,6 +55,11 @@ module Representable
56
55
  not binding.send(:evaluate_option, :if)
57
56
  end
58
57
 
58
+ # DISCUSS: this could be just another :if option in a Pipeline?
59
+ def skip_protected_property(binding, options)
60
+ options[:action] == :serialize ? binding[:readable] == false : binding[:writeable] == false
61
+ end
62
+
59
63
  def compile_fragment(bin, doc)
60
64
  bin.compile_fragment(doc)
61
65
  end
@@ -66,6 +70,5 @@ module Representable
66
70
  end
67
71
 
68
72
  include Methods
69
- include ReadableWriteable # DISCUSS: make this pluggable.
70
73
  end
71
74
  end
@@ -21,6 +21,7 @@ module Representable
21
21
  end
22
22
 
23
23
 
24
+ # Using a lambda as parse_strategy does not set the parsed property for you.
24
25
  class Proc
25
26
  def self.apply!(name, options)
26
27
  options[:setter] = lambda { |*| }
@@ -0,0 +1,14 @@
1
+ module Representable
2
+ # Allows to implement a pipeline of filters where a value gets passed in and the result gets
3
+ # passed to the next callable object.
4
+ #
5
+ # Note: this is still experimental.
6
+ class Pipeline < Array
7
+ include Uber::Callable
8
+ # include Representable::Cloneable
9
+
10
+ def call(context, value, *args)
11
+ inject(value) { |memo, block| block.call(memo, *args) }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ module Representable::Represent
2
+ def represent(represented, array_class=Array)
3
+ return for_collection.prepare(represented) if represented.is_a?(array_class)
4
+ prepare(represented)
5
+ end
6
+ end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.8.5"
2
+ VERSION = "2.0.0.rc1"
3
3
  end
@@ -9,6 +9,7 @@ module Representable
9
9
  include Representable
10
10
  extend ClassMethods
11
11
  self.representation_wrap = true # let representable compute it.
12
+ register_feature Representable::XML
12
13
  end
13
14
  end
14
15
 
@@ -18,24 +19,8 @@ module Representable
18
19
  representable_attrs.options[:remove_namespaces] = true
19
20
  end
20
21
 
21
- # Creates a new Ruby object from XML using mapping information declared in the class.
22
- #
23
- # Accepts a block yielding the currently iterated Definition. If the block returns false
24
- # the property is skipped.
25
- #
26
- # Example:
27
- # band.from_xml("<band><name>Nofx</name></band>")
28
- def from_xml(*args, &block)
29
- create_represented(*args, &block).from_xml(*args)
30
- end
31
-
32
- def from_node(*args, &block)
33
- create_represented(*args, &block).from_node(*args)
34
- end
35
-
36
- private
37
- def representer_engine
38
- Representable::XML
22
+ def collection_representer_class
23
+ Collection
39
24
  end
40
25
  end
41
26
 
@@ -3,10 +3,8 @@ module Representable::XML
3
3
  include Representable::XML
4
4
 
5
5
  def self.included(base)
6
- base.class_eval do
7
- include Representable::Hash::Collection
8
- include Methods
9
- end
6
+ base.send :include, Representable::Hash::Collection
7
+ base.send :include, Methods
10
8
  end
11
9
 
12
10
  module Methods
@@ -10,6 +10,7 @@ module Representable::XML
10
10
  base.class_eval do
11
11
  include Representable
12
12
  extend ClassMethods
13
+ representable_attrs.add(:_self, {:hash => true, :use_attributes => true})
13
14
  end
14
15
  end
15
16
 
@@ -19,11 +20,6 @@ module Representable::XML
19
20
  hash :_self, options.merge!(:use_attributes => true)
20
21
  end
21
22
  end
22
-
23
-
24
- def definition_opts
25
- [:_self, {:hash => true, :use_attributes => true}]
26
- end
27
23
  end
28
24
 
29
25
  module Hash
@@ -34,6 +30,7 @@ module Representable::XML
34
30
  base.class_eval do
35
31
  include Representable
36
32
  extend ClassMethods
33
+ representable_attrs.add(:_self, {:hash => true})
37
34
  end
38
35
  end
39
36
 
@@ -43,10 +40,5 @@ module Representable::XML
43
40
  hash :_self, options
44
41
  end
45
42
  end
46
-
47
-
48
- def definition_opts
49
- [:_self, {:hash => true}]
50
- end
51
43
  end
52
44
  end
@@ -7,27 +7,8 @@ module Representable
7
7
  def self.included(base)
8
8
  base.class_eval do
9
9
  include Representable
10
- extend ClassMethods
11
10
  #self.representation_wrap = true # let representable compute it.
12
- end
13
- end
14
-
15
-
16
- module ClassMethods
17
- # Creates a new Ruby object from XML using mapping information declared in the class.
18
- #
19
- # Accepts a block yielding the currently iterated Definition. If the block returns false
20
- # the property is skipped.
21
- #
22
- # Example:
23
- # band.from_xml("<band><name>Nofx</name></band>")
24
- def from_yaml(*args, &block)
25
- create_represented(*args, &block).from_yaml(*args)
26
- end
27
-
28
- private
29
- def representer_engine
30
- Representable::YAML
11
+ register_feature Representable::YAML
31
12
  end
32
13
  end
33
14
 
@@ -21,13 +21,13 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.add_dependency "nokogiri"
23
23
  s.add_dependency "multi_json"
24
- s.add_dependency "uber"
24
+ s.add_dependency "uber", "~> 0.0.7"
25
25
 
26
26
  s.add_development_dependency "rake"
27
27
  s.add_development_dependency "test_xml", ">= 0.1.6"
28
28
  s.add_development_dependency "minitest", "~> 5.0.0"
29
29
  s.add_development_dependency "mocha", ">= 0.13.0"
30
30
  s.add_development_dependency "mongoid"
31
- s.add_development_dependency "virtus", "~> 0.5.0"
31
+ s.add_development_dependency "virtus"
32
32
  s.add_development_dependency "json", '~> 1.7.7'
33
33
  end
@@ -39,21 +39,16 @@ class ClassTest < BaseTest
39
39
  end
40
40
 
41
41
 
42
- describe "lambda { nil }" do # TODO: remove in 2.0.
42
+ # this throws a DeserializationError now.
43
+ describe "lambda { nil }" do
43
44
  representer! do
44
45
  property :title, :class => nil
45
46
  end
46
47
 
47
- it "skips creating new instance" do
48
- object = Object.new
49
- object.instance_eval do
50
- def from_hash(hash, *args)
51
- hash
52
- end
48
+ it do
49
+ assert_raises Representable::DeserializeError do
50
+ OpenStruct.new.extend(representer).from_hash({"title" => {}})
53
51
  end
54
-
55
- song = OpenStruct.new.extend(representer).from_hash(hash = {"title" => object})
56
- song.title.must_equal object
57
52
  end
58
53
  end
59
54