representable 3.0.3 → 3.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -1
  3. data/CHANGES.md +5 -0
  4. data/README.md +1 -1
  5. data/TODO +1 -3
  6. data/TODO-4.0.md +72 -0
  7. data/lib/representable/declarative.rb +3 -3
  8. data/lib/representable/deserializer.rb +1 -1
  9. data/lib/representable/serializer.rb +1 -1
  10. data/lib/representable/version.rb +1 -1
  11. data/lib/representable/xml.rb +6 -4
  12. data/lib/representable/xml/binding.rb +19 -12
  13. data/lib/representable/xml/namespace.rb +122 -0
  14. data/representable.gemspec +2 -2
  15. data/test/as_test.rb +2 -2
  16. data/test/binding_test.rb +7 -7
  17. data/test/cached_test.rb +13 -13
  18. data/test/class_test.rb +2 -2
  19. data/test/coercion_test.rb +1 -1
  20. data/test/config_test.rb +5 -5
  21. data/test/decorator_scope_test.rb +1 -1
  22. data/test/decorator_test.rb +8 -8
  23. data/test/default_test.rb +1 -1
  24. data/test/defaults_options_test.rb +3 -3
  25. data/test/definition_test.rb +9 -11
  26. data/test/examples/object.rb +1 -5
  27. data/test/exec_context_test.rb +2 -2
  28. data/test/features_test.rb +3 -3
  29. data/test/filter_test.rb +2 -2
  30. data/test/for_collection_test.rb +8 -8
  31. data/test/generic_test.rb +11 -11
  32. data/test/hash_bindings_test.rb +1 -1
  33. data/test/hash_test.rb +13 -13
  34. data/test/heritage_test.rb +16 -13
  35. data/test/if_test.rb +3 -3
  36. data/test/include_exclude_test.rb +2 -2
  37. data/test/inherit_test.rb +3 -3
  38. data/test/inline_test.rb +13 -13
  39. data/test/instance_test.rb +2 -2
  40. data/test/json_test.rb +4 -6
  41. data/test/lonely_test.rb +15 -15
  42. data/test/nested_test.rb +6 -6
  43. data/test/object_test.rb +4 -4
  44. data/test/parse_pipeline_test.rb +0 -2
  45. data/test/pipeline_test.rb +7 -7
  46. data/test/populator_test.rb +7 -7
  47. data/test/prepare_test.rb +2 -2
  48. data/test/represent_test.rb +10 -10
  49. data/test/representable_test.rb +7 -7
  50. data/test/schema_test.rb +3 -6
  51. data/test/skip_test.rb +6 -6
  52. data/test/test_helper.rb +16 -6
  53. data/test/wrap_test.rb +8 -8
  54. data/test/xml_namespace_test.rb +186 -0
  55. data/test/xml_test.rb +53 -34
  56. data/test/yaml_test.rb +11 -11
  57. metadata +9 -7
  58. data/lib/representable/TODO.getting_serious +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c7190988a0107b8fdf04b353f9e4ce2839edbc63
4
- data.tar.gz: 4822a18cbbccd0b1953d24223dba3adcbfc7bd43
3
+ metadata.gz: 04ca3309b8af78dd1eaa8b4d47c457323180a881
4
+ data.tar.gz: c7d43dc372fc53576a6b64f22d3fc0bd1bff5a4e
5
5
  SHA512:
6
- metadata.gz: 6f58b1085876a04fc2ebeb076d94323b1ad45039cfb987693ebc9ddd957bdb367c02b5b64792e570981fe549eafae4c5390886613992187f5106b1d380c079ab
7
- data.tar.gz: db17d2feb4dfe80e37c2a3934218ef6101478fa1c4ab167cd9876710de4a9a30c8798a8914f9f9ff95cdf6642c7fbe8b650ae8d46855f642e364c1badeb88bf4
6
+ metadata.gz: 528696ff8d8eae7b409566d9831a0117a90a361fdabe5a0345d0e5accac5cfbb2f3bfae5a34589000b5bdbf2632f7951356861215d210172c602b581694e68dc
7
+ data.tar.gz: 4d7409a62a9a25d31fc1c67d20e2b32b9cd0a1d8e9987b2cbe118bd4e069fe271dd82127f3e7656b92f01e8f1266524965def4089e439b4a1eac370e13a06bff
@@ -11,4 +11,6 @@ rvm:
11
11
  matrix:
12
12
  include:
13
13
  - rvm: jruby-9.1.5.0
14
- env: JRUBY_OPTS="--profile.api"
14
+ env: JRUBY_OPTS="--profile.api"
15
+ allow_failures:
16
+ - rvm: jruby-9.1.5.0
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 3.0.4
2
+
3
+ * Add proper XML namespace support.
4
+ * [internal] Replace `XML::Binding#node_for` with function `XML::Node`.
5
+
1
6
  # 3.0.3
2
7
 
3
8
  * Replace `Uber::Option` with the new [`Declarative::Option`](https://github.com/apotonick/declarative-option). This should result in a significant performance boost.
data/README.md CHANGED
@@ -5,7 +5,7 @@ Representable maps Ruby objects to documents and back.
5
5
  [![Gitter Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://gitter.im/trailblazer/chat)
6
6
  [![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/)
7
7
  [![Build
8
- Status](https://travis-ci.org/apotonick/representable.svg)](https://travis-ci.org/apotonick/representable)
8
+ Status](https://travis-ci.org/trailblazer/representable.svg)](https://travis-ci.org/trailblazer/representable)
9
9
  [![Gem Version](https://badge.fury.io/rb/representable.svg)](http://badge.fury.io/rb/representable)
10
10
 
11
11
  In other words: Take an object and decorate it with a representer module. This will allow you to render a JSON, XML or YAML document from that object. But that's only half of it! You can also use representers to parse a document and create or populate an object.
data/TODO CHANGED
@@ -1,8 +1,6 @@
1
1
  * Pass key/index as first block arg to :class and :extend
2
2
  class: |key, hsh|
3
3
 
4
- * Allow passing options to Binding#serialize.
5
- serialize(.., options{:exclude => ..})
6
4
 
7
5
  document `XML::AttributeHash` etc
8
6
 
@@ -37,4 +35,4 @@ module ReaderWriter
37
35
  * DISCUSS: should inline representers be created at runtime, so we don't need ::representer_engine?
38
36
  * deprecate `Decorator::Coercion`.
39
37
 
40
- * cleanup XML so it matches the current #serialize standard.
38
+ * cleanup XML so it matches the current #serialize standard.
@@ -0,0 +1,72 @@
1
+ # Decorator
2
+
3
+ XML::Binding::Collection.to_xml(represented)
4
+ bindings.each bin.to_xml
5
+
6
+
7
+ # hat vorteil: [].each{ Collection.to_xml(item) }
8
+
9
+ * make all properties "Object-like", even arrays of strings etc. This saves us from having `extend ObjectBinding if typed?` and we could just call to_hash/from_hash on all attributes. performance issues here? otherwise: implement!
10
+
11
+
12
+ # how to?
13
+
14
+ class CH
15
+ wrap :character
16
+ prpoerty :a
17
+
18
+
19
+ class
20
+ proerty :author, dec: CH
21
+
22
+ # how to?
23
+
24
+ * override specific bindings and their logic? e.g. `Namespace#read`
25
+ * Extend nested representers, e.g. the namespace prefix, when it gets plugged into composition
26
+ * Easier polymorphic representer
27
+
28
+ # XML
29
+
30
+ * ditch annoying nokogiri in favor of https://github.com/YorickPeterse/oga
31
+
32
+ # Parsing
33
+
34
+ * Let bindings have any "xpath"
35
+ * Allow to parse "wildcard" sections where you have no idea about the property names (and attribute names, eg. with links)
36
+
37
+ # Options
38
+
39
+ * There should be an easier way to pass a set of options to all nested #to_node decorators.
40
+
41
+ ```ruby
42
+ representable_attrs.keys.each do |property|
43
+ options[property.to_sym] = { show_definition: false, namespaces: options[:namespaces] }
44
+ end
45
+ ```
46
+
47
+ * Allow passing options to Binding#serialize.
48
+ serialize(.., options{:exclude => ..})
49
+
50
+
51
+ # wrap, as
52
+
53
+ AsWithNamespace( Binding )
54
+ BUT NOT FOR AsWithNamespace( Binding::Attribute )
55
+ => selectively wrap bindings at compile- and runtime
56
+
57
+
58
+
59
+
60
+
61
+
62
+ * Cleanup the manifest part in Decorator.
63
+
64
+ * all property objects should be extended/wrapped so we don't need the switch.
65
+
66
+ # Deprecations
67
+
68
+ * deprecate instance: { nil } which is superseded by parse_strategy: :sync
69
+
70
+
71
+
72
+ from_hash, property :band, class: vergessen
@@ -47,10 +47,10 @@ module Representable
47
47
  NestedBuilder = ->(options) do
48
48
  Module.new do
49
49
  include Representable # FIXME: do we really need this?
50
- feature *options[:_features]
51
- include *options[:_base] # base when :inherit, or in decorator.
50
+ feature(*options[:_features])
51
+ include(*options[:_base]) # base when :inherit, or in decorator.
52
52
 
53
- module_eval &options[:_block]
53
+ module_eval(&options[:_block])
54
54
  end
55
55
  end
56
56
 
@@ -96,7 +96,7 @@ 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!
@@ -51,4 +51,4 @@ module Representable
51
51
  # Warning: don't rely on AssignAs/AssignName, i am not sure if i leave that as functions.
52
52
  AssignAs = ->(input, options) { options[:as] = As.(input, options); input }
53
53
  AssignName = ->(input, options) { options[:as] = options[:binding].name; input }
54
- end
54
+ end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "3.0.3"
2
+ VERSION = "3.0.4"
3
3
  end
@@ -1,6 +1,4 @@
1
1
  require 'representable'
2
- require 'representable/xml/binding'
3
- require 'representable/xml/collection'
4
2
 
5
3
  begin
6
4
  require 'nokogiri'
@@ -46,10 +44,10 @@ module Representable
46
44
 
47
45
  # Returns a Nokogiri::XML object representing this object.
48
46
  def to_node(options={})
49
- options[:doc] ||= Nokogiri::XML::Document.new
47
+ options[:doc] = Nokogiri::XML::Document.new # DISCUSS: why do we need a fresh Document here?
50
48
  root_tag = options[:wrap] || representation_wrap(options)
51
49
 
52
- create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, options[:doc]), options, Binding)
50
+ create_representation_with(Node(options[:doc], root_tag.to_s), options, Binding)
53
51
  end
54
52
 
55
53
  def to_xml(*args)
@@ -73,3 +71,7 @@ module Representable
73
71
  end
74
72
  end
75
73
  end
74
+
75
+ require "representable/xml/binding"
76
+ require "representable/xml/collection"
77
+ require "representable/xml/namespace"
@@ -3,6 +3,14 @@ require 'representable/hash/binding.rb'
3
3
 
4
4
  module Representable
5
5
  module XML
6
+ module_function
7
+ def Node(document, name, attributes={})
8
+ node = Nokogiri::XML::Node.new(name.to_s, document) # Java::OrgW3cDom::DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.
9
+
10
+ attributes.each { |k, v| node[k] = v } # TODO: benchmark.
11
+ node
12
+ end
13
+
6
14
  class Binding < Representable::Binding
7
15
  def self.build_for(definition)
8
16
  return Collection.new(definition) if definition.array?
@@ -17,7 +25,7 @@ module Representable
17
25
  wrap_node = parent
18
26
 
19
27
  if wrap = self[:wrap]
20
- parent << wrap_node = node_for(parent, wrap)
28
+ parent << wrap_node = XML::Node(parent, wrap)
21
29
  end
22
30
 
23
31
  wrap_node << serialize_for(fragments, parent, as)
@@ -32,12 +40,15 @@ module Representable
32
40
 
33
41
  # Creates wrapped node for the property.
34
42
  def serialize_for(value, parent, as)
35
- node = node_for(parent, as)
36
- serialize_node(node, value)
43
+ node = XML::Node(parent, as) # node doesn't have attr="" attributes!!!
44
+ serialize_node(node, value, as)
37
45
  end
38
46
 
39
- def serialize_node(node, value)
40
- return value if typed?
47
+ def serialize_node(node, value, as)
48
+ if typed?
49
+ value.name = as if as != self[:name]
50
+ return value
51
+ end
41
52
 
42
53
  node.content = value
43
54
  node
@@ -60,11 +71,7 @@ module Representable
60
71
  def find_nodes(doc, as)
61
72
  selector = as
62
73
  selector = "#{self[:wrap]}/#{as}" if self[:wrap]
63
- nodes = doc.xpath(selector)
64
- end
65
-
66
- def node_for(parent, name)
67
- Nokogiri::XML::Node.new(name.to_s, parent.document)
74
+ doc.xpath(selector) # nodes
68
75
  end
69
76
 
70
77
  def content_for(node) # TODO: move this into a ScalarDecorator.
@@ -100,8 +107,8 @@ module Representable
100
107
  class Hash < Collection
101
108
  def serialize_for(value, parent, as)
102
109
  set_for(parent, value.collect do |k, v|
103
- node = node_for(parent, k)
104
- serialize_node(node, v)
110
+ node = XML::Node(parent, k)
111
+ serialize_node(node, v, as)
105
112
  end)
106
113
  end
107
114
 
@@ -0,0 +1,122 @@
1
+ module Representable::XML
2
+ # Experimental!
3
+ # Best explanation so far: http://books.xmlschemata.org/relaxng/relax-CHP-11-SECT-1.html
4
+ #
5
+ # Note: This module doesn't work with JRuby because Nokogiri uses a completely
6
+ # different implementation in Java which has other requirements that we couldn't fulfil.
7
+ # Please wait for Representable 4 where we replace Nokogiri with Oga.
8
+ module Namespace
9
+ def self.included(includer)
10
+ includer.extend(DSL)
11
+ end
12
+
13
+ module DSL
14
+ def namespace(namespace)
15
+ representable_attrs.options[:local_namespace] = namespace
16
+ representable_attrs.options[:namespace_mappings] ||= {}
17
+ representable_attrs.options[:namespace_mappings][namespace] = nil # this might get overwritten via #namespace_def later.
18
+ end
19
+
20
+ def namespace_def(mapping)
21
+ namespace_defs.merge!(mapping.invert)
22
+ end
23
+
24
+ # :private:
25
+ def namespace_defs
26
+ representable_attrs.options[:namespace_mappings] ||= {}
27
+ end
28
+
29
+ def property(name, options={})
30
+ uri = representable_attrs.options[:local_namespace] # per default, a property belongs to the local namespace.
31
+ options[:namespace] ||= uri # don't override if already set.
32
+
33
+ # a nested representer is automatically assigned "its" local namespace. It's like saying
34
+ # property :author, namespace: "http://ns/author" do ... end
35
+
36
+ super.tap do |dfn|
37
+ if dfn.typed? # FIXME: ouch, this should be doable with property's API to hook into the creation process.
38
+ dfn.merge!( namespace: dfn.representer_module.representable_attrs.options[:local_namespace] )
39
+
40
+ update_namespace_defs!(namespace_defs)
41
+ end
42
+ end
43
+ end
44
+
45
+ # :private:
46
+ # super ugly hack
47
+ # recursively injects the namespace_defs into all representers of this tree. will be done better in 4.0.
48
+ def update_namespace_defs!(namespace_defs)
49
+ representable_attrs.each do |dfn|
50
+ dfn.merge!(namespace_defs: namespace_defs) # this only helps with scalars
51
+
52
+ if dfn.typed?
53
+ representer = Class.new(dfn.representer_module) # don't pollute classes.
54
+ representer.update_namespace_defs!(namespace_defs)
55
+ dfn.merge!(extend: representer)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ module AsWithNamespace
62
+ def write(doc, fragment, as)
63
+ super(doc, fragment, prefixed(self, as))
64
+ end
65
+
66
+ # FIXME: this is shit, the NestedOptions is executed too late here!
67
+ def read(node, as)
68
+ super(node, prefixed(self, as))
69
+ end
70
+
71
+ private
72
+ def prefixed(dfn, as)
73
+ uri = dfn[:namespace] # this is generic behavior and per property
74
+ prefix = dfn[:namespace_defs][uri]
75
+ as = Namespace::Namespaced(prefix, as)
76
+ end
77
+ end
78
+
79
+ # FIXME: some "bug" in Representable's XML doesn't consider the container tag, so we could theoretically pick the
80
+ # wrong namespaced tag here :O
81
+ def from_node(node, options={})
82
+ super
83
+ end
84
+
85
+ def to_node(options={})
86
+ local_uri = representable_attrs.options[:local_namespace] # every decorator MUST have a local namespace.
87
+ prefix = self.class.namespace_defs[local_uri]
88
+
89
+ root_tag = [prefix, representation_wrap(options)].compact.join(":")
90
+
91
+ options = { wrap: root_tag }.merge(options)
92
+
93
+ # TODO: there should be an easier way to pass a set of options to all nested #to_node decorators.
94
+ representable_attrs.keys.each do |property|
95
+ options[property.to_sym] = { show_definition: false, namespaces: options[:namespaces] }
96
+ end
97
+
98
+ super(options).tap do |node|
99
+ add_namespace_definitions!(node, self.class.namespace_defs) unless options[:show_definition] == false
100
+ end
101
+ end
102
+
103
+ # "Physically" add `xmlns` attributes to `node`.
104
+ def add_namespace_definitions!(node, namespaces)
105
+ namespaces.each do |uri, prefix|
106
+ prefix = prefix.nil? ? nil : prefix.to_s
107
+ node.add_namespace_definition(prefix, uri)
108
+ end
109
+ end
110
+
111
+ def self.Namespaced(prefix, name)
112
+ [ prefix, name ].compact.join(":")
113
+ end
114
+
115
+ # FIXME: this is a PoC, we need a better API to inject code.
116
+ def representable_map(options, format)
117
+ super.tap do |map|
118
+ map.each { |bin| bin.extend(AsWithNamespace) unless bin.is_a?(Binding::Attribute) }
119
+ end
120
+ end
121
+ end
122
+ end
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.platform = Gem::Platform::RUBY
10
10
  spec.authors = ["Nick Sutterer"]
11
11
  spec.email = ["apotonick@gmail.com"]
12
- spec.homepage = "https://github.com/apotonick/representable/"
12
+ spec.homepage = "https://github.com/trailblazer/representable/"
13
13
  spec.summary = %q{Renders and parses JSON/XML/YAML documents from and to Ruby objects. Includes plain properties, collections, nesting, coercion and more.}
14
14
  spec.description = spec.summary
15
15
 
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency "declarative-option", "< 0.2.0"
27
27
 
28
28
  spec.add_development_dependency "rake"
29
- spec.add_development_dependency "test_xml", "0.1.6"
29
+ spec.add_development_dependency "test_xml", ">= 0.1.6"
30
30
  spec.add_development_dependency "minitest"
31
31
  spec.add_development_dependency "virtus"
32
32
  spec.add_development_dependency "multi_json"
@@ -7,8 +7,8 @@ class AsTest < MiniTest::Spec
7
7
  # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"],
8
8
  ) do |format, mod, input, output|
9
9
 
10
- let (:song) { representer.prepare(Song.new("Revolution")) }
11
- let (:format) { format }
10
+ let(:song) { representer.prepare(Song.new("Revolution")) }
11
+ let(:format) { format }
12
12
 
13
13
 
14
14
  describe "as: with :symbol" do
@@ -2,10 +2,10 @@ require 'test_helper'
2
2
 
3
3
  class BindingTest < MiniTest::Spec
4
4
  Binding = Representable::Binding
5
- let (:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) }
5
+ let(:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) }
6
6
 
7
7
  describe "#skipable_empty_value?" do
8
- let (:binding) { Binding.new(render_nil_definition) }
8
+ let(:binding) { Binding.new(render_nil_definition) }
9
9
 
10
10
  # don't skip when present.
11
11
  it { binding.skipable_empty_value?("Disconnect, Disconnect").must_equal false }
@@ -22,8 +22,8 @@ class BindingTest < MiniTest::Spec
22
22
 
23
23
 
24
24
  describe "#default_for" do
25
- let (:definition) { Representable::Definition.new(:song, :default => "Insider") }
26
- let (:binding) { Binding.new(definition) }
25
+ let(:definition) { Representable::Definition.new(:song, :default => "Insider") }
26
+ let(:binding) { Binding.new(definition) }
27
27
 
28
28
  # return value when value present.
29
29
  it { binding.default_for("Black And Blue").must_equal "Black And Blue" }
@@ -35,12 +35,12 @@ class BindingTest < MiniTest::Spec
35
35
  it { binding.default_for(nil).must_equal "Insider" }
36
36
 
37
37
  # return nil when value nil and render_nil: true.
38
- it { Binding.new(render_nil_definition).default_for(nil).must_equal nil }
38
+ it { Binding.new(render_nil_definition).default_for(nil).must_be_nil }
39
39
 
40
40
  # return nil when value nil and render_nil: true, even when :default is set" do
41
- it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")).default_for(nil).must_equal nil }
41
+ it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")).default_for(nil).must_be_nil }
42
42
 
43
43
  # return nil if no :default
44
- it { Binding.new(Representable::Definition.new(:song)).default_for(nil).must_equal nil }
44
+ it { Binding.new(Representable::Definition.new(:song)).default_for(nil).must_be_nil }
45
45
  end
46
46
  end