representable 0.0.1.alpha1 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/README.rdoc +58 -153
  2. data/lib/representable.rb +18 -42
  3. data/lib/representable/bindings/json_bindings.rb +69 -0
  4. data/lib/representable/bindings/xml_bindings.rb +152 -0
  5. data/lib/representable/definition.rb +1 -12
  6. data/lib/representable/json.rb +66 -0
  7. data/lib/representable/version.rb +1 -1
  8. data/lib/representable/xml.rb +32 -38
  9. data/representable.gemspec +3 -2
  10. data/test/bindings_test.rb +110 -0
  11. data/test/json_test.rb +130 -0
  12. data/test/{roxml_test.rb → representable_test.rb} +28 -9
  13. data/test/test_helper.rb +2 -0
  14. data/test/xml_test.rb +192 -0
  15. metadata +32 -105
  16. data/History.txt +0 -354
  17. data/TODO +0 -37
  18. data/VERSION +0 -1
  19. data/examples/amazon.rb +0 -35
  20. data/examples/current_weather.rb +0 -27
  21. data/examples/dashed_elements.rb +0 -20
  22. data/examples/library.rb +0 -40
  23. data/examples/posts.rb +0 -27
  24. data/examples/rails.rb +0 -70
  25. data/examples/twitter.rb +0 -37
  26. data/examples/xml/active_record.xml +0 -70
  27. data/examples/xml/amazon.xml +0 -133
  28. data/examples/xml/current_weather.xml +0 -89
  29. data/examples/xml/dashed_elements.xml +0 -52
  30. data/examples/xml/posts.xml +0 -23
  31. data/examples/xml/twitter.xml +0 -422
  32. data/lib/representable/references.rb +0 -153
  33. data/spec/definition_spec.rb +0 -495
  34. data/spec/examples/active_record_spec.rb +0 -41
  35. data/spec/examples/amazon_spec.rb +0 -54
  36. data/spec/examples/current_weather_spec.rb +0 -37
  37. data/spec/examples/dashed_elements_spec.rb +0 -20
  38. data/spec/examples/library_spec.rb +0 -46
  39. data/spec/examples/post_spec.rb +0 -24
  40. data/spec/examples/twitter_spec.rb +0 -32
  41. data/spec/roxml_integration_test.rb +0 -289
  42. data/spec/roxml_spec.rb +0 -372
  43. data/spec/shared_specs.rb +0 -15
  44. data/spec/spec_helper.rb +0 -5
  45. data/spec/support/libxml.rb +0 -3
  46. data/spec/support/nokogiri.rb +0 -3
  47. data/spec/xml/array_spec.rb +0 -36
  48. data/spec/xml/attributes_spec.rb +0 -71
  49. data/spec/xml/encoding_spec.rb +0 -53
  50. data/spec/xml/namespace_spec.rb +0 -270
  51. data/spec/xml/namespaces_spec.rb +0 -67
  52. data/spec/xml/object_spec.rb +0 -82
  53. data/spec/xml/parser_spec.rb +0 -21
  54. data/spec/xml/text_spec.rb +0 -71
  55. data/test/fixtures/book_malformed.xml +0 -5
  56. data/test/fixtures/book_pair.xml +0 -8
  57. data/test/fixtures/book_text_with_attribute.xml +0 -5
  58. data/test/fixtures/book_valid.xml +0 -5
  59. data/test/fixtures/book_with_authors.xml +0 -7
  60. data/test/fixtures/book_with_contributions.xml +0 -9
  61. data/test/fixtures/book_with_contributors.xml +0 -7
  62. data/test/fixtures/book_with_contributors_attrs.xml +0 -7
  63. data/test/fixtures/book_with_default_namespace.xml +0 -9
  64. data/test/fixtures/book_with_depth.xml +0 -6
  65. data/test/fixtures/book_with_octal_pages.xml +0 -4
  66. data/test/fixtures/book_with_publisher.xml +0 -7
  67. data/test/fixtures/book_with_wrapped_attr.xml +0 -3
  68. data/test/fixtures/dictionary_of_attr_name_clashes.xml +0 -8
  69. data/test/fixtures/dictionary_of_attrs.xml +0 -6
  70. data/test/fixtures/dictionary_of_guarded_names.xml +0 -6
  71. data/test/fixtures/dictionary_of_mixeds.xml +0 -4
  72. data/test/fixtures/dictionary_of_name_clashes.xml +0 -10
  73. data/test/fixtures/dictionary_of_names.xml +0 -4
  74. data/test/fixtures/dictionary_of_texts.xml +0 -10
  75. data/test/fixtures/library.xml +0 -30
  76. data/test/fixtures/library_uppercase.xml +0 -30
  77. data/test/fixtures/muffins.xml +0 -3
  78. data/test/fixtures/nameless_ageless_youth.xml +0 -2
  79. data/test/fixtures/node_with_attr_name_conflicts.xml +0 -1
  80. data/test/fixtures/node_with_name_conflicts.xml +0 -4
  81. data/test/fixtures/numerology.xml +0 -4
  82. data/test/fixtures/person.xml +0 -1
  83. data/test/fixtures/person_with_guarded_mothers.xml +0 -13
  84. data/test/fixtures/person_with_mothers.xml +0 -10
  85. data/test/mocks/dictionaries.rb +0 -57
  86. data/test/mocks/mocks.rb +0 -279
  87. data/test/support/fixtures.rb +0 -11
  88. data/test/unit/definition_test.rb +0 -235
  89. data/test/unit/deprecations_test.rb +0 -24
  90. data/test/unit/to_xml_test.rb +0 -81
  91. data/test/unit/xml_attribute_test.rb +0 -39
  92. data/test/unit/xml_block_test.rb +0 -81
  93. data/test/unit/xml_bool_test.rb +0 -122
  94. data/test/unit/xml_convention_test.rb +0 -150
  95. data/test/unit/xml_hash_test.rb +0 -115
  96. data/test/unit/xml_initialize_test.rb +0 -49
  97. data/test/unit/xml_name_test.rb +0 -141
  98. data/test/unit/xml_namespace_test.rb +0 -31
  99. data/test/unit/xml_object_test.rb +0 -206
  100. data/test/unit/xml_required_test.rb +0 -94
  101. data/test/unit/xml_text_test.rb +0 -71
  102. data/website/index.html +0 -98
