dm-rest-adapter 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,26 @@
1
+ History.txt
2
+ LICENSE
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ TODO
7
+ lib/rest_adapter.rb
8
+ lib/rest_adapter/version.rb
9
+ spec/create_spec.rb
10
+ spec/delete_spec.rb
11
+ spec/read_spec.rb
12
+ spec/ruby_forker.rb
13
+ spec/spec.opts
14
+ spec/spec_helper.rb
15
+ spec/update_spec.rb
16
+ stories/all.rb
17
+ stories/crud/create
18
+ stories/crud/delete
19
+ stories/crud/read
20
+ stories/crud/stories.rb
21
+ stories/crud/update
22
+ stories/helper.rb
23
+ stories/resources/helpers/book.rb
24
+ stories/resources/helpers/story_helper.rb
25
+ stories/resources/steps/read.rb
26
+ stories/resources/steps/using_rest_adapter.rb
@@ -1,4 +1,3 @@
1
- dm-rest-adapter
2
- ==================
1
+ = dm-rest-adapter
3
2
 
4
3
  A DataMapper adapter for REST Web Services
data/Rakefile CHANGED
@@ -1,40 +1,33 @@
1
1
  require 'rubygems'
2
2
  require 'spec'
3
- require 'rake/clean'
4
- require 'rake/gempackagetask'
5
3
  require 'spec/rake/spectask'
6
4
  require 'pathname'
7
5
 
8
- CLEAN.include '{log,pkg}/'
9
-
10
- spec = Gem::Specification.new do |s|
11
- s.name = 'dm-rest-adapter'
12
- s.version = '0.9.2'
13
- s.platform = Gem::Platform::RUBY
14
- s.has_rdoc = true
15
- s.extra_rdoc_files = %w[ README LICENSE TODO ]
16
- s.summary = 'REST Adapter for DataMapper'
17
- s.description = s.summary
18
- s.author = 'Potomac Ruby Hackers'
19
- s.email = 'potomac-ruby-hackers@googlegroups.com'
20
- s.homepage = 'http://github.com/pjb3/dm-more/tree/master/adapters/dm-rest-adapter'
21
- s.require_path = 'lib'
22
- s.files = FileList[ '{lib,spec}/**/*.rb', 'spec/spec.opts', 'Rakefile', *s.extra_rdoc_files ]
23
- s.add_dependency('dm-core', "=#{s.version}")
24
- end
6
+ ROOT = Pathname(__FILE__).dirname.expand_path
7
+ require ROOT + 'lib/rest_adapter/version'
8
+
9
+ AUTHOR = "Potomac Ruby Hackers"
10
+ EMAIL = "potomac-ruby-hackers@googlegroups.com"
11
+ GEM_NAME = "dm-rest-adapter"
12
+ GEM_VERSION = DataMapper::More::RestAdapter::VERSION
13
+ GEM_DEPENDENCIES = [["dm-core", GEM_VERSION]]
14
+ GEM_CLEAN = ["log", "pkg"]
15
+ GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO ] }
16
+
17
+ PROJECT_NAME = "datamapper"
18
+ PROJECT_URL = "http://github.com/pjb3/dm-more/tree/master/adapters/dm-rest-adapter"
19
+ PROJECT_DESCRIPTION = PROJECT_SUMMARY = "REST Adapter for DataMapper"
20
+
21
+ require ROOT.parent.parent + 'tasks/hoe'
25
22
 
26
23
  task :default => [ :spec ]
27
24
 
28
25
  WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
29
26
  SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
30
27
 
31
- Rake::GemPackageTask.new(spec) do |pkg|
32
- pkg.gem_spec = spec
33
- end
34
-
35
- desc "Install #{spec.name} #{spec.version}"
28
+ desc "Install #{GEM_NAME} #{GEM_VERSION}"
36
29
  task :install => [ :package ] do
37
- sh "#{SUDO} gem install pkg/#{spec.name}-#{spec.version} --no-update-sources", :verbose => false
30
+ sh "#{SUDO} gem install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources", :verbose => false
38
31
  end
39
32
 
40
33
  desc 'Run specifications'
@@ -48,5 +41,6 @@ end
48
41
 
49
42
  desc "Run all stories"
50
43
  task :stories do
44
+ # TODO Re-migrate the book service or else you won't have test data!
51
45
  ruby "stories/all.rb --colour --format plain"
52
46
  end
data/TODO CHANGED
@@ -1,2 +1 @@
1
1
  TODO
2
- ====
@@ -1,9 +1,10 @@
1
1
  require 'rubygems'
2
- gem 'dm-core', '=0.9.2'
2
+ require 'pathname'
3
+ require Pathname(__FILE__).dirname + 'rest_adapter/version'
4
+ gem 'dm-core', DataMapper::More::RestAdapter::VERSION
3
5
  require 'dm-core'
4
6
  require 'extlib'
5
7
  require 'dm-serializer'
6
- require 'pathname'
7
8
  require 'net/http'
