representable 3.0.3 → 3.0.4

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 (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