dm-rest-adapter 0.9.2
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.
- data/LICENSE +20 -0
- data/README +4 -0
- data/Rakefile +52 -0
- data/TODO +2 -0
- data/lib/rest_adapter.rb +190 -0
- data/spec/rest_adapter_spec.rb +153 -0
- data/spec/ruby_forker.rb +13 -0
- data/spec/spec.opts +1 -0
- metadata +70 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Potomac Ruby Hackers
|
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
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
require 'pathname'
|
7
|
+
|
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
|
25
|
+
|
26
|
+
task :default => [ :spec ]
|
27
|
+
|
28
|
+
WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
|
29
|
+
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
30
|
+
|
31
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
32
|
+
pkg.gem_spec = spec
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Install #{spec.name} #{spec.version}"
|
36
|
+
task :install => [ :package ] do
|
37
|
+
sh "#{SUDO} gem install pkg/#{spec.name}-#{spec.version} --no-update-sources", :verbose => false
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'Run specifications'
|
41
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
42
|
+
if File.exists?('spec/spec.opts')
|
43
|
+
t.spec_opts << '--options' << 'spec/spec.opts'
|
44
|
+
end
|
45
|
+
t.spec_files =
|
46
|
+
Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Run all stories"
|
50
|
+
task :stories do
|
51
|
+
ruby "stories/all.rb --colour --format plain"
|
52
|
+
end
|
data/lib/rest_adapter.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'dm-core', '=0.9.2'
|
3
|
+
require 'dm-core'
|
4
|
+
require 'extlib'
|
5
|
+
require 'dm-serializer'
|
6
|
+
require 'pathname'
|
7
|
+
require 'net/http'
|
8
|
+
require 'rexml/document'
|
9
|
+
|
10
|
+
# TODO: Abstract XML support out from the protocol
|
11
|
+
# TODO: Build JSON support
|
12
|
+
module DataMapper
|
13
|
+
module Adapters
|
14
|
+
class RestAdapter < AbstractAdapter
|
15
|
+
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
|
+
|
29
|
+
# Creates a new resource in the specified repository.
|
30
|
+
def create(resources)
|
31
|
+
success = true
|
32
|
+
resources.each do |resource|
|
33
|
+
resource_name = Inflection.underscore(resource.class.name.downcase)
|
34
|
+
result = http_post("/#{resource_name.pluralize}.xml", resource.to_xml)
|
35
|
+
# TODO: Raise error if cannot reach server
|
36
|
+
success = success && result.instance_of?(Net::HTTPCreated)
|
37
|
+
if success
|
38
|
+
updated_resource = parse_resource(result.body, resource.class)
|
39
|
+
resource.id = updated_resource.id
|
40
|
+
end
|
41
|
+
# TODO: We're not using the response to update the DataMapper::Resource with the newly acquired ID!!!
|
42
|
+
end
|
43
|
+
success
|
44
|
+
end
|
45
|
+
|
46
|
+
# read_set
|
47
|
+
#
|
48
|
+
# Examples of query string:
|
49
|
+
# A. []
|
50
|
+
# GET /books/
|
51
|
+
#
|
52
|
+
# B. [[:eql, #<Property:Book:id>, 4200]]
|
53
|
+
# GET /books/4200
|
54
|
+
#
|
55
|
+
# IN PROGRESS
|
56
|
+
# TODO: Need to account for query.conditions (i.e., [[:eql, #<Property:Book:id>, 1]] for books/1)
|
57
|
+
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)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
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
|
71
|
+
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
|
80
|
+
end
|
81
|
+
|
82
|
+
def update(attributes, query)
|
83
|
+
# TODO update for v0.9.2
|
84
|
+
raise NotImplementedError.new unless is_single_resource_query? query
|
85
|
+
id = query.conditions.first[2]
|
86
|
+
resource = query.model.new
|
87
|
+
attributes.each do |attr, val|
|
88
|
+
resource.send("#{attr.name}=", val)
|
89
|
+
end
|
90
|
+
# 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)
|
92
|
+
# TODO: Raise error if cannot reach server
|
93
|
+
end
|
94
|
+
|
95
|
+
def delete(query)
|
96
|
+
#puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> QUERY: #{query} #{query.inspect}"
|
97
|
+
# TODO update for v0.9.2
|
98
|
+
raise NotImplementedError.new unless is_single_resource_query? query
|
99
|
+
id = query.conditions.first[2]
|
100
|
+
http_delete("/#{resource_name_from_query(query).pluralize}/#{id}.xml")
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
def read_set_all(repository, query, resource_name)
|
105
|
+
# TODO: how do we know whether the resource we're talking to is singular or plural?
|
106
|
+
res = http_get("/#{resource_name.pluralize}.xml")
|
107
|
+
data = res.body
|
108
|
+
parse_resources(data, query.model)
|
109
|
+
# TODO: Raise error if cannot reach server
|
110
|
+
end
|
111
|
+
|
112
|
+
# GET /books/4200
|
113
|
+
def read_set_for_condition(repository, query, resource_name)
|
114
|
+
# More complex conditions
|
115
|
+
raise NotImplementedError.new
|
116
|
+
end
|
117
|
+
|
118
|
+
# query.conditions like [[:eql, #<Property:Book:id>, 4200]]
|
119
|
+
def is_single_resource_query?(query)
|
120
|
+
query.conditions.length == 1 && query.conditions.first.first == :eql && query.conditions.first[1].name == :id
|
121
|
+
end
|
122
|
+
|
123
|
+
def http_put(uri, data = nil)
|
124
|
+
request { |http| http.put(uri, data) }
|
125
|
+
end
|
126
|
+
|
127
|
+
def http_post(uri, data)
|
128
|
+
request { |http| http.post(uri, data, {"Content-Type", "application/xml"}) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def http_get(uri)
|
132
|
+
request { |http| http.get(uri) }
|
133
|
+
end
|
134
|
+
|
135
|
+
def http_delete(uri)
|
136
|
+
request { |http| http.delete(uri) }
|
137
|
+
end
|
138
|
+
|
139
|
+
def request(&block)
|
140
|
+
res = nil
|
141
|
+
Net::HTTP.start(@uri[:host], @uri[:port].to_i) do |http|
|
142
|
+
res = yield(http)
|
143
|
+
end
|
144
|
+
res
|
145
|
+
end
|
146
|
+
|
147
|
+
def resource_from_rexml(entity_element, dm_model_class)
|
148
|
+
resource = dm_model_class.new
|
149
|
+
entity_element.elements.each do |field_element|
|
150
|
+
attribute = resource.attributes.find do |name, val|
|
151
|
+
# *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)
|
153
|
+
end
|
154
|
+
resource.send("#{Inflection.underscore(attribute[0])}=", field_element.text) if attribute
|
155
|
+
end
|
156
|
+
resource.instance_eval { @new_record= false }
|
157
|
+
resource
|
158
|
+
end
|
159
|
+
|
160
|
+
def parse_resource(xml, dm_model_class)
|
161
|
+
doc = REXML::Document::new(xml)
|
162
|
+
# TODO: handle singular resource case as well....
|
163
|
+
entity_element = REXML::XPath.first(doc, "/#{resource_name_from_model(dm_model_class)}")
|
164
|
+
return nil unless entity_element
|
165
|
+
resource_from_rexml(entity_element, dm_model_class)
|
166
|
+
end
|
167
|
+
|
168
|
+
def parse_resources(xml, dm_model_class)
|
169
|
+
doc = REXML::Document::new(xml)
|
170
|
+
# # TODO: handle singular resource case as well....
|
171
|
+
# array = XPath(doc, "/*[@type='array']")
|
172
|
+
# if array
|
173
|
+
# parse_resources()
|
174
|
+
# else
|
175
|
+
resource_name = resource_name_from_model dm_model_class
|
176
|
+
doc.elements.collect("#{resource_name.pluralize}/#{resource_name}") do |entity_element|
|
177
|
+
resource_from_rexml(entity_element, dm_model_class)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def resource_name_from_model(model)
|
182
|
+
Inflection.underscore(model.name.downcase)
|
183
|
+
end
|
184
|
+
|
185
|
+
def resource_name_from_query(query)
|
186
|
+
resource_name_from_model(query.model)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,153 @@
|
|
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
|
18
|
+
|
19
|
+
describe 'A REST adapter' do
|
20
|
+
|
21
|
+
before do
|
22
|
+
@adapter = DataMapper::Repository.adapters[:default]
|
23
|
+
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
|
+
|
37
|
+
describe 'when getting one resource' do
|
38
|
+
|
39
|
+
describe 'if the resource exists' do
|
40
|
+
|
41
|
+
before do
|
42
|
+
book_xml = <<-BOOK
|
43
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
44
|
+
<book>
|
45
|
+
<author>Stephen King</author>
|
46
|
+
<created-at type='datetime'>2008-06-08T17:03:07Z</created-at>
|
47
|
+
<id type='integer'>1</id>
|
48
|
+
<title>The Shining</title>
|
49
|
+
<updated-at type='datetime'>2008-06-08T17:03:07Z</updated-at>
|
50
|
+
</book>
|
51
|
+
BOOK
|
52
|
+
@id = 1
|
53
|
+
@response = mock(Net::HTTPResponse)
|
54
|
+
@response.stub!(:body).and_return(book_xml)
|
55
|
+
@adapter.stub!(:http_get).and_return(@response)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should return the resource' do
|
59
|
+
book = Book.get(@id)
|
60
|
+
book.should_not be_nil
|
61
|
+
book.id.should be_an_instance_of(Fixnum)
|
62
|
+
book.id.should == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should do an HTTP GET' do
|
66
|
+
@adapter.should_receive(:http_get).with('/books/1.xml').and_return(@response)
|
67
|
+
Book.get(@id)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'if the resource does not exist' do
|
72
|
+
it 'should return nil' do
|
73
|
+
@id = 1
|
74
|
+
@response = mock(Net::HTTPNotFound)
|
75
|
+
@response.stub!(:content_type).and_return('text/html')
|
76
|
+
@response.stub!(:body).and_return('<html></html>')
|
77
|
+
@adapter.stub!(:http_get).and_return(@response)
|
78
|
+
id = 4200
|
79
|
+
Book.get(id).should be_nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'when getting all resource of a particular type' do
|
85
|
+
before do
|
86
|
+
books_xml = <<-BOOK
|
87
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
88
|
+
<books type='array'>
|
89
|
+
<book>
|
90
|
+
<author>Ursula K LeGuin</author>
|
91
|
+
<created-at type='datetime'>2008-06-08T17:02:28Z</created-at>
|
92
|
+
<id type='integer'>1</id>
|
93
|
+
<title>The Dispossed</title>
|
94
|
+
<updated-at type='datetime'>2008-06-08T17:02:28Z</updated-at>
|
95
|
+
</book>
|
96
|
+
<book>
|
97
|
+
<author>Stephen King</author>
|
98
|
+
<created-at type='datetime'>2008-06-08T17:03:07Z</created-at>
|
99
|
+
<id type='integer'>2</id>
|
100
|
+
<title>The Shining</title>
|
101
|
+
<updated-at type='datetime'>2008-06-08T17:03:07Z</updated-at>
|
102
|
+
</book>
|
103
|
+
</books>
|
104
|
+
BOOK
|
105
|
+
@response = mock(Net::HTTPResponse)
|
106
|
+
@response.stub!(:body).and_return(books_xml)
|
107
|
+
@adapter.stub!(:http_get).and_return(@response)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should get a non-empty list' do
|
111
|
+
Book.all.should_not be_empty
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should receive one Resource for each entity in the XML' do
|
115
|
+
Book.all.size.should == 2
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should do an HTTP GET' do
|
119
|
+
@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
|
+
|
152
|
+
end
|
153
|
+
end
|
data/spec/ruby_forker.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
module RubyForker
|
4
|
+
# Forks a ruby interpreter with same type as ourself.
|
5
|
+
# juby will fork jruby, ruby will fork ruby etc.
|
6
|
+
def ruby(args, stderr=nil)
|
7
|
+
config = ::Config::CONFIG
|
8
|
+
interpreter = File::join(config['bindir'], config['ruby_install_name']) + config['EXEEXT']
|
9
|
+
cmd = "#{interpreter} #{args}"
|
10
|
+
cmd << " 2> #{stderr}" unless stderr.nil?
|
11
|
+
`#{cmd}`
|
12
|
+
end
|
13
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-rest-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Potomac Ruby Hackers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-06-25 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dm-core
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - "="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.9.2
|
23
|
+
version:
|
24
|
+
description: REST Adapter for DataMapper
|
25
|
+
email: potomac-ruby-hackers@googlegroups.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README
|
32
|
+
- LICENSE
|
33
|
+
- TODO
|
34
|
+
files:
|
35
|
+
- lib/rest_adapter.rb
|
36
|
+
- spec/rest_adapter_spec.rb
|
37
|
+
- spec/ruby_forker.rb
|
38
|
+
- spec/spec.opts
|
39
|
+
- Rakefile
|
40
|
+
- README
|
41
|
+
- LICENSE
|
42
|
+
- TODO
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/pjb3/dm-more/tree/master/adapters/dm-rest-adapter
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.0.1
|
66
|
+
signing_key:
|
67
|
+
specification_version: 2
|
68
|
+
summary: REST Adapter for DataMapper
|
69
|
+
test_files: []
|
70
|
+
|