8
9
  require 'rexml/document'
9
10
 
@@ -13,36 +14,26 @@ module DataMapper
13
14
  module Adapters
14
15
  class RestAdapter < AbstractAdapter
15
16
  include Extlib
16
-
17
- # def read_one(query)
18
- # raise NotImplementedError
19
- # end
20
- #
21
- # def update(attributes, query)
22
- # raise NotImplementedError
23
- # end
24
- #
25
- # def delete(query)
26
- # raise NotImplementedError
27
- # end
28
-
17
+
29
18
  # Creates a new resource in the specified repository.
30
19
  def create(resources)
31
- success = true
20
+ count = 0
32
21
  resources.each do |resource|
33
- resource_name = Inflection.underscore(resource.class.name.downcase)
22
+ resource_name = Inflection.underscore(resource.class.name)
34
23
  result = http_post("/#{resource_name.pluralize}.xml", resource.to_xml)
35
24
  # TODO: Raise error if cannot reach server
36
- success = success && result.instance_of?(Net::HTTPCreated)
25
+ success = result.instance_of?(Net::HTTPCreated)
37
26
  if success
38
- updated_resource = parse_resource(result.body, resource.class)
39
- resource.id = updated_resource.id
27
+ count += 1
28
+ # TODO: Fix commented out code below to work through the identity_map of the repository
29
+ # values = parse_resource(result.body, resource.class)
30
+ # resource.id = updated_resource.id
40
31
  end
41
32
  # TODO: We're not using the response to update the DataMapper::Resource with the newly acquired ID!!!
42
33
  end
43
- success
34
+ count
44
35
  end
45
-
36
+
46
37
  # read_set
47
38
  #
48
39
  # Examples of query string:
@@ -55,73 +46,107 @@ module DataMapper
55
46
  # IN PROGRESS
56
47
  # TODO: Need to account for query.conditions (i.e., [[:eql, #<Property:Book:id>, 1]] for books/1)
57
48
  def read_many(query)
58
- resource_name = Inflection.underscore(query.model.name.downcase)
59
- case query.conditions
60
- when []
61
- read_set_all(repository, query, resource_name)
62
- else
63
- read_set_for_condition(repository, query, resource)
49
+ resource_name = Inflection.underscore(query.model.name)
50
+ Collection.new(query) do |collection|
51
+ case query.conditions
52
+ when []
53
+ resources_meta = read_set_all(repository, query, resource_name)
54
+ else
55
+ resources_meta = read_set_for_condition(repository, query, resource_name)
56
+ end
57
+ resources_meta.each do |resource_meta|
58
+ if resource_meta.has_key?(:associations)
59
+ load_nested_resources_from resource_meta[:associations], query
60
+ end
61
+ collection.load(resource_meta[:values])
62
+ end
64
63
  end
65
64
  end
66
-
65
+
67
66
  def read_one(query)
68
- # puts "---------------- QUERY: #{query} #{query.inspect}"
69
- id = query.conditions.first[2]
70
- # KLUGE: Again, we're assuming below that we're dealing with a pluralized resource mapping
67
+ resource = nil
71
68
  resource_name = resource_name_from_query(query)
72
- response = http_get("/#{resource_name.pluralize}/#{id}.xml")
73
-
74
- # KLUGE: Rails returns HTML if it can't find a resource. A properly RESTful app would return a 404, right?
75
- return nil if response.is_a? Net::HTTPNotFound || response.content_type == "text/html"
76
-
77
- data = response.body
78
- res = parse_resource(data, query.model)
79
- res
69
+ resources_meta = nil
70
+ if query.conditions.empty? && query.limit == 1
71
+ results = read_set_all(repository, query, resource_name)
72
+ resource_meta = results.first unless results.empty?
73
+ else
74
+ id = query.conditions.first[2]
75
+ # KLUGE: Again, we're assuming below that we're dealing with a pluralized resource mapping
76
+
77
+ response = http_get("/#{resource_name.pluralize}/#{id}.xml")
78
+
79
+ # KLUGE: Rails returns HTML if it can't find a resource. A properly RESTful app would return a 404, right?
80
+ return nil if response.is_a? Net::HTTPNotFound || response.content_type == "text/html"
81
+
82
+ data = response.body
83
+ resource_meta = parse_resource(data, query.model, query)
84
+ end
85
+ if resource_meta
86
+ if resource_meta.has_key?(:associations)
87
+ load_nested_resources_from resource_meta[:associations], query
88
+ end
89
+ resource = query.model.load(resource_meta[:values], query)
90
+ end
91
+ resource
80
92
  end
81
-
93
+
82
94
  def update(attributes, query)
83
- # TODO update for v0.9.2
95
+ # TODO What if we have a compound key?
84
96
  raise NotImplementedError.new unless is_single_resource_query? query
85
97
  id = query.conditions.first[2]
86
- resource = query.model.new
98
+ resource = nil
99
+ query.repository.scope do
100
+ resource = query.model.get(id)
101
+ end
87
102
  attributes.each do |attr, val|