@@ -43,7 +43,7 @@ module Representable
43
43
  elsif opts[:from] == :namespace
44
44
  opts[:from] = '*'
45
45
  @sought_type = :namespace
46
- elsif opts[:from].to_s.starts_with?('@')
46
+ elsif opts[:from].to_s =~ /^@/ # FIXME: move me to xml.
47
47
  @sought_type = :attr
48
48
  opts[:from].sub!('@', '')
49
49
  end
@@ -65,7 +65,6 @@ module Representable
65
65
  sought_type.is_a?(Class)
66
66
  end
67
67
 
68
-
69
68
  def name?
70
69
  @name == '*'
71
70
  end
@@ -88,16 +87,6 @@ module Representable
88
87
 
89
88
  value
90
89
  end
91
-
92
- def to_ref
93
- case sought_type
94
- when :attr then XMLAttributeRef
95
- when :text then XMLTextRef
96
- when :namespace then XMLNameSpaceRef
97
- when Symbol then raise ArgumentError, "Invalid type argument #{sought_type}"
98
- else XMLObjectRef
99
- end.new(self)
100
- end
101
90
 
102
91
  private
103
92
  def extract_type(as)
@@ -0,0 +1,66 @@
1
+ require 'json'
2
+ require 'representable/bindings/json_bindings'
3
+
4
+ module Representable
5
+ module JSON
6
+ BINDING_FOR_TYPE = { # TODO: refactor #representable_accessor for better extendability.
7
+ :text => TextBinding,
8
+ }
9
+ def self.binding_for_definition(definition)
10
+ (BINDING_FOR_TYPE[definition.sought_type] or ObjectBinding).new(definition)
11
+ end
12
+
13
+ def self.included(base)
14
+ base.class_eval do
15
+ include Representable
16
+ include InstanceMethods
17
+ end
18
+ base.extend ClassMethods # DISCUSS: do that dynamically?
19
+ end
20
+
21
+ module ClassMethods
22
+ # Creates a new Ruby object from XML using mapping information declared in the class.
23
+ #
24
+ # Example:
25
+ # book = Book.from_xml("<book><name>Beyond Java</name></book>")
26
+ def from_json(data, options={})
27
+ # DISCUSS: extract #from_json call in Bindings to this place.
28
+ data = ::JSON[data] if data.is_a?(String) # DISCUSS: #from_json sometimes receives a string (in nestings).
29
+ data ||= {}
30
+
31
+ data = data[representation_name] unless options[:wrap] == false
32
+
33
+ create_from_json.tap do |inst|
34
+ refs = representable_attrs.map {|attr| JSON.binding_for_definition(attr) }
35
+
36
+ refs.each do |ref|
37
+ value = ref.value_in(data)
38
+
39
+ inst.send(ref.definition.setter, value)
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+ def create_from_json(*args)
46
+ new(*args)
47
+ end
48
+ end
49
+
50
+ module InstanceMethods # :nodoc:
51
+ # Returns a Nokogiri::XML object representing this object.
52
+ def to_json(options={})
53
+ attributes = {}.tap do |root|
54
+ refs = self.class.representable_attrs.map {|attr| JSON.binding_for_definition(attr) }
55
+
56
+ refs.each do |ref|
57
+ value = public_send(ref.accessor) # DISCUSS: eventually move back to Ref.
58
+ ref.update_json(root, value) if value
59
+ end
60
+ end
61
+
62
+ options[:wrap] == false ? attributes : {self.class.representation_name => attributes}
63
+ end
64
+ end
65
+ end # Xml
66
+ end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "0.0.1.alpha1"
2
+ VERSION = "0.0.1"
3
3
  end
