dm-googlebase 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Carl Porth
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.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = dm-googlebase
2
+
3
+ Description goes here.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 Carl Porth. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "dm-googlebase"
8
+ gem.summary = %Q{A DataMapper adapter for Google Base}
9
+ gem.email = "badcarl@gmail.com"
10
+ gem.homepage = "http://github.com/badcarl/dm-googlebase"
11
+ gem.authors = ["Carl Porth"]
12
+ gem.add_dependency 'dm-core', '~> 0.10.2'
13
+ gem.add_dependency 'dm-types', '~> 0.10.2'
14
+ gem.add_dependency 'dm-validations', '~> 0.10.2'
15
+ gem.add_dependency 'gdata'
16
+ gem.add_dependency 'nokogiri'
17
+ gem.add_development_dependency 'dm-sweatshop', '~> 0.10.0'
18
+ gem.add_development_dependency 'fakeweb', '~> 1.2.8'
19
+ end
20
+
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:spec) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.spec_files = FileList['spec/**/*_spec.rb']
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ task :spec => :check_dependencies
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "dm-googlebase #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dm-googlebase}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Carl Porth"]
12
+ s.date = %q{2010-01-22}
13
+ s.email = %q{badcarl@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "dm-googlebase.gemspec",
26
+ "lib/googlebase.rb",
27
+ "lib/googlebase/adapter.rb",
28
+ "lib/googlebase/product.rb",
29
+ "lib/googlebase/product_properties.rb",
30
+ "spec/googlebase/adapter_spec.rb",
31
+ "spec/googlebase/product_spec.rb",
32
+ "spec/spec_helper.rb",
33
+ "spec/spec_matchers.rb",
34
+ "spec/xml_helpers.rb"
35
+ ]
36
+ s.homepage = %q{http://github.com/badcarl/dm-googlebase}
37
+ s.rdoc_options = ["--charset=UTF-8"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.3.5}
40
+ s.summary = %q{A DataMapper adapter for Google Base}
41
+ s.test_files = [
42
+ "spec/googlebase/adapter_spec.rb",
43
+ "spec/googlebase/product_spec.rb",
44
+ "spec/spec_helper.rb",
45
+ "spec/spec_matchers.rb",
46
+ "spec/xml_helpers.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 3
52
+
53
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
54
+ s.add_runtime_dependency(%q<dm-core>, ["~> 0.10.2"])
55
+ s.add_runtime_dependency(%q<dm-types>, ["~> 0.10.2"])
56
+ s.add_runtime_dependency(%q<dm-validations>, ["~> 0.10.2"])
57
+ s.add_runtime_dependency(%q<gdata>, [">= 0"])
58
+ s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
59
+ s.add_development_dependency(%q<dm-sweatshop>, ["~> 0.10.0"])
60
+ s.add_development_dependency(%q<fakeweb>, ["~> 1.2.8"])
61
+ else
62
+ s.add_dependency(%q<dm-core>, ["~> 0.10.2"])
63
+ s.add_dependency(%q<dm-types>, ["~> 0.10.2"])
64
+ s.add_dependency(%q<dm-validations>, ["~> 0.10.2"])
65
+ s.add_dependency(%q<gdata>, [">= 0"])
66
+ s.add_dependency(%q<nokogiri>, [">= 0"])
67
+ s.add_dependency(%q<dm-sweatshop>, ["~> 0.10.0"])
68
+ s.add_dependency(%q<fakeweb>, ["~> 1.2.8"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<dm-core>, ["~> 0.10.2"])
72
+ s.add_dependency(%q<dm-types>, ["~> 0.10.2"])
73
+ s.add_dependency(%q<dm-validations>, ["~> 0.10.2"])
74
+ s.add_dependency(%q<gdata>, [">= 0"])
75
+ s.add_dependency(%q<nokogiri>, [">= 0"])
76
+ s.add_dependency(%q<dm-sweatshop>, ["~> 0.10.0"])
77
+ s.add_dependency(%q<fakeweb>, ["~> 1.2.8"])
78
+ end
79
+ end
80
+
data/lib/googlebase.rb ADDED
@@ -0,0 +1,8 @@
1
+ module GoogleBase
2
+ dir = File.expand_path(File.join(File.dirname(__FILE__), 'googlebase'))
3
+
4
+ require File.join(dir, 'adapter')
5
+
6
+ autoload :Product, File.join(dir, 'product')
7
+ autoload :ProductProperties, File.join(dir, 'product_properties')
8
+ end
@@ -0,0 +1,186 @@
1
+ require 'dm-core'
2
+ require 'gdata'
3
+ require 'nokogiri'
4
+
5
+ module DataMapper
6
+ class Property
7
+ OPTIONS << :to_xml << :from_xml
8
+ end
9
+ end
10
+
11
+ module GoogleBase
12
+ class Adapter < DataMapper::Adapters::AbstractAdapter
13
+
14
+ XML_ATTRIBUTES = {
15
+ :xmlns => 'http://www.w3.org/2005/Atom',
16
+ 'xmlns:g' => 'http://base.google.com/ns/1.0',
17
+ 'xmlns:gd' => 'http://schemas.google.com/g/2005'
18
+ }
19
+
20
+ attr_reader :gb
21
+ attr_accessor :dry_run
22
+
23
+ def read(query)
24
+ records = []
25
+
26
+ operands = query.conditions.operands
27
+
28
+ if read_one?(operands)
29
+
30
+ response = @gb.get(operands.first.value)
31
+ xml = Nokogiri::XML.parse(response.body)
32
+
33
+ each_record(xml, query.fields) do |record|
34
+ records << record
35
+ end
36
+
37
+ elsif read_all?(operands)
38
+
39
+ start = query.offset + 1
40
+ per_page = query.limit || 250
41
+
42
+ url = "http://www.google.com/base/feeds/items?start-index=#{start}&max-results=#{per_page}"
43
+
44
+ while url
45
+ response = @gb.get(url)
46
+ xml = Nokogiri::XML.parse(response.body).at('./xmlns:feed')
47
+
48
+ each_record(xml, query.fields) do |record|
49
+ records << record
50
+ end
51
+
52
+ break if query.limit && query.limit >= records.length
53
+ url = xml.at("./xmlns:link[@rel='next']/@href")
54
+ end
55
+
56
+ else
57
+ raise NotImplementedError
58
+ # TODO implement query conditions
59
+ end
60
+
61
+ records
62
+ end
63
+
64
+ def create(resources)
65
+ result = 0
66
+
67
+ resources.each do |resource|
68
+ xml = build_xml(resource)
69
+ url = "http://www.google.com/base/feeds/items"
70
+ url << "?dry-run=true" if @dry_run
71
+
72
+ response = @gb.post(url, xml)
73
+
74
+ result += 1 if response.status_code == 201
75
+ end
76
+
77
+ result
78
+ end
79
+
80
+ def update(attributes, resources)
81
+ result = 0
82
+
83
+ resources.each do |resource|
84
+ xml = build_xml(resource)
85
+ url = resource.key.first
86
+ url << "?dry-run=true" if @dry_run
87
+
88
+ response = @gb.put(url, xml)
89
+
90
+ result += 1 if response.status_code == 200
91
+ end
92
+
93
+ result
94
+ end
95
+
96
+ def delete(resources)
97
+ result = 0
98
+
99
+ resources.each do |resource|
100
+ url = resource.key.first
101
+ url << "?dry-run=true" if @dry_run
102
+
103
+ response = @gb.delete(url)
104
+
105
+ result += 1 if response.status_code == 200
106
+ end
107
+
108
+ result
109
+ end
110
+
111
+ def token
112
+ @gb.auth_handler.token
113
+ end
114
+
115
+ def build_xml(resource)
116
+ builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
117
+
118
+ xml.entry(XML_ATTRIBUTES) do
119
+ resource.model.properties.each do |property|
120
+ value = property.get(resource)
121
+ next if value.blank?
122
+
123
+ if to_xml = property.options[:to_xml]
124
+ to_xml.call(xml, value)
125
+ elsif not property.options.has_key?(:to_xml)
126
+ xml.send "#{property.field}_", value
127
+ end
128
+ end
129
+ end
130
+
131
+ end
132
+
133
+ builder.to_xml
134
+ end
135
+
136
+ private
137
+
138
+ def initialize(name, options)
139
+ super(name, options)
140
+
141
+ assert_kind_of 'options[:user]', options[:user], String
142
+ assert_kind_of 'options[:password]', options[:password], String
143
+
144
+ @gb = GData::Client::GBase.new
145
+ @gb.source = 'dm-googlebase'
146
+ @gb.clientlogin(options[:user], options[:password])
147
+ @dry_run = options[:dry_run] || false
148
+ end
149
+
150
+ def each_record(xml, fields)
151
+ xml.xpath('./xmlns:entry').each do |entry|
152
+ record = fields.map do |property|
153
+
154
+ value = if from_xml = property.options[:from_xml]
155
+ if from_xml.respond_to?(:call)
156
+ from_xml.call(entry)
157
+ else
158
+ element = entry.at("./#{from_xml}") or next
159
+ element.content
160
+ end
161
+ else
162
+ element = entry.at("./#{property.field}") or next
163
+ element.content
164
+ end
165
+
166
+ [ property.field, property.typecast(value.to_s) ]
167
+ end
168
+
169
+ yield record.to_hash
170
+ end
171
+ end
172
+
173
+ def read_one?(operands)
174
+ operands.length == 1 &&
175
+ operands.first.kind_of?(DataMapper::Query::Conditions::EqualToComparison) &&
176
+ operands.first.subject.key?
177
+ end
178
+
179
+ def read_all?(operands)
180
+ operands.empty?
181
+ end
182
+
183
+ end
184
+ end
185
+
186
+ DataMapper::Adapters::GoogleBaseAdapter = GoogleBase::Adapter
@@ -0,0 +1,6 @@
1
+ module GoogleBase
2
+ class Product
3
+ include DataMapper::Resource
4
+ include GoogleBase::ProductProperties
5
+ end
6
+ end
@@ -0,0 +1,40 @@
1
+ require 'extlib/lazy_module'
2
+ require 'dm-types/uri'
3
+ require 'dm-validations'
4
+
5
+ module GoogleBase
6
+ ProductProperties = LazyModule.new do
7
+ property :id, String, :key => true, :required => false, :length => 255, :from_xml => 'xmlns:id'
8
+ property :title, String, :from_xml => 'xmlns:title', :length => 70
9
+ property :description, DataMapper::Types::Text, :field => 'content', :from_xml => 'xmlns:content', :lazy => false
10
+ property :link, URI,
11
+ :from_xml => "xmlns:link[@rel='alternate']/@href",
12
+ :to_xml => lambda { |xml, value| xml.link :href => value, :type => 'text/html', :rel => 'alternate' }
13
+ property :condition, String, :field => 'g:condition', :required => true # :set => %w[ new used refurbished ], :default => 'new',
14
+ property :product_type, String, :field => 'g:product_type', :length => 255
15
+ property :image_link, URI, :field => 'g:image_link'
16
+ property :product_id, String, :field => 'g:id', :required => true
17
+ property :price, String, :field => 'g:price', :required => true
18
+ property :brand, String, :field => 'g:brand'
19
+ property :item_type, String, :field => 'g:item_type', :required => true, :set => %w[ Products Produkte ], :default => 'Products'
20
+
21
+ # optional
22
+ property :expires_at, DateTime, :field => 'g:expiration_date',
23
+ :to_xml => lambda { |xml, value| xml.send 'g:expiration_date', value.strftime('%F') }
24
+ property :quantity, Integer, :field => 'g:quantity'
25
+ property :payment_accepted, String,
26
+ :from_xml => lambda { |entry| entry.xpath('./g:payment').map { |e| e.content }.join(',') },
27
+ :to_xml => lambda { |xml, values| values.split(',').each { |value| xml.send 'g:payment_accepted', value } }
28
+ property :item_language, String, :field => 'g:item_language'
29
+ property :target_country, String, :field => 'g:target_country'
30
+
31
+ # read only
32
+ property :created_at, DateTime, :field => 'xmlns:published', :to_xml => false
33
+ property :updated_at, DateTime, :field => 'xmlns:updated', :to_xml => false
34
+ property :category, String, :field => 'xmlns:category/@term', :to_xml => false
35
+ property :author_name, String, :field => 'xmlns:author/xmlns:name', :to_xml => false
36
+ property :author_email, String, :field => 'xmlns:author/xmlns:email', :to_xml => false
37
+ property :customer_id, Integer, :field => 'g:customer_id', :to_xml => false
38
+ property :feed_link, URI, :field => 'gd:feedLink/@href', :to_xml => false
39
+ end
40
+ end
@@ -0,0 +1,334 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'dm-types'
3
+
4
+ describe GoogleBase::Adapter do
5
+
6
+ before(:each) do
7
+ FakeWeb.register_uri(:post, 'https://www.google.com:443/accounts/ClientLogin',
8
+ :response => http_response(<<-CONTENT.margin))
9
+ SID=#{'x' * 182}
10
+ LSID=#{'y' * 182}
11
+ Auth=#{'z' * 182}
12
+ CONTENT
13
+
14
+ @adapter = DataMapper.setup(:default, :adapter => :google_base, :user => 'carl', :password => 'secret')
15
+ @repository = DataMapper.repository(@adapter.name)
16
+
17
+ @url = 'http://www.google.com:80/base/feeds/items/123456789'
18
+
19
+ Object.const_defined?(:Item).should be_false
20
+
21
+ class ::Item
22
+ include DataMapper::Resource
23
+ property :id, String, :key => true, :field => 'xmlns:id', :required => false
24
+ end
25
+ end
26
+
27
+ after(:each) do
28
+ DataMapper::Model.descendants.delete(Item)
29
+ Object.send(:remove_const, :Item)
30
+ end
31
+
32
+ describe "setup" do
33
+ before(:each) do
34
+ @config = { :adapter => :google_base, :user => 'carl', :password => 'secret' }
35
+ end
36
+
37
+ it "accepts dry_run" do
38
+ adapter = DataMapper.setup(:default, @config.merge(:dry_run => true))
39
+ adapter.dry_run.should == true
40
+ end
41
+ end
42
+
43
+ describe 'authenticating' do
44
+
45
+ # TODO ensure user and password are used
46
+
47
+ it "authenticates before the first request" do
48
+ FakeWeb.register_uri(:get, @url, :body => xml_entry(options))
49
+ Item.get(@url).id
50
+ @adapter.token.should == 'z' * 182
51
+ end
52
+
53
+ end
54
+
55
+ shared_examples_for 'parsing an xml entry' do
56
+
57
+ before(:each) do
58
+ raise 'do_read_one undefined' unless defined?(:do_read_one)
59
+ raise 'stub_get undefined' unless defined?(:stub_get)
60
+ end
61
+
62
+ it "parses the id" do
63
+ @url = 'http://www.google.com/base/feeds/items/1337'
64
+ stub_get('id' => @url)
65
+ do_read_one.id.should == @url
66
+ end
67
+
68
+ it "parses an element via :field" do
69
+ Item.property :title, String, :field => 'xmlns:title'
70
+ stub_get('title' => 'Product Title')
71
+ do_read_one.title.should == 'Product Title'
72
+ end
73
+
74
+ it "parses an element via :from_xml string" do
75
+ Item.property :alternate_link, URI, :from_xml => "xmlns:link[@rel='alternate']/@href"
76
+ stub_get('alternate_link' => 'http://example.com/products/123')
77
+ do_read_one.alternate_link.should == Addressable::URI.parse('http://example.com/products/123')
78
+ end
79
+
80
+ it "parses an element via :from_xml proc" do
81
+ Item.property :alternate_link, URI, :from_xml => lambda { |entry| entry.at("./xmlns:link[@rel='alternate']")['href'] }
82
+ stub_get('alternate_link' => 'http://example.com/products/123')
83
+ do_read_one.alternate_link.should == Addressable::URI.parse('http://example.com/products/123')
84
+ end
85
+
86
+ it "parses and typecasts a date via :from_xml string" do
87
+ Item.property :published, DateTime, :from_xml => "xmlns:published"
88
+ stub_get('published' => '2008-06-12T02:47:04.000Z')
89
+ do_read_one.published.should == DateTime.civil(2008, 6, 12, 2, 47, 4)
90
+ end
91
+
92
+ it "parses a nested element via :from_xml string" do
93
+ Item.property :author_name, String, :from_xml => 'xmlns:author/xmlns:name'
94
+ stub_get('author_name' => 'Carl Porth')
95
+ do_read_one.author_name.should == 'Carl Porth'
96
+ end
97
+
98
+ it "parses a g: namespaced element via :field" do
99
+ Item.property :condition, String, :field => 'g:condition'
100
+ stub_get('g:condition' => 'used')
101
+ do_read_one.condition.should == 'used'
102
+ end
103
+
104
+ it "parses a g: namespace and typecasts via :field" do
105
+ Item.property :customer_id, Integer, :field => 'g:customer_id'
106
+ stub_get('g:customer_id' => '1234')
107
+ do_read_one.customer_id.should == 1234
108
+ end
109
+ end
110
+
111
+ describe "read one" do
112
+
113
+ def do_read_one
114
+ Item.get(@url)
115
+ end
116
+
117
+ def stub_get(options = {})
118
+ FakeWeb.register_uri(:get, @url, :body => xml_entry(options))
119
+ end
120
+
121
+ it_should_behave_like 'parsing an xml entry'
122
+
123
+ end
124
+
125
+ describe "read many" do
126
+
127
+ it 'reads each entry of the feed' do
128
+ FakeWeb.register_uri(:get,
129
+ 'http://www.google.com/base/feeds/items?start-index=1&max-results=250',
130
+ :body => xml_feed(Array.new(3) { xml_entry })
131
+ )
132
+
133
+ Item.all.length.should == 3
134
+ end
135
+
136
+ it 'makes multiple requests when necessary' do
137
+ options = { :total => 501 }
138
+
139
+ # entries 1..250
140
+ FakeWeb.register_uri(:get,
141
+ 'http://www.google.com/base/feeds/items?start-index=1&max-results=250',
142
+ :body => xml_feed(Array.new(250) { xml_entry }, options)
143
+ )
144
+
145
+ # entries 251..500
146
+ FakeWeb.register_uri(:get,
147
+ 'http://www.google.com/base/feeds/items?start-index=251&max-results=250',
148
+ :body => xml_feed(Array.new(250) { xml_entry }, options.merge(:start => 251))
149
+ )
150
+
151
+ # entry 501
152
+ FakeWeb.register_uri(:get,
153
+ 'http://www.google.com/base/feeds/items?start-index=501&max-results=250',
154
+ :body => xml_feed(Array.new(1) { xml_entry }, options.merge(:start => 501))
155
+ )
156
+
157
+ Item.all.length.should == 501
158
+ end
159
+
160
+ it 'obeys offset and limit' do
161
+ FakeWeb.register_uri(:get,
162
+ 'http://www.google.com/base/feeds/items?start-index=2&max-results=10',
163
+ :body => xml_feed((1..10).map { xml_entry }, { :start => 2, :per_page => 10, :total => 20 })
164
+ )
165
+
166
+ Item.all(:offset => 1, :limit => 10).length.should == 10
167
+ end
168
+
169
+ describe "parsing" do
170
+
171
+ def do_read_one
172
+ Item.all.first
173
+ end
174
+
175
+ def stub_get(options = {})
176
+ FakeWeb.register_uri(:get,
177
+ 'http://www.google.com/base/feeds/items?start-index=1&max-results=1',
178
+ :body => xml_feed([ xml_entry(options) ])
179
+ )
180
+ end
181
+
182
+ it_should_behave_like 'parsing an xml entry'
183
+
184
+ end
185
+
186
+ end
187
+
188
+ describe "building xml" do
189
+
190
+ def build_xml(options = {})
191
+ item = Item.new(options)
192
+
193
+ doc = Nokogiri::XML.parse(@adapter.build_xml(item))
194
+ doc.at('./xmlns:entry')
195
+ end
196
+
197
+ it "sets namespaces" do
198
+ xml = build_xml
199
+
200
+ xml.namespaces['xmlns'].should == 'http://www.w3.org/2005/Atom'
201
+ xml.namespaces['xmlns:g'].should == 'http://base.google.com/ns/1.0'
202
+ xml.namespaces['xmlns:gd'].should == 'http://schemas.google.com/g/2005'
203
+ end
204
+
205
+ it "builds an element" do
206
+ Item.property :some_field, String
207
+ xml = build_xml(:some_field => 'value')
208
+
209
+ xml.at('some_field').content.should == 'value'
210
+ end
211
+
212
+ it "builds an element via :field" do
213
+ Item.property :some_field, String, :field => 'another_name'
214
+ xml = build_xml(:some_field => 'value')
215
+
216
+ xml.at('another_name').content.should == 'value'
217
+ end
218
+
219
+ it "builds an element via :to_xml" do
220
+ Item.property :some_link, String,
221
+ :to_xml => lambda { |xml, value| xml.some_link_here :href => value, :type => 'text/html', :rel => 'alternate' }
222
+ xml = build_xml :some_link => 'http://example.com/something'
223
+
224
+ xml.at('some_link_here')['href'].should == 'http://example.com/something'
225
+ xml.at('some_link_here')['rel'].should == 'alternate'
226
+ xml.at('some_link_here')['type'].should == 'text/html'
227
+ end
228
+
229
+ it "doesn't build an element with :to_xml => false" do
230
+ Item.property :ignore_me, String, :to_xml => false
231
+ xml = build_xml :ignore_me => 'Hai'
232
+
233
+ xml.to_s.should_not match(/ignore_me/)
234
+ xml.to_s.should_not match(/Hai/)
235
+ end
236
+ end
237
+
238
+ describe "create" do
239
+
240
+ before(:each) do
241
+ @response = GData::HTTP::Response.new
242
+ @response.status_code = 201
243
+ @matching_xml = /<title>hai<\/title>/
244
+ end
245
+
246
+ def do_create
247
+ Item.property :title, String
248
+ item = Item.new(:title => 'hai')
249
+ item.save.should be_true
250
+ end
251
+
252
+ it "creates a resource" do
253
+ @adapter.gb.should_receive(:post).with("http://www.google.com/base/feeds/items", @matching_xml).and_return(@response)
254
+
255
+ do_create
256
+ end
257
+
258
+ it "creates a resource with dry run" do
259
+ @adapter.gb.should_receive(:post).with("http://www.google.com/base/feeds/items?dry-run=true", @matching_xml).and_return(@response)
260
+ @adapter.dry_run = true
261
+
262
+ do_create
263
+ end
264
+
265
+ end
266
+
267
+ describe "update" do
268
+
269
+ before(:each) do
270
+ Item.property :title, String
271
+
272
+ get_response = GData::HTTP::Response.new
273
+ get_response.status_code = 200
274
+ get_response.body = xml_entry('title' => 'foo', 'id' => 'http://www.google.com/base/feeds/items/123456789')
275
+
276
+ @adapter.gb.stub(:get).and_return(get_response)
277
+
278
+ @put_response = GData::HTTP::Response.new
279
+ @put_response.status_code = 200
280
+ end
281
+
282
+ def do_update
283
+ item = Item.get(1)
284
+ item.title = 'bar'
285
+ item.save
286
+ end
287
+
288
+ it "updates a resource" do
289
+ @adapter.gb.should_receive(:put).with("http://www.google.com/base/feeds/items/123456789", /<title>bar<\/title>/).and_return(@put_response)
290
+
291
+ do_update
292
+ end
293
+
294
+ it "updates a resource with dry run" do
295
+ @adapter.gb.should_receive(:put).with("http://www.google.com/base/feeds/items/123456789?dry-run=true", /<title>bar<\/title>/).and_return(@put_response)
296
+ @adapter.dry_run = true
297
+
298
+ do_update
299
+ end
300
+ end
301
+
302
+ describe "delete" do
303
+
304
+ before(:each) do
305
+ get_response = GData::HTTP::Response.new
306
+ get_response.status_code = 200
307
+ get_response.body = xml_entry('id' => 'http://www.google.com/base/feeds/items/123456789')
308
+
309
+ @adapter.gb.stub(:get).and_return(get_response)
310
+
311
+ @delete_response = GData::HTTP::Response.new
312
+ @delete_response.status_code = 200
313
+ end
314
+
315
+ def do_delete
316
+ Item.get(1).destroy
317
+ end
318
+
319
+ it "deletes a resource" do
320
+ @adapter.gb.should_receive(:delete).with("http://www.google.com/base/feeds/items/123456789").and_return(@delete_response)
321
+
322
+ do_delete
323
+ end
324
+
325
+ it "deletes a resource with dry run" do
326
+ @adapter.gb.should_receive(:delete).with("http://www.google.com/base/feeds/items/123456789?dry-run=true").and_return(@delete_response)
327
+ @adapter.dry_run = true
328
+
329
+ do_delete
330
+ end
331
+
332
+ end
333
+
334
+ end
@@ -0,0 +1,167 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'dm-sweatshop'
3
+
4
+ describe GoogleBase::Product do
5
+
6
+ before(:each) do
7
+ FakeWeb.register_uri(:post, 'https://www.google.com:443/accounts/ClientLogin',
8
+ :response => http_response(<<-CONTENT.margin))
9
+ SID=#{'x' * 182}
10
+ LSID=#{'y' * 182}
11
+ Auth=#{'z' * 182}
12
+ CONTENT
13
+
14
+ @adapter = DataMapper.setup(:default, :adapter => :google_base, :user => 'carl', :password => 'secret')
15
+ @repository = DataMapper.repository(@adapter.name)
16
+
17
+ @url = 'http://www.google.com:80/base/feeds/items/123456789'
18
+ end
19
+
20
+ GoogleBase::Product.fixture {{
21
+ :title => 'A Product',
22
+ :description => 'About me',
23
+ :link => 'http://example.com/products/123',
24
+ :condition => 'new',
25
+ :product_type => 'Electronics > Computers > Laptops',
26
+ :image_link => 'http://example.com/images/123.jpg',
27
+ :product_id => '123',
28
+ :price => '12.34 usd',
29
+ :brand => 'Brand Name',
30
+ :item_type => 'Products'
31
+ }}
32
+
33
+ describe "building xml" do
34
+ def expected_xml(optional_lines = [])
35
+ optional_lines = Array(optional_lines)
36
+
37
+ <<-XML.compress_lines(false)
38
+ <?xml version="1.0" encoding="UTF-8"?>
39
+ <entry xmlns:g="http://base.google.com/ns/1.0" xmlns:gd="http://schemas.google.com/g/2005" xmlns="http://www.w3.org/2005/Atom">
40
+ <title>A Product</title>
41
+ <content>About me</content>
42
+ <link rel="alternate" type="text/html" href="http://example.com/products/123"/>
43
+ <g:condition>new</g:condition>
44
+ <g:product_type>Electronics &gt; Computers &gt; Laptops</g:product_type>
45
+ <g:image_link>http://example.com/images/123.jpg</g:image_link>
46
+ <g:id>123</g:id>
47
+ <g:price>12.34 usd</g:price>
48
+ <g:brand>Brand Name</g:brand>
49
+ <g:item_type>Products</g:item_type>
50
+ #{optional_lines}
51
+ </entry>
52
+ XML
53
+ end
54
+
55
+ it "with required properties" do
56
+ product = GoogleBase::Product.make
57
+
58
+ @adapter.build_xml(product).should match_xml_document(expected_xml)
59
+ end
60
+
61
+ it 'with expiration date' do
62
+ expires_at = Date.today + 7
63
+ product = GoogleBase::Product.make(:expires_at => expires_at)
64
+ xml = expected_xml("<g:expiration_date>#{expires_at.strftime}</g:expiration_date>")
65
+
66
+ @adapter.build_xml(product).should match_xml_document(xml)
67
+ end
68
+
69
+ it "with quantity" do
70
+ product = GoogleBase::Product.make(:quantity => 10)
71
+ xml = expected_xml("<g:quantity>10</g:quantity>")
72
+
73
+ @adapter.build_xml(product).should match_xml_document(xml)
74
+ end
75
+
76
+ it "with payment accepted" do
77
+ product = GoogleBase::Product.make(:payment_accepted => 'Visa,MasterCard,American Express,Discover')
78
+ xml = expected_xml([
79
+ '<g:payment_accepted>Visa</g:payment_accepted>',
80
+ '<g:payment_accepted>MasterCard</g:payment_accepted>',
81
+ '<g:payment_accepted>American Express</g:payment_accepted>',
82
+ '<g:payment_accepted>Discover</g:payment_accepted>'
83
+ ])
84
+
85
+ @adapter.build_xml(product).should match_xml_document(xml)
86
+ end
87
+
88
+ it "with item language" do
89
+ product = GoogleBase::Product.make(:item_language => 'EN')
90
+ xml = expected_xml('<g:item_language>EN</g:item_language>')
91
+
92
+ @adapter.build_xml(product).should match_xml_document(xml)
93
+ end
94
+
95
+ it "with target country" do
96
+ product = GoogleBase::Product.make(:target_country => 'US')
97
+ xml = expected_xml('<g:target_country>US</g:target_country>')
98
+
99
+ @adapter.build_xml(product).should match_xml_document(xml)
100
+ end
101
+ end
102
+
103
+ it "parses xml" do
104
+ received_xml = <<-XML.compress_lines(false)
105
+ <?xml version="1.0" encoding="UTF-8"?>
106
+ <entry xmlns="http://www.w3.org/2005/Atom" xmlns:gm="http://base.google.com/ns-metadata/1.0" xmlns:g="http://base.google.com/ns/1.0" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;D04FSX47eCp7ImA9WxJbFEo.&quot;">
107
+ <id>http://www.google.com/base/feeds/items/123456789</id>
108
+ <published>2009-07-24T22:51:58.000Z</published>
109
+ <updated>2009-07-24T22:51:58.000Z</updated>
110
+ <app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-24T22:51:58.000Z</app:edited>
111
+ <category scheme="http://base.google.com/categories/itemtypes" term="Products"/>
112
+ <title>Product Title</title>
113
+ <content type="html">About me</content>
114
+ <link rel="alternate" type="text/html" href="http://example.com/products/123"/>
115
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/base/feeds/items/123456789"/>
116
+ <link rel="edit" type="application/atom+xml" href="http://www.google.com/base/feeds/items/123456789"/>
117
+ <author>
118
+ <name>Author Name</name>
119
+ <email>anon-123@base.google.com</email>
120
+ </author>
121
+ <g:payment type="text">American Express</g:payment>
122
+ <g:payment type="text">Discover</g:payment>
123
+ <g:payment type="text">Visa</g:payment>
124
+ <g:payment type="text">MasterCard</g:payment>
125
+ <g:condition type="text">new</g:condition>
126
+ <g:product_type type="text">Electronics &gt; Computers &gt; Laptops</g:product_type>
127
+ <g:image_link type="url">http://example.com/images/123.jpg</g:image_link>
128
+ <g:item_language type="text">en</g:item_language>
129
+ <g:id type="text">123</g:id>
130
+ <g:price type="floatUnit">12.34 usd</g:price>
131
+ <g:target_country type="text">US</g:target_country>
132
+ <g:expiration_date type="dateTime">2009-08-23T22:51:58Z</g:expiration_date>
133
+ <g:brand type="text">Brand X</g:brand>
134
+ <g:customer_id type="int">123456789</g:customer_id>
135
+ <g:item_type type="text">Products</g:item_type>
136
+ <gd:feedLink rel="media" href="http://www.google.com/base/feeds/items/123456789/media" countHint="1"/>
137
+ </entry>
138
+ XML
139
+
140
+ FakeWeb.register_uri(:get, @url, :body => received_xml)
141
+ item = GoogleBase::Product.get(@url)
142
+
143
+ item.id.should == 'http://www.google.com/base/feeds/items/123456789'
144
+ item.created_at.should == DateTime.civil(2009,7,24,22,51,58)
145
+ item.updated_at.should == DateTime.civil(2009,7,24,22,51,58)
146
+ item.category.should == 'Products'
147
+ item.title.should == 'Product Title'
148
+ item.description.should == 'About me'
149
+ item.link.should == Addressable::URI.new(:scheme => 'http', :host => 'example.com', :path => 'products/123')
150
+ item.author_name.should == 'Author Name'
151
+ item.author_email.should == 'anon-123@base.google.com'
152
+ item.payment_accepted.should == 'American Express,Discover,Visa,MasterCard'
153
+ item.condition.should == 'new'
154
+ item.product_type.should == 'Electronics > Computers > Laptops'
155
+ item.image_link.should == Addressable::URI.new(:scheme => 'http', :host => 'example.com', :path => 'images/123.jpg')
156
+ item.item_language.should == 'en'
157
+ item.product_id.should == '123'
158
+ item.price.should == '12.34 usd'
159
+ item.target_country.should == 'US'
160
+ item.expires_at.should == DateTime.civil(2009,8,23,22,51,58)
161
+ item.brand.should == 'Brand X'
162
+ item.customer_id.should == 123456789
163
+ item.item_type.should == 'Products'
164
+ item.feed_link.should == Addressable::URI.new(:scheme => 'http', :host => 'www.google.com', :path => 'base/feeds/items/123456789/media')
165
+ end
166
+
167
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec'
2
+ require 'rubygems'
3
+ require 'fakeweb'
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+
8
+ require 'xml_helpers'
9
+ require 'spec_matchers'
10
+ require 'googlebase'
11
+
12
+ Spec::Runner.configure do |config|
13
+
14
+ config.include(XmlHelpers)
15
+
16
+ config.before(:all) do
17
+ FakeWeb.allow_net_connect = false
18
+ end
19
+
20
+ config.after(:each) do
21
+ FakeWeb.clean_registry
22
+ end
23
+
24
+ end
@@ -0,0 +1,59 @@
1
+ Spec::Matchers.define :match_xml_document do |expected|
2
+ match do |actual|
3
+ actual_doc = Nokogiri.XML(actual) { |cfg| cfg.noblanks }
4
+ expected_doc = Nokogiri.XML(expected) { |cfg| cfg.noblanks }
5
+
6
+ actual_doc.encoding.should == expected_doc.encoding
7
+ actual_doc.root.should match_xml_node(expected_doc.root)
8
+ end
9
+ end
10
+
11
+ class MatchXMLNode
12
+ def initialize(expected)
13
+ @expected = expected
14
+ end
15
+
16
+ def matches?(actual)
17
+ @actual = actual
18
+
19
+ if @expected.namespace
20
+ return false if @actual.namespace.nil?
21
+ return false if @actual.namespace.prefix != @expected.namespace.prefix
22
+ else
23
+ return false if not @actual.namespace.nil?
24
+ end
25
+
26
+ return false if @actual.name != @expected.name
27
+ return false if @actual.attributes.map { |k,v| [k,v.to_s] }.to_hash !=
28
+ @expected.attributes.map { |k,v| [k,v.to_s] }.to_hash
29
+
30
+ @actual.children.each_with_index do |child, i|
31
+ if child.text?
32
+ return false if @expected.children[i].nil?
33
+ return false if child.text != @expected.children[i].text
34
+ else
35
+ child.should match_xml_node(@expected.children[i])
36
+ end
37
+ end
38
+
39
+ true
40
+ end
41
+
42
+ def failure_message_for_should
43
+ @actual_part = @actual.dup
44
+ @actual_part.content = nil if @actual_part.child && !@actual_part.child.text?
45
+
46
+ @expected_part = @expected.dup
47
+ @expected_part.content = nil if @expected_part.child && !@expected_part.child.text?
48
+
49
+ "expected:\n#{@actual_part.inspect}\n to match node:\n#{@expected_part.inspect}\n but it didn't"
50
+ end
51
+
52
+ def failure_message_for_should_not
53
+ "expected:\n#{@actual_part.inspect}\n not to match node:\n#{@expected_part.inspect}\n but it did"
54
+ end
55
+ end
56
+
57
+ def match_xml_node(expected)
58
+ MatchXMLNode.new(expected)
59
+ end
@@ -0,0 +1,127 @@
1
+ module XmlHelpers
2
+ def xml_feed(entries, options = {})
3
+ start = options[:start] || 1
4
+ per_page = options[:per_page] || 250
5
+ total = options[:total] || entries.length
6
+
7
+ next_link = if total >= start + per_page
8
+ "<link rel='next' type='application/atom+xml' href='http://www.google.com/base/feeds/items?start-index=#{start + per_page}&amp;max-results=#{per_page}'/>"
9
+ else
10
+ ""
11
+ end
12
+
13
+ <<-XML.compress_lines(false)
14
+ <?xml version='1.0' encoding='UTF-8'?>
15
+ <feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns:gm='http://base.google.com/ns-metadata/1.0' xmlns:g='http://base.google.com/ns/1.0' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:gd='http://schemas.google.com/g/2005' gd:etag='W/&quot;Dk4BRHc5cSp7ImA9WxJWFkw.&quot;'>
16
+ <id>http://www.google.com/base/feeds/items</id>
17
+ <updated>2009-06-21T20:09:15.929Z</updated>
18
+ <title>Items matching query: [customer id(int):123456789]</title>
19
+ <link rel='alternate' type='text/html' href='http://base.google.com'/>
20
+ <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.google.com/base/feeds/items'/>
21
+ <link rel='http://schemas.google.com/g/2005#post' type='application/atom+xml' href='http://www.google.com/base/feeds/items'/>
22
+ <link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='http://www.google.com/base/feeds/items/batch'/>
23
+ <link rel='self' type='application/atom+xml' href='http://www.google.com/base/feeds/items?start-index=#{start}&amp;max-results=#{per_page}'/>
24
+ #{next_link}
25
+ <author>
26
+ <name>Google Inc.</name>
27
+ <email>base@google.com</email>
28
+ </author>
29
+ <generator version='1.0' uri='http://base.google.com'>GoogleBase</generator>
30
+ <openSearch:totalResults>#{total}</openSearch:totalResults>
31
+ <openSearch:startIndex>#{start}</openSearch:startIndex>
32
+ <openSearch:itemsPerPage>#{per_page}</openSearch:itemsPerPage>
33
+ <g:customer_id type='int'>123456789</g:customer_id>
34
+ #{entries.join}
35
+ XML
36
+ end
37
+
38
+ def xml_entry(updated_options = {})
39
+ options = Hash.new { |hash, key| raise "Don't know #{key}" }
40
+
41
+ id = updated_options.delete('id') || 'http://www.google.com/base/feeds/items/123456789'
42
+ g_id = updated_options.delete('g:id') || '123'
43
+
44
+ options.merge!({
45
+ 'id' => id,
46
+ 'published' => '2008-06-12T02:47:04.000Z',
47
+ 'updated' => '2009-06-19T23:42:07.000Z',
48
+ 'edited' => '2009-06-19T23:42:07.000Z',
49
+
50
+ 'title' => 'MacBook Pro',
51
+ 'content' => 'A computer',
52
+
53
+ 'alternate_link' => "http://example.com/products/#{g_id}",
54
+ 'self_link' => id,
55
+ 'edit_link' => id,
56
+
57
+ 'author_name' => 'Store Name',
58
+
59
+ 'g:condition' => 'new',
60
+ 'g:product_type' => 'Electronics &gt; Computers &gt; Laptops',
61
+ 'g:customer' => 'Store Name',
62
+ 'g:image_link' => "http://example.com/images/#{g_id}.jpg",
63
+ 'g:item_language' => 'EN',
64
+ 'g:id' => g_id,
65
+ 'g:price' => '12.34 usd',
66
+ 'g:target_country' => 'US',
67
+ 'g:expiration_date' => '2009-07-19T23:42:07Z',
68
+ 'g:brand' => 'Apple Inc.',
69
+ 'g:customer_id' => '123456789',
70
+ 'g:item_type' => 'Products',
71
+
72
+ 'gd:feedLink' => "#{id}/media"
73
+ })
74
+
75
+ updated_options.keys.each { |key| raise "Don't know #{key}" unless options.has_key?(key) }
76
+ options.merge!(updated_options)
77
+
78
+ <<-XML.compress_lines(false)
79
+ <?xml version='1.0' encoding='UTF-8'?>
80
+ <entry xmlns='http://www.w3.org/2005/Atom' xmlns:gm='http://base.google.com/ns-metadata/1.0' xmlns:g='http://base.google.com/ns/1.0' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:gd='http://schemas.google.com/g/2005' gd:etag='W/&quot;CEEFRn47eCp7ImA9WxJWGE8.&quot;'>
81
+ <id>#{options['id']}</id>
82
+ <published>#{options['published']}</published>
83
+ <updated>#{options['updated']}</updated>
84
+ <app:edited xmlns:app='http://www.w3.org/2007/app'>#{options['edited']}</app:edited>
85
+ <category scheme='http://base.google.com/categories/itemtypes' term='Products'/>
86
+ <title>#{options['title']}</title>
87
+ <content type='html'>#{options['content']}</content>
88
+ <link rel='alternate' type='text/html' href='#{options['alternate_link']}'/>
89
+ <link rel='self' type='application/atom+xml' href='#{options['self_link']}'/>
90
+ <link rel='edit' type='application/atom+xml' href='#{options['edit_link']}'/>
91
+ <author>
92
+ <name>#{options['author_name']}</name>
93
+ <email>anon-123@base.google.com</email>
94
+ </author>
95
+ <g:condition type='text'>#{options['g:condition']}</g:condition>
96
+ <g:product_type type='text'>#{options['g:product_type']}</g:product_type>
97
+ <g:customer type='text'>#{options['g:customer']}</g:customer>
98
+ <g:image_link type='url'>#{options['g:image_link']}</g:image_link>
99
+ <g:item_language type='text'>#{options['g:item_language']}</g:item_language>
100
+ <g:id type='text'>#{options['g:id']}</g:id>
101
+ <g:price type='floatUnit'>#{options['g:price']}</g:price>
102
+ <g:target_country type='text'>#{options['g:target_country']}</g:target_country>
103
+ <g:expiration_date type='dateTime'>#{options['g:expiration_date']}</g:expiration_date>
104
+ <g:brand type='text'>#{options['g:brand']}</g:brand>
105
+ <g:customer_id type='int'>#{options['g:customer_id']}</g:customer_id>
106
+ <g:item_type type='text'>#{options['g:item_type']}</g:item_type>
107
+ <gd:feedLink rel='media' href='#{options['gd:feedLink']}' countHint='1'/>
108
+ </entry>
109
+ XML
110
+ end
111
+
112
+ def http_response(content, options = {})
113
+ <<-HTTP.margin
114
+ HTTP/1.1 200 OK
115
+ Content-Type: text/plain
116
+ Cache-control: no-cache, no-store
117
+ Pragma: no-cache
118
+ Expires: Mon, 01-Jan-1990 00:00:00 GMT
119
+ Date: Sat, 20 Jun 2009 22:04:48 GMT
120
+ X-Content-Type-Options: nosniff
121
+ Content-Length: #{content.length}
122
+ Server: GFE/2.0
123
+
124
+ #{content}
125
+ HTTP
126
+ end
127
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-googlebase
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Carl Porth
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-22 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: dm-core
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 0.10.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: dm-types
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.10.2
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: dm-validations
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.10.2
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: gdata
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: dm-sweatshop
67
+ type: :development
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ version: 0.10.0
74
+ version:
75
+ - !ruby/object:Gem::Dependency
76
+ name: fakeweb
77
+ type: :development
78
+ version_requirement:
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ version: 1.2.8
84
+ version:
85
+ description:
86
+ email: badcarl@gmail.com
87
+ executables: []
88
+
89
+ extensions: []
90
+
91
+ extra_rdoc_files:
92
+ - LICENSE
93
+ - README.rdoc
94
+ files:
95
+ - .document
96
+ - .gitignore
97
+ - LICENSE
98
+ - README.rdoc
99
+ - Rakefile
100
+ - VERSION
101
+ - dm-googlebase.gemspec
102
+ - lib/googlebase.rb
103
+ - lib/googlebase/adapter.rb
104
+ - lib/googlebase/product.rb
105
+ - lib/googlebase/product_properties.rb
106
+ - spec/googlebase/adapter_spec.rb
107
+ - spec/googlebase/product_spec.rb
108
+ - spec/spec_helper.rb
109
+ - spec/spec_matchers.rb
110
+ - spec/xml_helpers.rb
111
+ has_rdoc: true
112
+ homepage: http://github.com/badcarl/dm-googlebase
113
+ licenses: []
114
+
115
+ post_install_message:
116
+ rdoc_options:
117
+ - --charset=UTF-8
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: "0"
125
+ version:
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: "0"
131
+ version:
132
+ requirements: []
133
+
134
+ rubyforge_project:
135
+ rubygems_version: 1.3.5
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: A DataMapper adapter for Google Base
139
+ test_files:
140
+ - spec/googlebase/adapter_spec.rb
141
+ - spec/googlebase/product_spec.rb
142
+ - spec/spec_helper.rb
143
+ - spec/spec_matchers.rb
144
+ - spec/xml_helpers.rb