88
103
  resource.send("#{attr.name}=", val)
89
104
  end
90
105
  # KLUGE: Again, we're assuming below that we're dealing with a pluralized resource mapping
91
- http_put("/#{resource_name_from_query(query).pluralize}/#{id}.xml", resource.to_xml)
106
+ res = http_put("/#{resource_name_from_query(query).pluralize}/#{id}.xml", resource.to_xml)
92
107
  # TODO: Raise error if cannot reach server
108
+ res.kind_of?(Net::HTTPSuccess) ? 1 : 0
93
109
  end
94
-
110
+
95
111
  def delete(query)
96
- #puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> QUERY: #{query} #{query.inspect}"
97
- # TODO update for v0.9.2
98
112
  raise NotImplementedError.new unless is_single_resource_query? query
99
113
  id = query.conditions.first[2]
100
- http_delete("/#{resource_name_from_query(query).pluralize}/#{id}.xml")
114
+ res = http_delete("/#{resource_name_from_query(query).pluralize}/#{id}.xml")
115
+ res.kind_of?(Net::HTTPSuccess) ? 1 : 0
116
+ end
117
+
118
+ protected
119
+ def load_nested_resources_from(nested_resources, query)
120
+ nested_resources.each do |resource_meta|
121
+ # TODO: Houston, we have a problem. Model#load expects a Query. When we're nested, we don't have a query yet...
122
+ #resource_meta[:model].load(resource_meta[:values])
123
+ #if resource_meta.has_key? :associations
124
+ # load_nested_resources_from resource_meta, query
125
+ #end
126
+ end
101
127
  end
102
-
103
- protected
128
+
104
129
  def read_set_all(repository, query, resource_name)
105
130
  # TODO: how do we know whether the resource we're talking to is singular or plural?
106
131
  res = http_get("/#{resource_name.pluralize}.xml")
107
132
  data = res.body
108
- parse_resources(data, query.model)
133
+ parse_resources(data, query.model, query)
109
134
  # TODO: Raise error if cannot reach server
110
135
  end
111
-
136
+
112
137
  # GET /books/4200
113
138
  def read_set_for_condition(repository, query, resource_name)
114
139
  # More complex conditions
115
140
  raise NotImplementedError.new
116
- end
117
-
141
+ end
142
+
118
143
  # query.conditions like [[:eql, #<Property:Book:id>, 4200]]
119
144
  def is_single_resource_query?(query)
120
145
  query.conditions.length == 1 && query.conditions.first.first == :eql && query.conditions.first[1].name == :id
121
146
  end
122
-
147
+
123
148
  def http_put(uri, data = nil)
124
- request { |http| http.put(uri, data) }
149
+ request { |http| http.put(uri, data, {"Content-Type", "application/xml"}) }
125
150
  end
126
151
 
127
152
  def http_post(uri, data)
@@ -129,11 +154,11 @@ module DataMapper
129
154
  end
130
155
 
131
156
  def http_get(uri)
132
- request { |http| http.get(uri) }
157
+ request { |http| http.get(uri, {"Content-Type", "application/xml"}) }
133
158
  end
134
159
 
135
160
  def http_delete(uri)
136
- request { |http| http.delete(uri) }
161
+ request { |http| http.delete(uri, {"Content-Type", "application/xml"}) }
137
162
  end
138
163
 
139
164
  def request(&block)
@@ -142,30 +167,45 @@ module DataMapper
142
167
  res = yield(http)
143
168
  end
144
169
  res
145
- end
170
+ end
146
171
 
147
- def resource_from_rexml(entity_element, dm_model_class)
148
- resource = dm_model_class.new
172
+ def values_from_rexml(entity_element, dm_model_class)
173
+ resource = {}
174
+ resource[:values] = []
149
175
  entity_element.elements.each do |field_element|
150
- attribute = resource.attributes.find do |name, val|
176
+ attribute = dm_model_class.properties(repository.name).find do |property|
151
177
  # *MUST* use Inflection.underscore on the XML as Rails converts '_' to '-' in the XML
152
- name.to_s == Inflection.underscore(field_element.name.to_s)
178
+ property.name.to_s == Inflection.underscore(field_element.name.to_s)
179
+ end
180
+ if attribute
181
+ resource[:values] << field_element.text
182
+ next
183
+ end
184
+ association = dm_model_class.relationships.find do |name, dm_relationship|
185
+ field_element.name.to_s == Inflection.pluralize(Inflection.underscore(dm_relationship.child_model.to_s))
186
+ end
187
+ if association
188
+ field_element.each_element do |associated_element|
189
+ model = association[1].child_model
190
+ (resource[:associations] ||= []) << {
191
+ :model => model,
192
+ :value => values_from_rexml(associated_element, association[1].child_model)
193
+ }
194
+ end
153
195
  end
154
- resource.send("#{Inflection.underscore(attribute[0])}=", field_element.text) if attribute
155
196
  end