@@ -1,50 +1,44 @@
1
+ require 'representable'
2
+ require 'representable/bindings/xml_bindings'
3
+
1
4
  module Representable
2
- module Xml
3
- module Declarations
4
- # Sets the name of the XML element that represents this class. Use this
5
- # to override the default lowercase class name.
6
- #
7
- # Example:
8
- # class BookWithPublisher
9
- # xml_name :book
10
- # end
11
- #
12
- # Without the xml_name annotation, the XML mapped tag would have been "bookwithpublisher".
13
- def xml_name(name)
14
- self.explicit_representation_name = name
15
- end
16
-
17
- def xml_accessor(*args) # TODO: remove me, just for back-compat.
18
- representable_accessor(*args)
5
+ module XML
6
+ BINDING_FOR_TYPE = {
7
+ :attr => AttributeBinding,
8
+ :text => TextBinding,
9
+ :namespace=> NamespaceBinding,
10
+ }
11
+
12
+ def self.binding_for_definition(definition)
13
+ (BINDING_FOR_TYPE[definition.sought_type] or ObjectBinding).new(definition)
14
+ end
15
+
16
+ def self.included(base)
17
+ base.class_eval do
18
+ include Representable
19
+ include InstanceMethods
20
+ extend ClassMethods
19
21
  end
20
-
22
+ end
23
+
24
+ class Definition < Representable::Definition
25
+ # FIXME: extract xml-specific from Definition.
21
26
  end
22
27
 
23
28
  module ClassMethods
29
+ def definition_class
30
+ Definition
31
+ end
32
+
33
+ # Creates a new Ruby object from XML using mapping information declared in the class.
24
34
  #
25
- # Creates a new Ruby object from XML using mapping information
26
- # annotated in the class.
27
- #
28
- # The input data is either an XML::Node, String, Pathname, or File representing
29
- # the XML document.
30
- #
31
- # Example
32
- # book = Book.from_xml(File.read("book.xml"))
33
- # or
35
+ # Example:
34
36
  # book = Book.from_xml("<book><name>Beyond Java</name></book>")
35
- #
36
- # _initialization_args_ passed into from_xml will be passed into
37
- # the object's .new, prior to populating the xml_attrs.
38
- #
39
- # After the instatiation and xml population
40
- #
41
- # See also: xml_initialize
42
- #
43
37
  def from_xml(data, *args)
44
38
  xml = Nokogiri::XML::Node.from(data)
45
39
 
46
40
  create_from_xml(*args).tap do |inst|
47
- refs = representable_attrs.map {|attr| attr.to_ref }
41
+ refs = representable_attrs.map {|attr| XML.binding_for_definition(attr) }
48
42
 
49
43
  refs.each do |ref|
50
44
  value = ref.value_in(xml)
@@ -61,12 +55,12 @@ module Representable
61
55
  end
62
56
 
63
57
  module InstanceMethods # :nodoc:
64
- # Returns an XML object representing this object
58
+ # Returns a Nokogiri::XML object representing this object.
65
59
  def to_xml(params={})
66
60
  params.reverse_merge!(:name => self.class.representation_name)
67
61
 
68
62
  Nokogiri::XML::Node.new(params[:name].to_s, Nokogiri::XML::Document.new).tap do |root|
69
- refs = self.class.representable_attrs.map {|attr| attr.to_ref }
63
+ refs = self.class.representable_attrs.map {|attr| XML.binding_for_definition(attr) }
70
64
 
71
65
  refs.each do |ref|
72
66
  value = public_send(ref.accessor) # DISCUSS: eventually move back to Ref.
