representable 1.8.5 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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