156
- resource.instance_eval { @new_record= false }
157
197
  resource
158
198
  end
159
199
 
160
- def parse_resource(xml, dm_model_class)
200
+ def parse_resource(xml, dm_model_class, query = nil)
161
201
  doc = REXML::Document::new(xml)
162
202
  # TODO: handle singular resource case as well....
163
203
  entity_element = REXML::XPath.first(doc, "/#{resource_name_from_model(dm_model_class)}")
164
204
  return nil unless entity_element
165
- resource_from_rexml(entity_element, dm_model_class)
205
+ values_from_rexml(entity_element, dm_model_class)
166
206
  end
167
-
168
- def parse_resources(xml, dm_model_class)
207
+
208
+ def parse_resources(xml, dm_model_class, query = nil)
169
209
  doc = REXML::Document::new(xml)
170
210
  # # TODO: handle singular resource case as well....
171
211
  # array = XPath(doc, "/*[@type='array']")
@@ -174,17 +214,17 @@ module DataMapper
174
214
  # else
175
215
  resource_name = resource_name_from_model dm_model_class
176
216
  doc.elements.collect("#{resource_name.pluralize}/#{resource_name}") do |entity_element|
177
- resource_from_rexml(entity_element, dm_model_class)
217
+ values_from_rexml(entity_element, dm_model_class)
178
218
  end
179
- end
180
-
219
+ end
220
+
181
221
  def resource_name_from_model(model)
182
- Inflection.underscore(model.name.downcase)
222
+ Inflection.underscore(model.name)
183
223
  end
184
-
224
+
185
225
  def resource_name_from_query(query)
186
226
  resource_name_from_model(query.model)
187
227
  end
188
228
  end
189
229
  end
190
- end
230
+ end
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+ module More
3
+ class RestAdapter
4
+ VERSION = "0.9.3"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+ require 'spec_helper'
3
+
4
+ describe 'A REST adapter' do
5
+
6
+ before do
7
+ @adapter = DataMapper::Repository.adapters[:default]
8
+ end
9
+
10
+ describe 'when saving a resource' do
11
+
12
+ before do
13
+ @book = Book.new(:title => 'Hello, World!', :author => 'Anonymous')
14
+ end
15
+
16
+ it 'should make an HTTP Post' do
17
+ @adapter.should_receive(:http_post).with('/books.xml', @book.to_xml)
18
+ @book.save
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+ require 'spec_helper'
3
+
4
+ describe 'A REST adapter' do
5
+
6
+ before do
7
+ @adapter = DataMapper::Repository.adapters[:default]
8
+ end
9
+
10
+ describe 'when deleting an existing resource' do
11
+ before do
12
+ @book = Book.new(:title => 'Hello, World!', :author => 'Anonymous')
13
+ @book.stub!(:new_record?).and_return(false)
14
+ end
15
+
16
+ it 'should do an HTTP DELETE' do
17
+ @adapter.should_receive(:http_delete)
18
+ @book.destroy
19
+ end
20
+
21
+ end
22
+ end
@@ -1,43 +1,16 @@
1
- require 'pathname'
2
- require Pathname(__FILE__).dirname.parent.expand_path + 'lib/rest_adapter'
3
-
4
- DataMapper.setup(:default, {
5
- :adapter => 'rest',
6
- :format => 'xml',
7
- :host => 'localhost',
8
- :port => '3001'
9
- })
10
-
11
- class Book
12
- include DataMapper::Resource
13
- property :id, Integer, :serial => true
14
- property :title, String
15
- property :author, String
16
- property :created_at, DateTime
17
- end
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+ require 'spec_helper'
18
3
 
19
4
  describe 'A REST adapter' do
20
-
5
+
21
6
  before do
22
7
  @adapter = DataMapper::Repository.adapters[:default]
23
8
  end
24
-
25
- describe 'when saving a resource' do
26
-
27
- before do
28
- @book = Book.new(:title => 'Hello, World!', :author => 'Anonymous')
29
- end
30
-
31
- it 'should make an HTTP Post' do
32
- @adapter.should_receive(:http_post).with('/books.xml', @book.to_xml)
33
- @book.save
34
- end
35
- end
36
-
9
+
37
10
  describe 'when getting one resource' do
38
11
 
39
12
  describe 'if the resource exists' do
40
-
13
+
41
14
  before do
42
15
  book_xml = <<-BOOK
43
16
  <?xml version='1.0' encoding='UTF-8'?>
@@ -54,20 +27,25 @@ describe 'A REST adapter' do
54
27
  @response.stub!(:body).and_return(book_xml)
55
28
  @adapter.stub!(:http_get).and_return(@response)
56
29
  end
57
-
30
+
58
31
  it 'should return the resource' do
59
32
  book = Book.get(@id)
60
33
  book.should_not be_nil
61
34
  book.id.should be_an_instance_of(Fixnum)
62
35
  book.id.should == 1