@@ -12,17 +12,18 @@ Gem::Specification.new do |s|
12
12
  s.email = ["apotonick@gmail.com"]
13
13
  s.homepage = "http://representable.apotomo.de"
14
14
  s.summary = %q{Maps representation documents from and to Ruby objects. Includes XML and JSON support, plain properties and compositions.}
15
- s.description = %q{CMaps representation documents from and to Ruby objects. Includes XML and JSON support, plain properties and compositions.}
15
+ s.description = %q{Maps representation documents from and to Ruby objects. Includes XML and JSON support, plain properties and compositions.}
16
16
 
17
17
  s.files = `git ls-files`.split("\n")
18
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_dependency "activesupport", "~> 3.0.0"
22
+ s.add_dependency "activesupport"
23
23
  s.add_dependency "hooks"
24
24
  s.add_dependency "nokogiri"
25
25
  s.add_dependency "i18n"
26
+ s.add_dependency "json"
26
27
 
27
28
  s.add_development_dependency "rspec"
28
29
  s.add_development_dependency "test_xml"
@@ -0,0 +1,110 @@
1
+ require 'test_helper'
2
+
3
+ class ReferenceTest < MiniTest::Spec
4
+ describe "ObjectRef with []" do
5
+ before do
6
+ @ref = Representable::XML::ObjectBinding.new(Representable::Definition.new(:songs, :as => [Hash]))
7
+ end
8
+
9
+ it "responds to #default" do
10
+ assert_equal [], @ref.send(:default)
11
+ end
12
+ end
13
+
14
+
15
+ describe "TextRef#value_in" do
16
+ def parse_xml(xml); Nokogiri::XML::Node.from(xml); end
17
+
18
+ before do
19
+ @ref = Representable::XML::TextBinding.new(Representable::Definition.new(:song))
20
+ end
21
+
22
+ it "returns found value" do
23
+ assert_equal "Unkoil", @ref.value_in(parse_xml("<a><song>Unkoil</song></a>"))
24
+ end
25
+ end
26
+ end
27
+
28
+ class DefinitionTest < MiniTest::Spec
29
+ describe "generic API" do
30
+ before do
31
+ @def = Representable::Definition.new(:songs)
32
+ end
33
+
34
+ it "responds to #typed?" do
35
+ assert ! @def.typed?
36
+ assert Representable::Definition.new(:songs, :as => Hash).typed?
37
+ assert Representable::Definition.new(:songs, :as => [Hash]).typed?
38
+ end
39
+ end
40
+
41
+
42
+ describe "#apply" do
43
+ it "works with a single item" do
44
+ @d = Representable::Definition.new(:song)
45
+ assert_equal 2, @d.apply(1) { |v| v+1 }
46
+ end
47
+
48
+ it "works with collection" do
49
+ @d = Representable::Definition.new(:song, :as => [])
50
+ assert_equal [2,3,4], @d.apply([1,2,3]) { |v| v+1 }
51
+ end
52
+
53
+ it "skips with collection and nil" do
54
+ @d = Representable::Definition.new(:song, :as => [])
55
+ assert_equal nil, @d.apply(nil) { |v| v+1 }
56
+ end
57
+ end
58
+
59
+ describe ":as => []" do
60
+ before do
61
+ @def = Representable::Definition.new(:songs, :as => [], :tag => :song)
62
+ end
63
+
64
+ it "responds to #accessor" do
65
+ assert_equal "songs", @def.accessor
66
+ end
67
+
68
+ it "responds to #array?" do
69
+ assert @def.array?
70
+ end
71
+
72
+ it "responds to #name" do
73
+ assert_equal "songs", @def.accessor
74
+ end
75
+
76
+ it "responds to #instance_variable_name" do
77
+ assert_equal :"@songs", @def.instance_variable_name
78
+ end
79
+
80
+ it "responds to #setter" do
81
+ assert_equal :"songs=", @def.setter
82
+ end
83
+
84
+ it "responds to #sought_type" do
85
+ assert_equal :text, @def.sought_type
86
+ end
87
+ end
88
+
89
+
90
+ describe ":as => [Item]" do
91
+ before do
92
+ @def = Representable::Definition.new(:songs, :as => [Hash])
93
+ end
94
+
95
+ it "responds to #sought_type" do
96
+ assert_equal Hash, @def.sought_type
97
+ end
98
+ end
99
+
100
+
101
+ describe ":as => Item" do
102
+ before do
103
+ @def = Representable::Definition.new(:songs, :as => Hash)
104
+ end
105
+
106
+ it "responds to #sought_type" do
107
+ assert_equal Hash, @def.sought_type
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,130 @@
1
+ require 'test_helper'
2
+ require 'representable/json'
3
+
4
+ module JsonTest
5
+ class APITest < MiniTest::Spec
6
+ Json = Representable::JSON
7
+ Def = Representable::Definition
8
+
9
+ describe "Xml module" do
10
+ describe "#binding_for_definition" do
11
+ it "returns ObjectBinding" do
12
+ assert_kind_of Json::ObjectBinding, Json.binding_for_definition(Def.new(:band, :as => Hash))
13
+ end
14
+
15
+ it "returns TextBinding" do
16
+ assert_kind_of Json::TextBinding, Json.binding_for_definition(Def.new(:band))
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ class PropertyTest < MiniTest::Spec
23
+ describe "property :name" do
24
+ class Band
25
+ include Representable::JSON
26
+ representable_property :name
27
+ end
28
+
29
+ it "#from_json creates correct accessors" do
30
+ band = Band.from_json({:band => {:name => "Bombshell Rocks"}}.to_json)
31
+ assert_equal "Bombshell Rocks", band.name
32
+ end
33
+
34
+ it "#to_json serializes correctly" do
35
+ band = Band.new
36
+ band.name = "Cigar"
37
+
38
+ assert_equal "{\"band\"=>{\"name\"=>\"Cigar\"}}", band.to_json.to_s
39
+ end
40
+ end
41
+
42
+ describe "property :name, :as => []" do
43
+ class CD
44
+ include Representable::JSON
45
+ representable_property :songs, :as => []
46
+ end
47
+
48
+ it "#from_json creates correct accessors" do
49
+ cd = CD.from_json({:cd => {:songs => ["Out in the cold", "Microphone"]}}.to_json)
50
+ assert_equal ["Out in the cold", "Microphone"], cd.songs
51
+ end
52
+
53
+ it "#to_json serializes correctly" do
54
+ cd = CD.new
55
+ cd.songs = ["Out in the cold", "Microphone"]
56
+
57
+ assert_equal "{\"cd\"=>{\"songs\"=>[\"Out in the cold\", \"Microphone\"]}}", cd.to_json.to_s
58
+ end
59
+ end
60
+ end
61
+
62
+ class TypedPropertyTest < MiniTest::Spec
63
+ describe ":as => Item" do
64
+ class Label
65
+ include Representable::JSON
66
+ representable_property :name
67
+ end
68
+
69
+ class Album
70
+ include Representable::JSON
71
+ representable_property :label, :as => Label
72
+ end
73
+
74
+ it "#from_json creates one Item instance" do
75
+ album = Album.from_json({:album => {:label => "Fat Wreck"}}.to_json)
76
+ assert_equal "Bad Religion", album.label.name
77
+ end
78
+
79
+ describe "#to_json" do
80
+ it "serializes" do
81
+ label = Label.new; label.name = "Fat Wreck"
82
+ album = Album.new; album.label = label
83
+
84
+ assert_equal "{\"album\"=>{\"label\"=>{\"name\"=>\"Fat Wreck\"}}}", album.to_json.to_s
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+
91
+ class CollectionTest < MiniTest::Spec
92
+ describe ":as => [Band]" do
93
+ class Band
94
+ include Representable::JSON
95
+ representable_property :name
96
+
97
+ def initialize(name="")
98
+ self.name = name
99
+ end
100
+ end
101
+
102
+ class Compilation
103
+ include Representable::JSON
104
+ representable_property :bands, :as => [Band]
105
+ end
106
+
107
+ describe "#from_json" do
108
+ it "pushes collection items to array" do
109
+ cd = Compilation.from_json({:compilation => {:bands => [
110
+ {:name => "Cobra Skulls"},
111
+ {:name => "Diesel Boy"}]}}.to_json)
112
+ assert_equal ["Cobra Skulls", "Diesel Boy"], cd.bands.map(&:name).sort
113
+ end
114
+
115
+ it "collections can be empty" do
116
+ cd = Compilation.from_json({:compilation => {}}.to_json)
117
+ assert_equal [], cd.bands
118
+ end
119
+ end
120
+
121
+ it "responds to #to_json" do
122
+ cd = Compilation.new
123
+ cd.bands = [Band.new("Diesel Boy"), Band.new("Bad Religion")]
124
+
125
+ assert_equal "{\"compilation\"=>{\"bands\"=>[{\"name\"=>\"Diesel Boy\"}, {\"name\"=>\"Bad Religion\"}]}}", cd.to_json.to_s
126
+ end
127
+ end
128
+
129
+ end
130
+ end