dozuki-mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,13 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ root = File.dirname(__FILE__)
3
+ at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m|
4
+ ["spec/#{m[1]}_spec.rb"]
5
+ }
6
+ at.add_mapping(%r%^spec/.*_spec\.rb$%) { |filename|
7
+ filename
8
+ }
9
+ at.add_mapping(%r%^spec/support/.*\.rb$%) { |_|
10
+ Dir[File.join(root, 'spec/**/*_spec.rb')]
11
+ }
12
+ nil
13
+ end
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dozuki-mapper.gemspec
4
+ gemspec
data/README.markdown ADDED
@@ -0,0 +1,132 @@
1
+ # Dozuki Mapper
2
+
3
+ A simple DSL for mapping xml documents directly to user-defined classes.
4
+ Ideal for API parsing.
5
+
6
+ ## What does it do?
7
+
8
+ You've got an XML API and you want to model the output in Ruby objects,
9
+ parse values to primitives and build a structure. You don't want to write lots of code or XPaths or repeat yourself.
10
+ Dozuki-mapper, built on the Dozuki gem, lets you define the mapping
11
+ between the XML and the objects, but does little more, meaning you
12
+ have control over the definition of your class. It is currently only
13
+ suited to reasonably direct mappings as it's being used to model API
14
+ responses.
15
+
16
+ Features can be found in the [features
17
+ folder](https://github.com/jamesalmond/dozuki-mapper/tree/master/features).
18
+
19
+ It's built using the [dozuki gem](https://github.com/jamesalmond/dozuki)
20
+
21
+ # How do I use it?
22
+
23
+ xml = %Q{
24
+ <person>
25
+ <name>John Smith</name>
26
+ <address>1 Main Street</address>
27
+ <post_code>NW1 3ED</post_code>
28
+ </person>
29
+ }
30
+
31
+ class Person
32
+ include Dozuki::Mapper
33
+
34
+ attr_accessor :name, :address, :post_code
35
+
36
+ map_with do |map|
37
+ map.string :name
38
+ map.string :address
39
+ map.string :post_code
40
+ end
41
+ end
42
+
43
+
44
+ doc = Dozuki::XML.parse(xml)
45
+
46
+ person = Person.from_node(doc.get('./person')
47
+
48
+ Setting the relevant fields on the person object. More conversions (e.g.
49
+ integer, float, date) to come.
50
+
51
+ ## Mapping data types
52
+
53
+ * [String](https://github.com/jamesalmond/dozuki-mapper/tree/master/features/string_mapping.feature)
54
+ * [Float](https://github.com/jamesalmond/dozuki-mapper/tree/master/features/float_mapping.feature)
55
+ * [Int](https://github.com/jamesalmond/dozuki-mapper/tree/master/features/int_mapping.feature)
56
+ * [Date](https://github.com/jamesalmond/dozuki-mapper/tree/master/features/date_mapping.feature)
57
+
58
+ ## Mapping object hierarchies
59
+
60
+
61
+ * [Mapping nodes](https://github.com/jamesalmond/dozuki-mapper/tree/master/features/node_mapping.feature)
62
+
63
+ Example:
64
+
65
+ xml = %Q{
66
+ <plot>
67
+ <name>Tiny Plot</name>
68
+ <dimensions>
69
+ <width>100</width>
70
+ <depth>200</depth>
71
+ </dimensions>
72
+ </plot>
73
+ }
74
+
75
+ class Plot
76
+ include Dozuki::Mapper
77
+ attr_accessor :name, :dimensions
78
+ map_with do |map|
79
+ map.string :name
80
+ map.node :dimensions, :as => Dimensions
81
+ end
82
+ end
83
+
84
+ class Dimensions
85
+ include Dozuki::Mapper
86
+ attr_accessor :width, :height
87
+ map_with do |map|
88
+ map.int :width
89
+ map.int :height
90
+ end
91
+ end
92
+
93
+
94
+ ## Mapping collections
95
+
96
+ * [Mapping each](https://github.com/jamesalmond/dozuki-mapper/tree/master/features/each_mapping.feature)
97
+
98
+
99
+ ## Contributing to Dozuki Mapper
100
+
101
+ * Fork the project.
102
+ * Add tests that cover the new feature or bug fix.
103
+ * Make your feature addition or bug fix.
104
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
105
+ * Send me a pull request. Bonus points for topic branches.
106
+
107
+ ## LICENSE:
108
+
109
+ (The MIT License)
110
+
111
+ Copyright (c) 2011:
112
+
113
+ * [James Almond](http://jamesalmond.com)
114
+
115
+ Permission is hereby granted, free of charge, to any person obtaining
116
+ a copy of this software and associated documentation files (the
117
+ 'Software'), to deal in the Software without restriction, including
118
+ without limitation the rights to use, copy, modify, merge, publish,
119
+ distribute, sublicense, and/or sell copies of the Software, and to
120
+ permit persons to whom the Software is furnished to do so, subject to
121
+ the following conditions:
122
+
123
+ The above copyright notice and this permission notice shall be
124
+ included in all copies or substantial portions of the Software.
125
+
126
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
127
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
128
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
129
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
130
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
131
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
132
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec'
5
+ require 'rspec/core/rake_task'
6
+ require 'cucumber'
7
+ require 'cucumber/rake/task'
8
+
9
+
10
+
11
+ desc "Run specs"
12
+ RSpec::Core::RakeTask.new :spec
13
+
14
+ desc "Run integrations"
15
+ Cucumber::Rake::Task.new(:features) do |t|
16
+ t.cucumber_opts = "features --format pretty"
17
+ end
18
+ task :default => [:spec, :features]
19
+
20
+
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dozuki-mapper/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dozuki-mapper"
7
+ s.version = Dozuki::Mapper::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["James Almond"]
10
+ s.email = ["james@jamesalmond.com"]
11
+ s.homepage = "https://github.com/jamesalmond/dozuki-mapper"
12
+ s.summary = %q{A DSL for mapping API output to objects}
13
+ s.description = %q{A DSL for mapping API output to objects}
14
+
15
+ s.rubyforge_project = "dozuki-mapper"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {spec,features}/* .autotest`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("dozuki")
23
+
24
+ s.add_development_dependency("rspec")
25
+ s.add_development_dependency("cucumber")
26
+ s.add_development_dependency("autotest")
27
+ s.add_development_dependency("rake")
28
+ end
@@ -0,0 +1,25 @@
1
+ Feature: mapping a document with date elements to an object
2
+ So that I don't have to spend all my time typing in repetitive XPaths
3
+ As an API consumer
4
+ I want to map a document to an object
5
+
6
+
7
+ Scenario: I have a simple mapping
8
+ Given I have an event with start_date, end_date and sold_out
9
+ And I have the XML:
10
+ """
11
+ <event>
12
+ <start_date>2011-02-01</start_date>
13
+ <end_date>2011-05-13</end_date>
14
+ <sold_out>2010-12-25</sold_out>
15
+ </event>
16
+ """
17
+ When I map the event node to an event object with:
18
+ """
19
+ map.date :start_date
20
+ map.date :end_date
21
+ map.date :sold_out
22
+ """
23
+ Then the event should have a start_date of 01/2/2011
24
+ And the event should have a end_date of 13/5/2011
25
+ And the event should have a sold_out of 25/12/2010
@@ -0,0 +1,30 @@
1
+ Feature: mapping a node's children to to another object
2
+ So that I don't have to spend all my time typing in repetitive XPaths
3
+ As an API consumer
4
+ I want to map a node to a series of other objects
5
+
6
+ Scenario: I have a simple mapping
7
+ Given I have car with brand and colours
8
+ And I have a colour class with description and hex and a from_node method
9
+ And I have the XML:
10
+ """
11
+ <car>
12
+ <brand>Fawd</brand>
13
+ <colour>
14
+ <description>Red</description>
15
+ <hex>#FF0000</hex>
16
+ </colour>
17
+ <colour>
18
+ <description>Blue</description>
19
+ <hex>#0000FF</hex>
20
+ </colour>
21
+ </car>
22
+ """
23
+ When I map the car node to a car object with:
24
+ """
25
+ map.string :brand
26
+ map.each :colour, :as => Colour, :to => :colours
27
+ """
28
+ Then the car should have a brand of "Fawd"
29
+ And the car should have a colour with a description of "Red" and a hex of "#FF0000"
30
+ And the car should have a colour with a description of "Blue" and a hex of "#0000FF"
@@ -0,0 +1,25 @@
1
+ Feature: mapping a document with float elements to an object
2
+ So that I don't have to spend all my time typing in repetitive XPaths
3
+ As an API consumer
4
+ I want to map a document to an object
5
+
6
+
7
+ Scenario: I have a simple mapping
8
+ Given I have an item with max_price, min_price and average_price
9
+ And I have the XML:
10
+ """
11
+ <item>
12
+ <max_price>20.43</max_price>
13
+ <min_price>9.50</min_price>
14
+ <average_price>99</average_price>
15
+ </item>
16
+ """
17
+ When I map the item node to an item object with:
18
+ """
19
+ map.float :max_price
20
+ map.float :min_price
21
+ map.float :average_price
22
+ """
23
+ Then the event should have a max_price of 20.43
24
+ And the event should have a min_price of 9.50
25
+ And the event should have a average_price of 99.0
@@ -0,0 +1,25 @@
1
+ Feature: mapping a document with int elements to an object
2
+ So that I don't have to spend all my time typing in repetitive XPaths
3
+ As an API consumer
4
+ I want to map a document to an object
5
+
6
+
7
+ Scenario: I have a simple mapping
8
+ Given I have a shop with items, staff and suppliers
9
+ And I have the XML:
10
+ """
11
+ <shop>
12
+ <items>300</items>
13
+ <staff>1</staff>
14
+ <suppliers>43</suppliers>
15
+ </shop>
16
+ """
17
+ When I map the shop node to a shop object with:
18
+ """
19
+ map.int :items
20
+ map.int :staff
21
+ map.int :suppliers
22
+ """
23
+ Then the shop should have 1 staff
24
+ And the shop should have 300 items
25
+ And the shop should have 43 suppliers
@@ -0,0 +1,26 @@
1
+ Feature: mapping a node to to another object
2
+ So that I don't have to spend all my time typing in repetitive XPaths
3
+ As an API consumer
4
+ I want to map a node to another object
5
+
6
+
7
+ Scenario: I have a simple mapping
8
+ Given I have plot with name and dimensions
9
+ And I have a dimensions class with width, depth and a from_node class method
10
+ And I have the XML:
11
+ """
12
+ <plot>
13
+ <name>Tiny Plot</name>
14
+ <dimensions>
15
+ <width>100</width>
16
+ <depth>200</depth>
17
+ </dimensions>
18
+ </plot>
19
+ """
20
+ When I map the plot node to a plot object with:
21
+ """
22
+ map.string :name
23
+ map.node :dimensions, :as => Dimensions
24
+ """
25
+ Then the plot should have a name of "Tiny Plot"
26
+ And the plot should have dimensions with a width of 100 and a depth of 200
@@ -0,0 +1,20 @@
1
+ Given /^I have an event with start_date, end_date and sold_out$/ do
2
+ class Event
3
+ include Dozuki::Mapper
4
+ attr_accessor :start_date, :end_date, :sold_out
5
+ end
6
+ end
7
+
8
+ When /^I map the event node to an event object with:$/ do |string|
9
+ Event.instance_eval %Q{
10
+ map_with do |map|
11
+ #{string}
12
+ end
13
+ }
14
+ @event = Event.from_node(@doc.get('/event'))
15
+ end
16
+
17
+ Then /^the event should have a (.*) of (\d+)\/(\d+)\/(\d+)$/ do |field, day, month, year|
18
+ @event.send(field).should == Date.civil(year.to_i, month.to_i, day.to_i)
19
+ end
20
+
@@ -0,0 +1,38 @@
1
+ Given /^I have car with brand and colours$/ do
2
+ class Car
3
+ include Dozuki::Mapper
4
+ attr_accessor :brand, :colours
5
+ def initialize
6
+ self.colours = []
7
+ end
8
+ end
9
+ end
10
+
11
+ Given /^I have a colour class with description and hex and a from_node method$/ do
12
+ class Colour
13
+ include Dozuki::Mapper
14
+ attr_accessor :description, :hex
15
+ map_with do |map|
16
+ map.string :description
17
+ map.string :hex
18
+ end
19
+ end
20
+ end
21
+
22
+ When /^I map the car node to a car object with:$/ do |string|
23
+ Car.instance_eval %Q{
24
+ map_with do |map|
25
+ #{string}
26
+ end
27
+ }
28
+ @car = Car.from_node(@doc.get('/car'))
29
+ end
30
+
31
+ Then /^the car should have a brand of "([^"]*)"$/ do |brand|
32
+ @car.brand.should == brand
33
+ end
34
+
35
+ Then /^the car should have a colour with a description of "([^"]*)" and a hex of "([^"]*)"$/ do |description, hex|
36
+ @car.colours.any?{|car| car.description == description && car.hex == hex}.should be_true
37
+ end
38
+
@@ -0,0 +1,19 @@
1
+ Given /^I have an item with max_price, min_price and average_price$/ do
2
+ class Item
3
+ include Dozuki::Mapper
4
+ attr_accessor :max_price, :min_price, :average_price
5
+ end
6
+ end
7
+
8
+ When /^I map the item node to an item object with:$/ do |string|
9
+ Item.instance_eval %Q{
10
+ map_with do |map|
11
+ #{string}
12
+ end
13
+ }
14
+ @item = Item.from_node(@doc.get('/item'))
15
+ end
16
+
17
+ Then /^the event should have a (.*) of (\d+\.\d+)$/ do |field, float|
18
+ @item.send(field).should == float.to_f
19
+ end
@@ -0,0 +1,20 @@
1
+ Given /^I have a shop with items, staff and suppliers$/ do
2
+ class Shop
3
+ include Dozuki::Mapper
4
+ attr_accessor :staff, :items, :suppliers
5
+ end
6
+ end
7
+
8
+ Then /^the shop should have (\d+) (.*)$/ do |count, field|
9
+ @shop.send(field).should == count.to_i
10
+ end
11
+
12
+ When /^I map the shop node to a shop object with:$/ do |string|
13
+ Shop.instance_eval %Q{
14
+ map_with do |map|
15
+ #{string}
16
+ end
17
+ }
18
+ @shop = Shop.from_node(@doc.get('/shop'))
19
+ end
20
+
@@ -0,0 +1,37 @@
1
+ Given /^I have plot with name and dimensions$/ do
2
+ class Plot
3
+ include Dozuki::Mapper
4
+ attr_accessor :name, :dimensions
5
+ end
6
+ end
7
+
8
+ Given /^I have a dimensions class with width, depth and a from_node class method$/ do
9
+ class Dimensions
10
+ include Dozuki::Mapper
11
+ attr_accessor :width, :depth
12
+ map_with do |map|
13
+ map.int :width
14
+ map.int :depth
15
+ end
16
+ end
17
+ end
18
+
19
+ When /^I map the plot node to a plot object with:$/ do |string|
20
+ Plot.instance_eval %Q{
21
+ map_with do |map|
22
+ #{string}
23
+ end
24
+ }
25
+ @plot = Plot.from_node(@doc.get('/plot'))
26
+ end
27
+
28
+ Then /^the plot should have a name of "([^"]*)"$/ do |name|
29
+ @plot.name.should == name
30
+ end
31
+
32
+ Then /^the plot should have dimensions with a width of (\d+) and a depth of (\d+)$/ do |width, depth|
33
+ dimensions = @plot.dimensions
34
+ dimensions.width.should == width.to_i
35
+ dimensions.depth.should == depth.to_i
36
+ end
37
+
@@ -0,0 +1,23 @@
1
+ Given /^I have an person with name, address and post_code$/ do
2
+ class Person
3
+ include Dozuki::Mapper
4
+ attr_accessor :name, :address, :post_code
5
+ end
6
+ end
7
+
8
+ Given /^I have the XML:$/ do |string|
9
+ @doc = Dozuki::XML.parse(string)
10
+ end
11
+
12
+ When /^I map the person node to a person object with:$/ do |string|
13
+ Person.instance_eval %Q{
14
+ map_with do |map|
15
+ #{string}
16
+ end
17
+ }
18
+ @person = Person.from_node(@doc.get('/person'))
19
+ end
20
+
21
+ Then /^the person should have the (.*) "([^"]*)"$/ do |field, string|
22
+ @person.send(field).should == string
23
+ end
@@ -0,0 +1,25 @@
1
+ Feature: mapping a document with string elements to an object
2
+ So that I don't have to spend all my time typing in repetitive XPaths
3
+ As an API consumer
4
+ I want to map a document to an object
5
+
6
+ @string
7
+ Scenario: I have a simple mapping
8
+ Given I have an person with name, address and post_code
9
+ And I have the XML:
10
+ """
11
+ <person>
12
+ <name>John Smith</name>
13
+ <address>1 Main Street</address>
14
+ <post_code>NW1 3ED</post_code>
15
+ </person>
16
+ """
17
+ When I map the person node to a person object with:
18
+ """
19
+ map.string :name
20
+ map.string :address
21
+ map.string :post_code
22
+ """
23
+ Then the person should have the name "John Smith"
24
+ Then the person should have the address "1 Main Street"
25
+ Then the person should have the post_code "NW1 3ED"
@@ -0,0 +1 @@
1
+ require 'dozuki-mapper'
@@ -0,0 +1,39 @@
1
+ module Dozuki
2
+ module Mapper
3
+ class Proxy
4
+ attr_accessor :receiver, :from_node
5
+
6
+ def initialize(receiver, node)
7
+ self.receiver = receiver
8
+ self.from_node = node
9
+ end
10
+
11
+ def string(attribute)
12
+ self.receiver.send("#{attribute}=", from_node.string("./#{attribute}"))
13
+ end
14
+
15
+ def int(attribute)
16
+ self.receiver.send("#{attribute}=", from_node.int("./#{attribute}"))
17
+ end
18
+
19
+ def date(attribute)
20
+ self.receiver.send("#{attribute}=", from_node.date("./#{attribute}"))
21
+ end
22
+
23
+ def float(attribute)
24
+ self.receiver.send("#{attribute}=", from_node.float("./#{attribute}"))
25
+ end
26
+
27
+ def node(attribute, opts={})
28
+ self.receiver.send("#{attribute}=", opts[:as].from_node(from_node.get("./#{attribute}")))
29
+ end
30
+
31
+ def each(attribute, opts={})
32
+ from_node.each("./#{attribute}") do |node|
33
+ receiver.send(opts[:to]) << opts[:as].from_node(node)
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module Dozuki
2
+ module Mapper
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ require 'dozuki'
2
+ require 'dozuki-mapper/proxy'
3
+ module Dozuki
4
+ module Mapper
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+ def map_from(node)
9
+ self.class.mapper.call Proxy.new(self, node)
10
+ end
11
+ module ClassMethods
12
+ def map_with(&blk)
13
+ @mapper = blk
14
+ end
15
+ def mapper
16
+ @mapper
17
+ end
18
+ def from_node(node)
19
+ new.tap do |instance|
20
+ instance.map_from(node)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+ module Dozuki
3
+ module Mapper
4
+ describe Proxy do
5
+ class TestClass < Struct.new(:field, :fields); end
6
+
7
+ describe "string" do
8
+ let(:receiver){ TestClass.new }
9
+ let(:node) { mock :node}
10
+ let(:method_name){:field}
11
+ let(:string) { "The Decemberists" }
12
+
13
+ before do
14
+ node.stub(:string).and_return(string)
15
+ end
16
+
17
+ subject { Proxy.new(receiver, node).string(method_name) }
18
+ it "should get the field from the node using the xpath ./field as a string" do
19
+ node.should_receive(:string).with('./field').and_return(string)
20
+ subject
21
+ end
22
+
23
+ it "should set the field on the receiver with the string" do
24
+ subject
25
+ receiver.field.should == string
26
+ end
27
+
28
+ end
29
+
30
+ describe "string" do
31
+ let(:receiver){ TestClass.new }
32
+ let(:node) { mock :node}
33
+ let(:method_name){:field}
34
+ let(:int) { 54 }
35
+
36
+ before do
37
+ node.stub(:int).and_return(int)
38
+ end
39
+
40
+ subject { Proxy.new(receiver, node).int(method_name) }
41
+ it "should get the field from the node using the xpath ./field as an int" do
42
+ node.should_receive(:int).with('./field').and_return(int)
43
+ subject
44
+ end
45
+
46
+ it "should set the field on the receiver with the int" do
47
+ subject
48
+ receiver.field.should == int
49
+ end
50
+ end
51
+
52
+ describe "float" do
53
+ let(:receiver){ TestClass.new }
54
+ let(:node) { mock :node}
55
+ let(:method_name){:field}
56
+ let(:float) { 503.0 }
57
+
58
+ before do
59
+ node.stub(:float).and_return(float)
60
+ end
61
+
62
+ subject { Proxy.new(receiver, node).float(method_name) }
63
+ it "should get the field from the node using the xpath ./field as an float" do
64
+ node.should_receive(:float).with('./field').and_return(float)
65
+ subject
66
+ end
67
+
68
+ it "should set the field on the receiver with the float" do
69
+ subject
70
+ receiver.field.should == float
71
+ end
72
+ end
73
+
74
+
75
+ describe "node" do
76
+ let(:node) { mock :node }
77
+ let(:receiver){ TestClass.new }
78
+ let(:method_name){ :field }
79
+ let(:other_class) { mock :class }
80
+ let(:new_object) { mock :new_object }
81
+
82
+ before do
83
+ node.stub(:get).and_return(node)
84
+ other_class.stub(:from_node).and_return(new_object)
85
+ end
86
+
87
+ subject { Proxy.new(receiver, node).node(method_name, :as => other_class) }
88
+
89
+ it "should get the node from the receiver using the ./field xpath" do
90
+ node.should_receive(:get).with('./field').and_return(node)
91
+ subject
92
+ end
93
+ it "should create a new instance of the other class suing from_node" do
94
+ other_class.should_receive(:from_node).with(node).and_return(new_object)
95
+ subject
96
+ end
97
+ it "should set the new object to the field" do
98
+ subject
99
+ receiver.field.should == new_object
100
+ end
101
+ end
102
+
103
+ describe "each" do
104
+ let(:node) { mock :node }
105
+ let(:receiver){ TestClass.new(nil, []) }
106
+ let(:other_class){ mock :other_class }
107
+ let(:new_object) { mock :new_object }
108
+ let(:field){ mock :field }
109
+
110
+ before do
111
+ node.stub(:each).and_yield(field)
112
+ other_class.stub(:from_node).and_return(new_object)
113
+ end
114
+
115
+ subject { Proxy.new(receiver, node).each(:field, :as => other_class, :to => :fields)}
116
+
117
+ it "should get each field from the node using the ./field xpath" do
118
+ node.should_receive(:each).with('./field').and_yield(field)
119
+ subject
120
+ end
121
+ it "should create a new instance of the other class using the from_node method" do
122
+ other_class.should_receive(:from_node).with(field).and_return(new_object)
123
+ subject
124
+ end
125
+ it "should append the object to the as field" do
126
+ subject
127
+ receiver.fields.should == [new_object]
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ module Dozuki
4
+ describe Mapper do
5
+ let(:node) { mock :node }
6
+ let(:klass){ Class.new }
7
+ before do
8
+ klass.send(:include, Mapper)
9
+ end
10
+ describe "mappings" do
11
+ let(:instance) { klass.new }
12
+ let(:proxy) { mock :proxy }
13
+ before { Mapper::Proxy.stub(:new).and_return(proxy) }
14
+ subject {
15
+ klass.map_with do |arg|
16
+ @called_with = arg
17
+ end
18
+ instance.map_from(node)
19
+ }
20
+ it "should create a new proxy with the node and the mapper" do
21
+ Mapper::Proxy.should_receive(:new).with(instance, node).and_return(proxy)
22
+ subject
23
+ end
24
+ it "should yield the proxy to the block mapped block" do
25
+ subject
26
+ @called_with.should == proxy
27
+ end
28
+ end
29
+ describe "from_node" do
30
+ let(:node) { mock :node }
31
+ let(:instance) { mock :klass }
32
+ before do
33
+ klass.stub(:new).and_return(instance)
34
+ instance.stub(:map_from)
35
+ end
36
+ subject { klass.from_node(node) }
37
+ it "should create a new instance of the class" do
38
+ klass.should_receive(:new).and_return(instance)
39
+ subject
40
+ end
41
+ it "should map the instance from the node" do
42
+ instance.should_receive(:map_from).with(node)
43
+ subject
44
+ end
45
+ it "should return the instance of the class" do
46
+ subject.should == instance
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1 @@
1
+ require 'dozuki-mapper'
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dozuki-mapper
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - James Almond
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-16 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: dozuki
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: cucumber
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: autotest
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: rake
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ type: :development
70
+ version_requirements: *id005
71
+ description: A DSL for mapping API output to objects
72
+ email:
73
+ - james@jamesalmond.com
74
+ executables: []
75
+
76
+ extensions: []
77
+
78
+ extra_rdoc_files: []
79
+
80
+ files:
81
+ - .autotest
82
+ - .gitignore
83
+ - .rspec
84
+ - Gemfile
85
+ - README.markdown
86
+ - Rakefile
87
+ - dozuki-mapper.gemspec
88
+ - features/date_mapping.feature
89
+ - features/each_mapping.feature
90
+ - features/float_mapping.feature
91
+ - features/int_mapping.feature
92
+ - features/node_mapping.feature
93
+ - features/step_definitions/date_steps.rb
94
+ - features/step_definitions/each_steps.rb
95
+ - features/step_definitions/float_steps.rb
96
+ - features/step_definitions/int_steps.rb
97
+ - features/step_definitions/node_steps.rb
98
+ - features/step_definitions/string_steps.rb
99
+ - features/string_mapping.feature
100
+ - features/support/env.rb
101
+ - lib/dozuki-mapper.rb
102
+ - lib/dozuki-mapper/proxy.rb
103
+ - lib/dozuki-mapper/version.rb
104
+ - spec/dozuki-mapper/proxy_spec.rb
105
+ - spec/dozuki-mapper_spec.rb
106
+ - spec/spec_helper.rb
107
+ has_rdoc: true
108
+ homepage: https://github.com/jamesalmond/dozuki-mapper
109
+ licenses: []
110
+
111
+ post_install_message:
112
+ rdoc_options: []
113
+
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: "0"
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: "0"
128
+ requirements: []
129
+
130
+ rubyforge_project: dozuki-mapper
131
+ rubygems_version: 1.6.2
132
+ signing_key:
133
+ specification_version: 3
134
+ summary: A DSL for mapping API output to objects
135
+ test_files:
136
+ - .autotest
137
+ - features/date_mapping.feature
138
+ - features/each_mapping.feature
139
+ - features/float_mapping.feature
140
+ - features/int_mapping.feature
141
+ - features/node_mapping.feature
142
+ - features/step_definitions/date_steps.rb
143
+ - features/step_definitions/each_steps.rb
144
+ - features/step_definitions/float_steps.rb
145
+ - features/step_definitions/int_steps.rb
146
+ - features/step_definitions/node_steps.rb
147
+ - features/step_definitions/string_steps.rb
148
+ - features/string_mapping.feature
149
+ - features/support/env.rb
150
+ - spec/dozuki-mapper/proxy_spec.rb
151
+ - spec/dozuki-mapper_spec.rb
152
+ - spec/spec_helper.rb