63
36
  end
64
-
37
+
65
38
  it 'should do an HTTP GET' do
66
39
  @adapter.should_receive(:http_get).with('/books/1.xml').and_return(@response)
67
40
  Book.get(@id)
68
41
  end
42
+
43
+ it "it be equal to itself" do
44
+ Book.get(@id).should == Book.get(@id)
45
+ end
69
46
  end
70
-
47
+
48
+
71
49
  describe 'if the resource does not exist' do
72
50
  it 'should return nil' do
73
51
  @id = 1
@@ -80,9 +58,9 @@ describe 'A REST adapter' do
80
58
  end
81
59
  end
82
60
  end
83
-
61
+
84
62
  describe 'when getting all resource of a particular type' do
85
- before do
63
+ before do
86
64
  books_xml = <<-BOOK
87
65
  <?xml version='1.0' encoding='UTF-8'?>
88
66
  <books type='array'>
@@ -92,7 +70,7 @@ describe 'A REST adapter' do
92
70
  <id type='integer'>1</id>
93
71
  <title>The Dispossed</title>
94
72
  <updated-at type='datetime'>2008-06-08T17:02:28Z</updated-at>
95
- </book>
73
+ </book>
96
74
  <book>
97
75
  <author>Stephen King</author>
98
76
  <created-at type='datetime'>2008-06-08T17:03:07Z</created-at>
@@ -106,48 +84,18 @@ describe 'A REST adapter' do
106
84
  @response.stub!(:body).and_return(books_xml)
107
85
  @adapter.stub!(:http_get).and_return(@response)
108
86
  end
109
-
87
+
110
88
  it 'should get a non-empty list' do
111
89
  Book.all.should_not be_empty
112
90
  end
113
-
91
+
114
92
  it 'should receive one Resource for each entity in the XML' do
115
93
  Book.all.size.should == 2
116
94
  end
117
-
95
+
118
96
  it 'should do an HTTP GET' do
119
97
  @adapter.should_receive(:http_get).and_return(@response)
120
- Book.all
121
- end
122
- end
123
-
124
- describe 'when updating an existing resource' do
125
- before do
126
- @books_xml = "<book><id type='integer'>42</id><title>The Dispossed</title><author>Ursula K LeGuin</author><created-at type='datetime'>2008-06-08T17:02:28Z</created-at></book>"
127
- @book = Book.new(:id => 42,
128
- :title => 'The Dispossed',
129
- :author => 'Ursula K LeGuin',
130
- :created_at => DateTime.parse('2008-06-08T17:02:28Z'))
131
- @book.stub!(:new_record?).and_return(false)
132
- @book.stub!(:dirty?).and_return(true)
133
- end
134
-
135
- it 'should do an HTTP PUT' do
136
- @adapter.should_receive(:http_put).with('/books/42.xml', @book.to_xml)
137
- @book.save
138
- end
139
- end
140
-
141
- describe 'when deleting an existing resource' do
142
- before do
143
- @book = Book.new(:title => 'Hello, World!', :author => 'Anonymous')
144
- @book.stub!(:new_record?).and_return(false)
145
- end
146
-
147
- it 'should do an HTTP DELETE' do
148
- @adapter.should_receive(:http_delete)
149
- @book.destroy
150
- end
151
-
98
+ Book.first
99
+ end
152
100
  end
153
- end
101
+ end
@@ -10,4 +10,4 @@ module RubyForker
10
10
  cmd << " 2> #{stderr}" unless stderr.nil?
11
11
  `#{cmd}`
12
12
  end
