dm-rest-adapter 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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