oodler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Mike Hagedorn
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,58 @@
1
+ # Oodler
2
+
3
+ Ruby wrapper for the [Oodle API](http://developer.oodle.com). Heavily inspired by [Wynn Nederlands's](http://github.com/pengwynn) [LinkedIn gem](http://github.com/pengwynn/linkedin), the Oodler gem provides an easy-to-use wrapper for Oodles's XML APIs.
4
+
5
+ ## Installation
6
+
7
+ sudo gem install gemcutter
8
+ gem tumble
9
+ sudo gem install oodler
10
+
11
+ ## Usage
12
+
13
+ ### Authenticate
14
+
15
+ Ooodle's API uses a key for authentication, you can get a key at http://developer.oodle.com/request-api-key/
16
+
17
+ require 'rubygems'
18
+ require 'oodler'
19
+
20
+ # get your api keys at https://www.linkedin.com/secure/developer
21
+ client = Oodler::Client.new("YOURAPIKEY")
22
+
23
+ ### Listing examples
24
+
25
+ # get the profile for the authenticated user
26
+ result = client.listing({:region => "chicago",:category => "vehicle/car"})
27
+
28
+ puts result.listings[0].id
29
+ puts result.listings[0].title
30
+ puts result.listings[0].id
31
+ puts result.listings[0].body
32
+ puts result.listings[0].url
33
+ puts result.listings[0].images[0].src
34
+
35
+ ..etc
36
+
37
+
38
+
39
+
40
+
41
+ TODO
42
+
43
+ * Nothing yet
44
+
45
+ ## Note on Patches/Pull Requests
46
+
47
+ * Fork the project.
48
+ * Make your feature addition or bug fix.
49
+ * Add tests for it. This is important so I don't break it in a
50
+ future version unintentionally.
51
+ * Commit, do not mess with rakefile, version, or history.
52
+ (if you want to have your own version, that is fine but
53
+ bump version in a commit by itself I can ignore when I pull)
54
+ * Send me a pull request. Bonus points for topic branches.
55
+
56
+ ## Copyright
57
+
58
+ Copyright (c) 2010 [Mike Hagedorn](http://silverchairsolutions.com). See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "oodler"
8
+ gem.summary = %Q{Ruby wrapper for the Oodle API}
9
+ gem.description = %Q{Ruby wrapper for the Oodle API}
10
+ gem.email = "mike@silverchairsolutions.com"
11
+ gem.homepage = "http://github.com/mwhagedorn/oodler"
12
+ gem.authors = ["Mike Hagedorn"]
13
+ gem.files = FileList["[A-Z]*", "{lib,test}/**/*"]
14
+
15
+
16
+ gem.add_dependency('roxml', '~> 3.1.3')
17
+ gem.add_dependency('crack', '~> 0.1.4')
18
+
19
+ gem.add_development_dependency('shoulda', '>= 2.10.1')
20
+ gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
21
+ gem.add_development_dependency('mocha', '0.9.4')
22
+ gem.add_development_dependency('fakeweb', '>= 1.2.5')
23
+ gem.add_development_dependency("httparty", ">= 0.5.2")
24
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
25
+ end
26
+ Jeweler::GemcutterTasks.new
27
+ rescue LoadError
28
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
29
+ end
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'test'
34
+ test.ruby_opts << '-rubygems'
35
+ test.pattern = 'test/**/*_test.rb'
36
+ test.verbose = true
37
+ end
38
+
39
+ begin
40
+ require 'rcov/rcovtask'
41
+ Rcov::RcovTask.new do |test|
42
+ test.libs << 'test'
43
+ test.pattern = 'test/**/test_*.rb'
44
+ test.verbose = true
45
+ end
46
+ rescue LoadError
47
+ task :rcov do
48
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
49
+ end
50
+ end
51
+
52
+ task :test => :check_dependencies
53
+
54
+ task :default => :test
55
+
56
+ require 'rake/rdoctask'
57
+ Rake::RDocTask.new do |rdoc|
58
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
59
+
60
+ rdoc.rdoc_dir = 'rdoc'
61
+ rdoc.title = "oodler #{version}"
62
+ rdoc.rdoc_files.include('README*')
63
+ rdoc.rdoc_files.include('lib/**/*.rb')
64
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'oodler'
3
+
4
+ # get your api keys at http://developer.oodle.com/request-api-key
5
+ client = Oodler::Client.new('your_api_key')
6
+
7
+
8
+
9
+ #most generic
10
+
11
+ client.listing({:region =>"chicago", :category=>"vehicle/cars"})
12
+
13
+ #other formulations of same search
14
+ client.search("chicago", {:category => "vehicle/cars"})
15
+ client.search_by_category("chicago", "vehicle/cars",{})
16
+
17
+ #adding criteria
18
+
@@ -0,0 +1,9 @@
1
+ module Oodler
2
+ class Category
3
+ include ROXML
4
+ xml_reader :id
5
+ xml_reader :name
6
+ xml_reader :abbrev
7
+ end
8
+
9
+ end
@@ -0,0 +1,78 @@
1
+ module Oodler
2
+ class Client
3
+ include HTTParty
4
+
5
+ attr_reader :key, :http_options
6
+
7
+ def initialize(key=Oodle.key,options={})
8
+ @key, @http_options = key,options
9
+ end
10
+
11
+ def web_client
12
+ @web_client ||= Oodler::WebClient.new(@key)
13
+ end
14
+
15
+
16
+ def get(path, options={})
17
+ path = "/listings#{path}"
18
+ validate_parameters(options)
19
+ response = web_client.get(path, options)
20
+ raise_errors(response)
21
+ response.body
22
+ end
23
+
24
+ #client.search("chicago", {:category =>"vehicle/cars"})
25
+ def search( region, options={})
26
+ path = ""
27
+
28
+ options= {:query =>options}
29
+ options[:query].merge!({:region => region}) if region
30
+
31
+ OodleResponse.from_xml(get(path, options))
32
+ end
33
+
34
+ #helper method - client.search_by_category("chicago", "vehicle/car")
35
+ def search_by_category(region,category, options={})
36
+ path=""
37
+ options= {:query =>options}
38
+ options[:query].merge!({:region => region}) if region
39
+ options[:query].merge!({:category => category}) if category
40
+
41
+ OodleResponse.from_xml(get(path, options))
42
+
43
+ end
44
+
45
+
46
+ #the generic pash a hash version
47
+ #example client.listing(:region=>"chicago", :category => "vehicle/car")
48
+ #returns an OodleResponse
49
+ def listing(options={})
50
+ path = ""
51
+ options= {:query =>options}
52
+ OodleResponse.from_xml(get(path, options))
53
+ end
54
+
55
+ private
56
+
57
+ def validate_parameters(options)
58
+ params = options[:query]
59
+ unless params[:region]
60
+ raise OodleArgumentError, 'Missing region paramter. Visit http://developer.oodle.com/regions-list/ for possible regions.'
61
+ end
62
+ unless params[:category] || options[:q]
63
+ raise OodleArgumentError, 'You must supply a category or query parameter. Visit http://developer.oodle.com/categories-list/ for possible categories.'
64
+ end
65
+ end
66
+
67
+ def raise_errors(response)
68
+ if response["oodle_response"]["error"]
69
+ data = response["oodle_response"]["error"]
70
+ raise OodlerError.new(data), "(fail: #{data}"
71
+ end
72
+
73
+ end
74
+
75
+
76
+
77
+ end
78
+ end
@@ -0,0 +1,19 @@
1
+ module Oodler
2
+ class Element
3
+ include ROXML
4
+ xml_reader :id
5
+ xml_reader :title
6
+ xml_reader :body
7
+ xml_reader :url
8
+ xml_reader :category, :as => Category
9
+ xml_reader :source, :as => Source
10
+ xml_reader :location, :as => Location
11
+ xml_reader :images, :as => [Image], :in =>"images"
12
+ xml_reader :ctime
13
+ xml_reader :paid?
14
+ xml_reader :revenue_score
15
+ xml_reader :similar_url
16
+ xml_reader :properties ,:as => {:key => :name,:value => :content}, :in => "attributes"
17
+
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module Oodler
2
+ class Image
3
+ include ROXML
4
+ xml_name :element
5
+ xml_reader :src
6
+ xml_reader :width
7
+ xml_reader :height
8
+ xml_reader :alt
9
+
10
+
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module Oodler
2
+ class Location
3
+ include ROXML
4
+ xml_reader :latitude
5
+ xml_reader :longitude
6
+ xml_reader :address
7
+ xml_reader :zip
8
+ xml_reader :addresscode
9
+ xml_reader :citycode
10
+ xml_reader :name
11
+ xml_reader :state
12
+ xml_reader :country
13
+
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Oodler
2
+ class OodleResponse
3
+ include ROXML
4
+ xml_reader :region, :as => Region, :in => "current"
5
+ xml_reader :category, :as => Category, :in => "current"
6
+ xml_reader :elements, :as =>[Element], :in => "listings"
7
+ xml_reader :start, :in =>"current"
8
+ xml_reader :num, :in => "current"
9
+ xml_reader :stat, :from => :attr
10
+ xml_reader :error
11
+
12
+ def listings
13
+ elements
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,7 @@
1
+ module Oodler
2
+ class Region
3
+ include ROXML
4
+ xml_reader :id
5
+ xml_reader :name
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Oodler
2
+ class Source
3
+ include ROXML
4
+ xml_reader :id
5
+ xml_reader :name
6
+
7
+
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ module Oodler
2
+ class WebClient
3
+ include HTTParty
4
+
5
+ attr_accessor :key
6
+ base_uri 'api.oodle.com/api/v2/'
7
+ format :xml
8
+
9
+ def initialize(key=nil)
10
+ @key = key
11
+ end
12
+
13
+ def get(path,options)
14
+ options[:query].merge!({:key => @key}) if @key
15
+ validate_parameters(options)
16
+ self.class.get(path,options)
17
+ end
18
+
19
+ private
20
+ def validate_parameters(options)
21
+ params = options[:query]
22
+ unless params[:key]
23
+ raise OodleArgumentError, 'Missing API key parameter. Visit http://developer.oodle.com/request-api-key/ to get one'
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/oodler.rb ADDED
@@ -0,0 +1,66 @@
1
+
2
+ require 'rubygems'
3
+
4
+ gem 'roxml', '~> 3.1.3'
5
+ require 'roxml'
6
+
7
+ gem 'crack', '~> 0.1.4'
8
+ require 'crack'
9
+
10
+ gem 'httparty'
11
+ require 'httparty'
12
+
13
+ require 'cgi'
14
+
15
+ module Oodler
16
+ class OodlerError < StandardError
17
+ attr_reader :data
18
+
19
+ def initialize(data)
20
+ @data = data
21
+ super
22
+ end
23
+ end
24
+
25
+ class OodleArgumentError <OodlerError; end
26
+
27
+
28
+
29
+ # config/initializers/oodler.rb (for instance)
30
+ #
31
+ # Oodler.configure do |config|
32
+ # config.key = 'api_key'
33
+ # end
34
+ #
35
+ # elsewhere
36
+ #
37
+ # client = Oodler::Client.new
38
+ def self.configure
39
+ yield self
40
+
41
+ Oodler.key = key
42
+ true
43
+ end
44
+
45
+ def self.key
46
+ @key
47
+ end
48
+
49
+ def self.key=(key)
50
+ @key = key
51
+ end
52
+
53
+
54
+ end
55
+
56
+ directory = File.expand_path(File.dirname(__FILE__))
57
+ require File.join(directory, "oodler", 'category')
58
+ require File.join(directory, "oodler", 'region')
59
+ require File.join(directory, 'oodler', 'web_client')
60
+ require File.join(directory, 'oodler', 'location')
61
+ require File.join(directory, 'oodler', 'image')
62
+ require File.join(directory, 'oodler', 'source')
63
+ require File.join(directory, 'oodler', 'category')
64
+ require File.join(directory, 'oodler', 'element')
65
+ require File.join(directory, 'oodler', 'oodle_response')
66
+ require File.join(directory, 'oodler', 'client')
@@ -0,0 +1,137 @@
1
+ require 'test_helper'
2
+ require 'test_helper'
3
+
4
+ class ClientTest < Test::Unit::TestCase
5
+ context "when hitting the Oodle API" do
6
+ setup do
7
+ @oodler = Oodler::Client.new('key')
8
+ end
9
+ should "have a web client" do
10
+ stub_get("/listings?region=chicago&category=vehicle%2fcar&key=key", "listings.xml")
11
+ wc = mock('webclient')
12
+
13
+ Oodler::WebClient.expects(:new).with('keyval').returns(wc)
14
+ @oodler = Oodler::Client.new('keyval')
15
+
16
+ @oodler.web_client.should == wc
17
+
18
+ end
19
+
20
+ context "and a region is not provided" do
21
+ should "throw an error" do
22
+ stub_get("/v2/listings?region=chicago&category=vehicle%2Fcar&key=key", "listings.xml")
23
+ assert_raise(Oodler::OodleArgumentError) do
24
+ @oodler.search(nil, {:category=>"vehicle/car"})
25
+ end
26
+
27
+ end
28
+ end
29
+
30
+ should "retrieve a listing" do
31
+ stub_get("/api/v2/listings?key=key&region=chicago&category=vehicle%2Fcars", "listings.xml")
32
+ p = @oodler.search("chicago", {:category => "vehicle/cars"})
33
+
34
+ p.region.id.should == "chicago"
35
+ p.region.name.should == "Chicago"
36
+ p.category.id.should == "vehicle/car"
37
+ p.start.should == "1"
38
+ p.listings.size.should == 10
39
+ p.listings[0].id.should == "1899168373"
40
+ p.listings[0].title.should == "2002 Mazda Tribute"
41
+ p.listings[0].category.name.should == "SUVs"
42
+ p.listings[0].source.name.should == "EveryCarListed.com"
43
+ p.listings[0].location.zip.should == "60453"
44
+ p.listings[0].images.size.should == 2
45
+ p.listings[0].images[0].src.should == "http://i.oodleimg.com/item/1899168373_1m?1271098477"
46
+ p.listings[0].paid?.should == true
47
+ p.listings[0].properties.should_not == nil
48
+ p.listings[0].properties["color"].should == "Grey"
49
+ end
50
+
51
+ should "retrieve a listing using convenience method" do
52
+ stub_get("/api/v2/listings?key=key&region=chicago&category=vehicle%2Fcars", "listings.xml")
53
+ p = @oodler.search_by_category("chicago", "vehicle/cars")
54
+
55
+ p.region.id.should == "chicago"
56
+ p.region.name.should == "Chicago"
57
+ p.category.id.should == "vehicle/car"
58
+ p.start.should == "1"
59
+ p.listings.size.should == 10
60
+ p.listings[0].id.should == "1899168373"
61
+ p.listings[0].title.should == "2002 Mazda Tribute"
62
+ p.listings[0].category.name.should == "SUVs"
63
+ p.listings[0].source.name.should == "EveryCarListed.com"
64
+ p.listings[0].location.zip.should == "60453"
65
+ p.listings[0].images.size.should == 2
66
+ p.listings[0].images[0].src.should == "http://i.oodleimg.com/item/1899168373_1m?1271098477"
67
+ p.listings[0].paid?.should == true
68
+ p.listings[0].properties.should_not == nil
69
+ p.listings[0].properties["color"].should == "Grey"
70
+ end
71
+ context "when using the listing method " do
72
+ should "retrieve a listing " do
73
+ stub_get("/api/v2/listings?key=key&region=chicago&category=vehicle%2Fcars", "listings.xml")
74
+ p = @oodler.listing({:region =>"chicago", :category=>"vehicle/cars"})
75
+
76
+ p.region.id.should == "chicago"
77
+ p.region.name.should == "Chicago"
78
+ p.category.id.should == "vehicle/car"
79
+ p.start.should == "1"
80
+ p.listings.size.should == 10
81
+ p.listings[0].id.should == "1899168373"
82
+ p.listings[0].title.should == "2002 Mazda Tribute"
83
+ p.listings[0].category.name.should == "SUVs"
84
+ p.listings[0].source.name.should == "EveryCarListed.com"
85
+ p.listings[0].location.zip.should == "60453"
86
+ p.listings[0].images.size.should == 2
87
+ p.listings[0].images[0].src.should == "http://i.oodleimg.com/item/1899168373_1m?1271098477"
88
+ p.listings[0].paid?.should == true
89
+ p.listings[0].properties.should_not == nil
90
+ p.listings[0].properties["color"].should == "Grey"
91
+ end
92
+
93
+ should "throw an error with empty hash" do
94
+ stub_get("/api/v2/listings?key=key&region=chicago&category=vehicle%2Fcars", "listings.xml")
95
+ assert_raise(Oodler::OodleArgumentError) do
96
+ @oodler.listing()
97
+ end
98
+
99
+
100
+ end
101
+
102
+ should "allow inclusion of attributes in the query" do
103
+ stub_get("/api/v2/listings?region=chicago&attributes=make_honda%2Cmodel_civic%2Cprice_12000_15000&key=key&category=vehicle%2Fcars", "listings.xml")
104
+
105
+ @oodler.listing({:region =>"chicago", :category=>"vehicle/cars", :attributes=>"make_honda,model_civic,price_12000_15000"})
106
+
107
+ end
108
+
109
+ should "display error details" do
110
+ stub_get("/api/v2/listings?region=chicago&attributes=make_honda%2Cmodel_civic%2Cprice_12000_15000&key=key&category=vehicle%2Fbogus", "fail.xml")
111
+ assert_raise(Oodler::OodlerError) do
112
+ @oodler.listing({:region =>"chicago", :category=>"vehicle/bogus", :attributes=>"make_honda,model_civic,price_12000_15000"})
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
119
+
120
+ should "encode spaces as %20" do
121
+ stub_get("/api/v2/listings?key=key&region=sf&category=job&q=software%20engineer", "listings.xml")
122
+ p = @oodler.search("sf", {:category => "job", :q =>"software engineer"})
123
+
124
+ end
125
+
126
+ should "encode commas as %2C" do
127
+ stub_get("/api/v2/listings?location=San%20Mateo%2C%20CA&region=usa&key=key&category=job&", "listings.xml")
128
+ p = @oodler.search("usa", {:category => "job", :location =>"San Mateo, CA"})
129
+
130
+ end
131
+
132
+
133
+
134
+
135
+ end
136
+
137
+ end
@@ -0,0 +1,85 @@
1
+ require 'test_helper'
2
+
3
+ class ElementTest < Test::Unit::TestCase
4
+
5
+ should "parse element correctly from xml" do
6
+ element = Oodler::Element.from_xml('<element>
7
+ <id>1900074984</id>
8
+ <title>2010 Chrysler Town &amp; Country Touring</title>
9
+ <body>Driver manual lumbar adjust, Active headrests, Front center console w/cupholder, Pwr adjustable pedals, Tip start, Tilt steering column, Leather-wrapped steering wheel, Steering wheel audio controls, Trip computer w/temp &amp; compass, Door ajar warning light, Sliding door alert, Low washer fluid warning signal, Dark maple/silver instrument panel bezel, Satin silver instrument panel bezel center stack, Pwr front windows w/one-touch, 2nd row pwr windows, Pwr rear quarter vent windows, Pwr door locks, Keyless entry w/immobilizer, Speed control, Universal garage door opener, Rear window defroster, Analog clock, Dual glove boxes, Lower instrument panel cubby bin, Cupholders w/overhead illumination, Front/rear 12V DC pwr outlets, 12V auxiliary pwr outlets, 115V pwr outlet, Driver door sill storage bin, Door trim panel w/dark maple accents, Illuminated front door storage, Interior observation mirror, Overhead console, Overhead storage bins, Rear overhead console, Visors w/illuminated vanity mirrors</body>
10
+ <url>http://cars.oodle.com/r_a2xx_/1900074984-11202p454,10-EST/www.everycarlisted.com___chKLl2fSDGxSt6l73uN6064DyBKKek7cFjfdds1IVF0TXnFPtwBGejSLLeJvnwcNbSGADeRrrSEW8TQWmq9mTnxTRMpCVoogInIUUrMi4fVYmgrW86CjxEqU3RRWlP5g</url>
11
+ <category>
12
+ <id>vehicle/car</id>
13
+ <name>Cars</name>
14
+ </category>
15
+ <source>
16
+ <id>everycarlisted-bronze</id>
17
+ <name>EveryCarListed.com</name>
18
+ </source>
19
+ <location>
20
+ <latitude>42.0657997</latitude>
21
+ <longitude>-87.9197998</longitude>
22
+ <address>77 Rand Rd</address>
23
+ <zip>60016</zip>
24
+ <addresscode>addr:usa:il:desplaines:77+rand+rd</addresscode>
25
+ <citycode>usa:il:desplaines</citycode>
26
+ <name>Des Plaines</name>
27
+ <state>IL</state>
28
+ <country>USA</country>
29
+ </location>
30
+ <images>
31
+ <element>
32
+ <src>http://i.oodleimg.com/item/1900074984_1m?1271160266</src>
33
+ <width>144</width>
34
+ <height>108</height>
35
+ <alt>2010 Chrysler Town &amp; Country T</alt>
36
+ </element>
37
+ <element>
38
+ <src>http://i.oodleimg.com/item/1900074984_1s?1271160266</src>
39
+ <width>112</width>
40
+ <height>84</height>
41
+ <alt>2010 Chrysler Town &amp; Country T</alt>
42
+ </element>
43
+ </images>
44
+ <ctime>1271160145</ctime>
45
+ <paid>Yes</paid>
46
+ <revenue_score>10</revenue_score>
47
+ <similar_url>http://cars.oodle.com/2009_2011-hrysler-town_and_country/chicago-area/price_27000_36000/mileage_0_20000/?inbs=1</similar_url>
48
+ <attributes>
49
+ <color>Blue</color>
50
+ <condition>New</condition>
51
+ <currency>USD</currency>
52
+ <dealer_id>Advantage Chrysler Jeep Dodge of Des Pla</dealer_id>
53
+ <delivery>Local Delivery</delivery>
54
+ <engine_type>4.0L V6 24V MPFI SOHC</engine_type>
55
+ <features>Trip Computer,Keyless Entry,Clock,Center Console,Air Conditioning,Air Bags</features>
56
+ <fee>No</fee>
57
+ <has_photo>Thumbnail</has_photo>
58
+ <make>Chrysler</make>
59
+ <mileage>2</mileage>
60
+ <model>Town and Country</model>
61
+ <price>31415</price>
62
+ <price_display>$31,415</price_display>
63
+ <seller_type>Dealer</seller_type>
64
+ <transmission>Automatic</transmission>
65
+ <trim>Touring</trim>
66
+ <vin>2A4RR5DX9AR201012</vin>
67
+ <year>2010</year>
68
+ </attributes>
69
+ </element>')
70
+
71
+ element.id.should == "1900074984"
72
+ element.category.name.should == "Cars"
73
+ element.location.latitude.should == "42.0657997"
74
+ element.images.size.should == 2
75
+ element.images.first.src.should == "http://i.oodleimg.com/item/1900074984_1m?1271160266"
76
+ element.paid?.should == true
77
+ element.properties.keys.size.should == 19
78
+ element.properties["color"].should == "Blue"
79
+ element.properties["mileage"].should == "2"
80
+
81
+
82
+
83
+
84
+ end
85
+ end
File without changes
@@ -0,0 +1,5 @@
1
+ <oodle_response stat="fail">
2
+ <error>
3
+ Invalid category ('category'). To see the complete list of valid categories, visit http://developer.oodle.com/categories-list
4
+ </error>
5
+ </oodle_response>