13
- end
13
+ end
@@ -0,0 +1,17 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.parent.expand_path + 'lib/rest_adapter'
3
+
4
+ DataMapper.setup(:default, {
5
+ :adapter => 'rest',
6
+ :format => 'xml',
7
+ :host => 'localhost',
8
+ :port => '3001'
9
+ })
10
+
11
+ class Book
12
+ include DataMapper::Resource
13
+ property :author, String
14
+ property :created_at, DateTime
15
+ property :id, Integer, :serial => true
16
+ property :title, String
17
+ end
@@ -0,0 +1,36 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+ require 'spec_helper'
3
+
4
+ describe 'A REST adapter' do
5
+
6
+ describe 'when updating an existing resource' do
7
+ before do
8
+ @books_xml = <<-XML
9
+ <book>
10
+ <id type='integer'>42</id>
11
+ <title>Starship Troopers</title>
12
+ <author>Robert Heinlein</author>
13
+ <created-at type='datetime'>2008-06-08T17:02:28Z</created-at>
14
+ </book>
15
+ XML
16
+ repository do |repo|
17
+ @repository = repo
18
+ @book = Book.new(:id => 42,
19
+ :title => 'Starship Troopers',
20
+ :author => 'Robert Heinlein',
21
+ :created_at => DateTime.parse('2008-06-08T17:02:28Z'))
22
+ @book.instance_eval { @new_record = false }
23
+ @repository.identity_map(Book).set(@book.key, @book)
24
+ @book.title = "Mary Had a Little Lamb"
25
+ end
26
+ end
27
+
28
+ it 'should do an HTTP PUT' do
29
+ adapter = @repository.adapter #DataMapper::Repository.adapters[:default]
30
+ adapter.should_receive(:http_put).with('/books/42.xml', @book.to_xml)
31
+ @repository.scope do
32
+ @book.save
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ %w[crud].each do |dir|
4
+ require File.join(File.dirname(__FILE__), "#{dir}/stories")
5
+ end
@@ -0,0 +1,39 @@
1
+ Story: remote app wants to create a resource
2
+ As a remote app
3
+ I want to create a resource
4
+ So that I can store some data about a particular item
5
+
6
+ Scenario: remote app supplies a new valid Resource
7
+ Given a valid DataMapper::Resource
8
+ When I try to save the Resource
9
+ Then the Resource should save
10
+
11
+ Scenario: remote app supplies a new invalid Resource
12
+ Given a valid DataMapper::Resource
13
+ When I make invalid changes to that Resource
14
+ When I try to save the Resource
15
+ Then the Resource should not save
16
+
17
+ Scenario: remote app supplies a new valid Resource associated with another new valid Resource
18
+ Given a new Resource
19
+ And another new Resource associated with the first
20
+ When I try to save the first Resource
21
+ Then both Resources should save
22
+
23
+ Scenario: remote app supplies a new invalid Resource associated with another new valid Resource
24
+ Given a new invalid Resouce
25
+ And another new Resource associated with the first
26
+ When I try to save the first Resource
27
+ Neither Resource should save
28
+
29
+ Scenario: remote app supplies a new valid Resource associated with another new invalid Resource
30
+ Given a new Resource
31
+ And another new invalid Resource associted with the first
32
+ When I try to save the first Resource
33
+ Neither Resource should save
34
+
35
+ Scenario: remote app supplies a new invalid Resource associated with another new invalid Resource
36
+ Given a new invalid Resource
37
+ And another new invalid Resource associted with the first
38
+ When I try to save the first Resource
39
+ Neither Resource should save
@@ -0,0 +1,9 @@
1
+ Story: remote app wants to delete a Resource
2
+ As a remote app
3
+ I want to delete a Resource
4
+ So that the information will no longer be available
5
+
6
+ Scenario: remote app attempts to delete a Resource by ID
7
+ Given a local representation of a remote Resource
8
+ When I destroy the Resource
9
+ Then the Resource will no longer be available
@@ -0,0 +1,36 @@
1
+ Story: remote app wants to access one or more Resources
2
+ As a remote app
3
+ I want to get one or more Resources
4
+ So that I can take an action based upon the information
5
+
6
+ Scenario: GET <resource>
7
+ Given a type of Resource
8
+ When I request all of the Resources of that type
9
+ Then I should not receive an empty list
10
+
11
+ Scenario: GET <resource>/<id>
12
+ Given a type of Resource
13
+ And the ID of an existing Resource
14
+ When I request the Resource
15
+ Then I should receive that Resource
16
+
17
+ Scenario: GET <resource>/<invalid id>
18
+ Given a type of Resource
19
+ And the ID of a nonexistent Resource
20
+ When I request the Resource
21
+ Then I should get nothing in return
22
+
23
+ Scenario: GET <nested resource>/<id>
24
+ Given a Resource that returns associated resources
25
+ And the ID of an existing Resource that has associated Resources
26
+ And I have all of the necessary class definitions
27
+ When I GET <nested resource>/<id>
28
+ Then I should get the Resource
29
+ And the Resource will have associated Resources
30
+
31
+ Scenario: GET <nested resource>/<id> but we are missing some class definitions
32
+ Given a Resource that returns associated resources
33
+ And the ID of an existing Resource that has associated Resources
34
+ And I do not have all of the necessary class definitions
35
+ When I GET <nested resource>/<id>
36
+ Then I should get an Exception
@@ -0,0 +1,18 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. helper])
2
+
3
+ Dir["#{File.dirname(__FILE__)}/*"].each do |file|
4
+ unless (file =~ /\.rb$/)
5
+ file_name = file.split('/').last
6
+ begin
7
+ require File.join(File.dirname(__FILE__), "..", "resources", "steps", file_name)
8
+ rescue LoadError
9
+ puts "Can't find #{file_name}.rb to define story steps; assuming this is intentional"
10
+ rescue Exception => e
11
+ puts e.backtrace
12
+ exit 1
13
+ end
14
+ with_steps_for :using_rest_adapter, file_name.to_sym do
15
+ run file if File.file?(file)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,44 @@
1
+ Story: remote app wants to update a resource
2
+ As a remote app
3
+ I want to modify a resource
4
+ So that it's information represents the correct state
5
+
6
+ Scenario: remote app updates with changes made to a resource's state
7
+ Given a local representation of a remote Resource
8
+ When I make valid changes to that Resource
9
+ And I try to save the Resource
10
+ Then the Resource should save
11
+
12
+ Scenario: remote app updates with invalid changes to a resource's state
13
+ Given a local representation of a remote Resource
14
+ When I make invalid changes to that Resource
15
+ And I try to save the Resource
16
+ Then the Resource should not save
17
+
18
+ Scenario: remote app updates with changes to an object and one of its children
19
+ Given a local representation of a remote Resource
20
+ And a local representation of one of that Resource's child Resources
21
+ When I make valid changes to the parent Resource
22
+ And I make valid changes to the child Resource
23
+ Then both Resources should save
24
+
25
+ Scenario: remote app updates with valid changes to an object and invalid changes to one of its children
26
+ Given a local representation of a remote Resource
27
+ And a local representation of one of that Resource's child Resources
28
+ When I make valid changes to the parent Resource
29
+ And I make invalid changes to the child Resource
30
+ Then neither Resource should save
31
+
32
+ Scenario: remote app updates with invalid changes to an object and valid changes to one of its children
33
+ Given a local representation of a remote Resource
34
+ And a local representation of one of that Resource's child Resources
35
+ When I make invalid changes to the parent Resource
36
+ And I make valid changes to the child Resource
37
+ Then neither Resource should save
38
+
39
+ Scenario: remote app updates with invalid changes to an object and invalid changes to one of its children
40
+ Given a local representation of a remote Resource
41
+ And a local representation of one of that Resource's child Resources
42
+ When I make invalid changes to the parent Resource
43
+ And I make invalid changes to the child Resource
44
+ Then neither Resource should save
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift File.expand_path("#{File.dirname(__FILE__)}/../lib/rest_adapter")
2
+ require 'rubygems'
3
+ require 'spec'
4
+ require 'tempfile'
5
+ require 'dm-core'
6
+ #require 'pathname'
7
+ #require Pathname(__FILE__).dirname.parent.expand_path + '../../lib/rest_adapter'
8
+ require File.join(File.dirname(__FILE__), *%w[resources helpers story_helper])
9
+ require File.join(File.dirname(__FILE__), *%w[resources steps using_rest_adapter])
@@ -0,0 +1,7 @@
1
+ class Book
2
+ include DataMapper::Resource
3
+ property :author, String
4
+ property :created_at, DateTime
5
+ property :id, Integer, :serial => true
6
+ property :title, String
7
+ end
@@ -0,0 +1,2 @@
1
+ require 'spec/story'
2
+ require File.dirname(__FILE__) + '/../../../spec/ruby_forker'
@@ -0,0 +1,35 @@
1
+ steps_for :read do
2
+ Given "a Resource that returns associated resources" do
3
+ class Shelf
4
+ include DataMapper::Resource
5
+ property :id, Integer, :serial => true
6
+ property :name, String
7
+ has n, :books
8
+ end
9
+ end
10
+
11
+ Given "the ID of an existing Resource that has associated Resources" do
12
+ # Assuming that resource 1 is there.
13
+ # @type.first would do a GET; that's what we're testing
14
+ @resource_id = 1
15
+ end
16
+
17
+ Given "I have all of the necessary class definitions" do
18
+ # NO-OP because defined above
19
+ end
20
+
21
+ When "I GET <nested resource>/<id>" do
22
+ @resource = Shelf.get(@resource_id)
23
+ end
24
+
25
+ Then "I should get the Resource" do
26
+ @resource.should_not be_nil
27
+ @resource.should be_an_instance_of(Shelf)
28
+ @resource.id.should == 1
29
+ end
30
+
31
+ Then "the Resource will have associated Resources" do
32
+ @resource.books.should_not be_empty
33
+ @resource.books.first.should be_an_instance_of(Book)
34
+ end
35
+ end
@@ -0,0 +1,99 @@
1
+ require 'dm-core'
2
+
3
+ steps_for :using_rest_adapter do
4
+ #To run these specs, you must have a REST Application running on port 3001 on your localhost
5
+ #The app is expected to implement the REST API for CRUD operations on a Book
6
+
7
+ DataMapper.setup(:default, {
8
+ :adapter => 'rest',
9
+ :format => 'xml',
10
+ :host => 'localhost',
11
+ :port => '3001'
12
+ })
13
+
14
+ Given("a valid DataMapper::Resource") do
15
+ # TODO refactor
16
+ require File.join(File.dirname(__FILE__), '..', 'helpers', 'book')
17
+ @resource = new_book
18
+ end
19
+
20
+ Given("a type of Resource") do
21
+ # TODO refactor
22
+ # require File.join(File.dirname(__FILE__), '..', 'helpers', 'book')
23
+ # Book = Book
24
+ end
25
+
26
+ Given("the ID of an existing Resource") do
27
+ # TODO refactor
28
+ require File.join(File.dirname(__FILE__), '..', 'helpers', 'book')
29
+ @resource_id = Book.first.id
30
+ end
31
+
32
+ Given("the ID of a nonexistent Resource") do
33
+ @resource_id = 42 ** 3
34
+ end
35
+
36
+ Given("a local representation of a remote Resource") do
37
+ # TODO refactor
38
+ require File.join(File.dirname(__FILE__), '..', 'helpers', 'book')
39
+ @resource = Book.first
40
+ @resource_id = @resource.id
41
+ end
42
+
43
+ When("I try to save the Resource") do
44
+ @result = @resource.save
45
+ end
46
+
47
+ When("I request all of the Resources of that type") do
48
+ require File.join(File.dirname(__FILE__), '..', 'helpers', 'book')
49
+ @resources = Book.all
50
+ end
51
+
52
+ When("I request the Resource") do
53
+ require File.join(File.dirname(__FILE__), '..', 'helpers', 'book')
54
+ @resource = Book.get(@resource_id)
55
+ end
56
+
57
+ When("I make valid changes to that Resource") do
58
+ @resource.title = "Mary had a little lamb"
59
+ end
60
+
61
+ When("I make invalid changes to that Resource") do
62
+ @resource.title = nil
63
+ end
64
+
65
+ When("I destroy the Resource") do
66
+ @resource.destroy
67
+ end
68
+
69
+ Then("the Resource should save") do
70
+ @result.should be_true
71
+ end
72
+
73
+ Then("the Resource should not save") do
74
+ @result.should be_false
75
+ end
76
+
77
+ Then("I should not receive an empty list") do
78
+ @resources.should_not be_empty
79
+ end
80
+
81
+ Then("I should receive that Resource") do
82
+ @resource.should_not be_nil
83
+ @resource.id.should == @resource_id
84
+ end
85
+
86
+ Then("I should get nothing in return") do
87
+ @resource.should be_nil
88
+ end
89
+
90
+ Then("the Resource will no longer be available") do
91
+ # TODO refactor
92
+ require File.join(File.dirname(__FILE__), '..', 'helpers', 'book')
93
+ Book.get(@resource_id).should be_nil
94
+ end
95
+ end
96
+
97
+ def new_book(options={})
98
+ Book.new(options.merge({:title => "Hello, World!", :author => "Some dude"}))
99
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-rest-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Potomac Ruby Hackers
@@ -9,42 +9,73 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-06-25 00:00:00 -05:00
12
+ date: 2008-07-24 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: dm-core
17
+ type: :runtime
17
18
  version_requirement:
18
19
  version_requirements: !ruby/object:Gem::Requirement
19
20
  requirements:
20
21
  - - "="
21
22
  - !ruby/object:Gem::Version
22
- version: 0.9.2
23
+ version: 0.9.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.7.0
23
34
  version:
24
35
  description: REST Adapter for DataMapper
25
- email: potomac-ruby-hackers@googlegroups.com
36
+ email:
37
+ - potomac-ruby-hackers@googlegroups.com
26
38
  executables: []
27
39
 
28
40
  extensions: []
29
41
 
30
42
  extra_rdoc_files:
31
- - README
43
+ - README.txt
32
44
  - LICENSE
33
45
  - TODO
34
46
  files:
47
+ - History.txt
48
+ - LICENSE
49
+ - Manifest.txt
50
+ - README.txt
51
+ - Rakefile
52
+ - TODO
35
53
  - lib/rest_adapter.rb
36
- - spec/rest_adapter_spec.rb
54
+ - lib/rest_adapter/version.rb
55
+ - spec/create_spec.rb
56
+ - spec/delete_spec.rb
57
+ - spec/read_spec.rb
37
58
  - spec/ruby_forker.rb
38
59
  - spec/spec.opts
39
- - Rakefile
40
- - README
41
- - LICENSE
42
- - TODO
60
+ - spec/spec_helper.rb
61
+ - spec/update_spec.rb
62
+ - stories/all.rb
63
+ - stories/crud/create
64
+ - stories/crud/delete
65
+ - stories/crud/read
66
+ - stories/crud/stories.rb
67
+ - stories/crud/update
68
+ - stories/helper.rb
69
+ - stories/resources/helpers/book.rb
70
+ - stories/resources/helpers/story_helper.rb
71
+ - stories/resources/steps/read.rb
72
+ - stories/resources/steps/using_rest_adapter.rb
43
73
  has_rdoc: true
44
74
  homepage: http://github.com/pjb3/dm-more/tree/master/adapters/dm-rest-adapter
45
75
  post_install_message:
46
- rdoc_options: []
47
-
76
+ rdoc_options:
77
+ - --main
78
+ - README.txt
48
79
  require_paths:
49
80
  - lib
50
81
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -61,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
92
  version:
62
93
  requirements: []
63
94
 
64
- rubyforge_project:
65
- rubygems_version: 1.0.1
95
+ rubyforge_project: datamapper
96
+ rubygems_version: 1.2.0
66
97
  signing_key:
67
98
  specification_version: 2
68
99
  summary: REST Adapter for DataMapper