yogo-dm-persevere-adapter 0.10

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ === 0.0.1 / 2009-02-19
2
+
3
+ * Release!
4
+
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Montana State University
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/Manifest.txt ADDED
@@ -0,0 +1,12 @@
1
+ History.txt
2
+ LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/persevere_adapter.rb
7
+ lib/persevere_adapter/version.rb
8
+ spec/integration/persevere_adapter_spec.rb
9
+ spec/spec.opts
10
+ spec/spec_helper.rb
11
+ tasks/install.rb
12
+ tasks/spec.rb
data/README.txt ADDED
@@ -0,0 +1,85 @@
1
+ = dm-persevere-adapter
2
+
3
+ A DataMapper adapter for Persevere (http://www.persvr.org/)
4
+
5
+ This requires the persevere gem (http://github.com/irjudson/persevere) which provides a ruby interface to Persevere.
6
+
7
+ == Usage
8
+
9
+ DM Persevere Adapter is very simple and very similar to the REST
10
+ Adapter, however it has two differences: 1) instead of XML it uses
11
+ JSON, and 2) Persevere supports typing using JSON Schema. These
12
+ differences make it valuable to have a separate DM adapter
13
+ specifically for Persevere so it can leverage richer aspects of
14
+ persevere.
15
+
16
+ The setup and resource mapping is identical to standard datamapper
17
+ objects, as can be seen below.
18
+
19
+ DataMapper.setup(:default, {
20
+ :adapter => 'persevere',
21
+ :host => 'localhost',
22
+ :port => '8080'
23
+ })
24
+
25
+ class MyUser
26
+ include DataMapper::Resource
27
+
28
+ property :id, Serial
29
+ property :uuid, String
30
+ property :name, String
31
+ property :first_name, String
32
+ property :last_name, String
33
+ property :groupid, Integer
34
+ property :userid, Integer
35
+ property :username, String
36
+ property :homedirectory, String
37
+
38
+ end
39
+
40
+ To use with Rails, you can put this in your environment.rb:
41
+ config.gem "dm-core"
42
+ config.gem "data_objects"
43
+ config.gem "dm-persevere-adapter", :lib => 'persevere_adapter'
44
+
45
+ With a database.yml:
46
+
47
+ development: &defaults
48
+ :adapter: persevere
49
+ :host: localhost
50
+ :port: 8080
51
+
52
+ test:
53
+ <<: *defaults
54
+
55
+ production:
56
+ <<: *defaults
57
+
58
+ == Code
59
+
60
+ # Create
61
+ user = MyUser.new(:username => "dmtest", :uuid => UUID.random_create().to_s,
62
+ :name => "DataMapper Test", :homedirectory => "/home/dmtest",
63
+ :first_name => "DataMapperTest", :last_name => "User",
64
+ :userid => 3, :groupid => 500)
65
+ user.save
66
+
67
+ # Retrieve
68
+ user = MyUser.first(:netid => 'dmtest')
69
+ puts user
70
+
71
+ # Modify
72
+ if user.update_attributes(:name => 'DM Test')
73
+ puts user
74
+ else
75
+ puts "Failed to update attributes."
76
+ end
77
+
78
+ # Delete
79
+ result = user.destroy
80
+ puts "Result: #{result}"
81
+
82
+ == To Do:
83
+
84
+ - Make a do-adapter for persevere.
85
+ - Cleanup Documentation
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+ require 'hoe'
4
+
5
+ ROOT = Pathname(__FILE__).dirname.expand_path
6
+ JRUBY = RUBY_PLATFORM =~ /java/
7
+ WINDOWS = Gem.win_platform?
8
+ SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
9
+
10
+ require ROOT + 'lib/persevere_adapter/version'
11
+
12
+ # define some constants to help with task files
13
+ GEM_NAME = 'dm-persevere-adapter'
14
+ GEM_VERSION = DataMapper::PersevereAdapter::VERSION
15
+
16
+ Hoe.spec(GEM_NAME) do |p|
17
+ p.developer('Ivan R. Judson', 'irjudson [a] gmail [d] com')
18
+
19
+ p.description = 'A DataMapper Adapter for persevere'
20
+ p.summary = 'A DataMapper Adapter for persevere'
21
+ p.url = 'http://github.com/USERNAME/dm-persevere-adapter'
22
+
23
+ p.clean_globs |= %w[ log pkg coverage ]
24
+ p.spec_extras = {
25
+ :has_rdoc => true,
26
+ :extra_rdoc_files => %w[ README.txt LICENSE.txt History.txt ]
27
+ }
28
+
29
+ p.extra_deps = [
30
+ ['dm-core', "~> 0.9.10"],
31
+ ['extlib', "~> 0.9.10"],
32
+ ['persevere', "~> 1.0.0"]
33
+ ]
34
+
35
+ end
36
+
37
+ Pathname.glob(ROOT.join('tasks/**/*.rb').to_s).each { |f| require f }
@@ -0,0 +1,342 @@
1
+ require 'rubygems'
2
+ require 'dm-core'
3
+ require 'extlib'
4
+ require 'json'
5
+ require 'persevere'
6
+
7
+ module DataMapper
8
+ module Adapters
9
+ class PersevereAdapter < AbstractAdapter
10
+ ##
11
+ # Used by DataMapper to put records into a data-store: "INSERT"
12
+ # in SQL-speak. It takes an array of the resources (model
13
+ # instances) to be saved. Resources each have a key that can be
14
+ # used to quickly look them up later without searching, if the
15
+ # adapter supports it.
16
+ #
17
+ # @param [Array<DataMapper::Resource>] resources
18
+ # The set of resources (model instances)
19
+ #
20
+ # @return [Integer]
21
+ # The number of records that were actually saved into the
22
+ # data-store
23
+ #
24
+ # @api semipublic
25
+ def create(resources)
26
+ connect if @persevere.nil?
27
+ created = 0
28
+ resources.each do |resource|
29
+ #
30
+ # This isn't the best solution but for an adapter, it'd be nice
31
+ # to support objects being in *tables* instead of in one big icky
32
+ # sort of table.
33
+ #
34
+ tblname = Extlib::Inflection.classify(resource.class).pluralize
35
+
36
+ if ! @classes.include?(tblname)
37
+ payload = {
38
+ 'id' => tblname,
39
+ 'extends' => { "$ref" => "/Class/Object" }
40
+ }
41
+
42
+ response = @persevere.create("/Class/", payload)
43
+ end
44
+
45
+ path = "/#{tblname}/"
46
+ payload = resource.attributes
47
+ payload.delete(:id)
48
+
49
+ response = @persevere.create(path, payload)
50
+
51
+ # Check the response, this needs to be more robust and raise
52
+ # exceptions when there's a problem
53
+
54
+ if response.code == "201"# good:
55
+ rsrc_hash = JSON.parse(response.body)
56
+ # Typecast attributes, DM expects them properly cast
57
+ resource.model.properties.each do |prop|
58
+ value = rsrc_hash[prop.field.to_s]
59
+ if !value.nil?
60
+ rsrc_hash[prop.field.to_s] = prop.typecast(value)
61
+ end
62
+ end
63
+
64
+ resource.id = rsrc_hash["id"]
65
+
66
+ created += 1
67
+ else
68
+ return false
69
+ end
70
+ end
71
+
72
+ # Return the number of resources created in persevere.
73
+ return created
74
+ end
75
+
76
+ ##
77
+ # Used by DataMapper to update the attributes on existing
78
+ # records in a data-store: "UPDATE" in SQL-speak. It takes a
79
+ # hash of the attributes to update with, as well as a query
80
+ # object that specifies which resources should be updated.
81
+ #
82
+ # @param [Hash] attributes
83
+ # A set of key-value pairs of the attributes to update the
84
+ # resources with.
85
+ # @param [DataMapper::Query] query
86
+ # The query that should be used to find the resource(s) to
87
+ # update.
88
+ #
89
+ # @return [Integer]
90
+ # the number of records that were successfully updated
91
+ #
92
+ # @api semipublic
93
+ def update(attributes, query)
94
+ connect if @persevere.nil?
95
+ updated = 0
96
+
97
+ if ! query.is_a?(DataMapper::Query)
98
+ resources = [query].flatten
99
+ else
100
+ resources = read_many(query)
101
+ end
102
+
103
+ resources.each do |resource|
104
+ tblname = Extlib::Inflection.classify(resource.class).pluralize
105
+ path = "/#{tblname}/#{resource.id}"
106
+
107
+ result = @persevere.update(path, resource.attributes)
108
+
109
+ if result # good:
110
+ updated += 1
111
+ else
112
+ return false
113
+ end
114
+ end
115
+ return updated
116
+ end
117
+
118
+ ##
119
+ # Look up a single record from the data-store. "SELECT ... LIMIT
120
+ # 1" in SQL. Used by Model#get to find a record by its
121
+ # identifier(s), and Model#first to find a single record by some
122
+ # search query.
123
+ #
124
+ # @param [DataMapper::Query] query
125
+ # The query to be used to locate the resource.
126
+ #
127
+ # @return [DataMapper::Resource]
128
+ # A Resource object representing the record that was found, or
129
+ # nil for no matching records.
130
+ #
131
+ # @api semipublic
132
+
133
+ def read_one(query)
134
+ results = read_many(query)
135
+ results[0,1]
136
+ end
137
+
138
+ ##
139
+ # Looks up a collection of records from the data-store: "SELECT"
140
+ # in SQL. Used by Model#all to search for a set of records;
141
+ # that set is in a DataMapper::Collection object.
142
+ #
143
+ # @param [DataMapper::Query] query
144
+ # The query to be used to seach for the resources
145
+ #
146
+ # @return [DataMapper::Collection]
147
+ # A collection of all the resources found by the query.
148
+ #
149
+ # @api semipublic
150
+ def read_many(query)
151
+ connect if @persevere.nil?
152
+
153
+ resources = Array.new
154
+ json_query = make_json_query(query)
155
+
156
+ tblname = Extlib::Inflection.classify(query.model).pluralize
157
+ path = "/#{tblname}/#{json_query}"
158
+
159
+ response = @persevere.retrieve(path)
160
+
161
+ if response.code == "200"
162
+ results = JSON.parse(response.body)
163
+ results.each do |rsrc_hash|
164
+ # Typecast attributes, DM expects them properly cast
165
+ query.model.properties.each do |prop|
166
+ value = rsrc_hash[prop.field.to_s]
167
+ if !value.nil?
168
+ rsrc_hash[prop.field.to_s] = prop.typecast(value)
169
+ end
170
+ end
171
+ end
172
+
173
+ resources = query.model.load(results, query)
174
+ else
175
+ return false
176
+ end
177
+
178
+ query.filter_records(resources)
179
+ end
180
+
181
+ alias :read :read_many
182
+
183
+ ##
184
+ # Destroys all the records matching the given query. "DELETE" in SQL.
185
+ #
186
+ # @param [DataMapper::Query] query
187
+ # The query used to locate the resources to be deleted.
188
+ #
189
+ # @return [Integer]
190
+ # The number of records that were deleted.
191
+ #
192
+ # @api semipublic
193
+ def delete(query)
194
+ connect if @persevere.nil?
195
+
196
+ deleted = 0
197
+
198
+ if ! query.is_a?(DataMapper::Query)
199
+ resources = [query].flatten
200
+ else
201
+ resources = read_many(query)
202
+ end
203
+
204
+ resources.each do |resource|
205
+ tblname = Extlib::Inflection.classify(resource.class).pluralize
206
+ path = "/#{tblname}/#{resource.id}"
207
+
208
+ result = @persevere.delete(path)
209
+
210
+ if result # ok
211
+ deleted += 1
212
+ end
213
+ end
214
+ return deleted
215
+ end
216
+
217
+ private
218
+
219
+ ##
220
+ # Make a new instance of the adapter. The @model_records ivar is
221
+ # the 'data-store' for this adapter. It is not shared amongst
222
+ # multiple incarnations of this adapter, eg
223
+ # DataMapper.setup(:default, :adapter => :in_memory);
224
+ # DataMapper.setup(:alternate, :adapter => :in_memory) do not
225
+ # share the data-store between them.
226
+ #
227
+ # @param [String, Symbol] name
228
+ # The name of the DataMapper::Repository using this adapter.
229
+ # @param [String, Hash] uri_or_options
230
+ # The connection uri string, or a hash of options to set up
231
+ # the adapter
232
+ #
233
+ # @api semipublic
234
+
235
+ def initialize(name, uri_or_options)
236
+ super
237
+
238
+ if uri_or_options.class
239
+ @identity_maps = {}
240
+ end
241
+
242
+ @options = Hash.new
243
+
244
+ uri_or_options.each do |k,v|
245
+ @options[k.to_sym] = v
246
+ end
247
+
248
+ @options[:scheme] = @options[:adapter]
249
+ @options.delete(:scheme)
250
+
251
+ @resource_naming_convention = NamingConventions::Resource::Underscored
252
+ @identity_maps = {}
253
+ @classes = []
254
+ @persevere = nil
255
+ @prepped = false
256
+ end
257
+
258
+ def connect
259
+ uri = URI::HTTP.build(@options).to_s
260
+ @persevere = Persevere.new(uri)
261
+ prep_persvr unless @prepped
262
+ end
263
+
264
+ def prep_persvr
265
+ # Because this is an AbstractAdapter and not a
266
+ # DataObjectAdapter, we can't assume there are any schemas
267
+ # present, so we retrieve the ones that exist and keep them up
268
+ # to date
269
+ result = @persevere.retrieve('/Class[=id]')
270
+ if result.code == "200"
271
+ hresult = JSON.parse(result.body)
272
+ hresult.each do |cname|
273
+ junk,name = cname.split("/")
274
+ @classes << name
275
+ end
276
+ @prepped = true
277
+ else
278
+ puts "Error retrieving existing tables: ", result
279
+ end
280
+ end
281
+
282
+ ##
283
+ # Convert a DataMapper Resource to a JSON.
284
+ #
285
+ # @param [Query] query
286
+ # The DataMapper query object passed in
287
+ #
288
+ # @api semipublic
289
+ def make_json(resource)
290
+ json_rsrc = nil
291
+
292
+ # Gather up all the attributes
293
+ json_rsrc = resource.attributes.to_json
294
+ end
295
+
296
+ ##
297
+ # Convert a DataMapper Query to a JSON Query.
298
+ #
299
+ # @param [Query] query
300
+ # The DataMapper query object passed in
301
+ #
302
+ # @api semipublic
303
+
304
+ def make_json_query(query)
305
+ query_terms = Array.new
306
+
307
+ conditions = query.conditions
308
+
309
+ conditions.each do |condition|
310
+ operator, property, bind_value = condition
311
+ if ! property.nil? && !bind_value.nil?
312
+ v = property.typecast(bind_value)
313
+ if v.is_a?(String)
314
+ value = "'#{bind_value}'"
315
+ else
316
+ value = "#{bind_value}"
317
+ end
318
+
319
+ query_terms << case operator
320
+ when :eql then "#{property.field()}=#{value}"
321
+ when :lt then "#{property.field()}<#{value}"
322
+ when :gt then "#{property.field()}>#{value}"
323
+ when :lte then "#{property.field()}<=#{value}"
324
+ when :gte then "#{property.field()}=>#{value}"
325
+ when :not then "#{property.field()}!=#{value}"
326
+ when :like then "#{property.field()}~'*#{value}*'"
327
+ else puts "Unknown condition: #{operator}"
328
+ end
329
+ end
330
+ end
331
+
332
+ if query_terms.length != 0
333
+ query = "?#{query_terms.join("&")}"
334
+ else
335
+ query = ""
336
+ end
337
+
338
+ query
339
+ end
340
+ end
341
+ end
342
+ end
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+ module PersevereAdapter
3
+ VERSION = '0.10'
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
3
+
4
+ describe 'DataMapper::Adapters::PersevereAdapter' do
5
+
6
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,28 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+
4
+ gem 'rspec'
5
+ require 'spec'
6
+
7
+ require Pathname(__FILE__).dirname.expand_path.parent + 'lib/persevere_adapter'
8
+
9
+ DataMapper.setup(:default, {
10
+ :adapter => 'persevere',
11
+ :host => 'localhost',
12
+ :port => '8080',
13
+ :uri => 'http://localhost:8080'
14
+ })
15
+
16
+ #
17
+ # I need to make the Book class for Books to relate to
18
+ #
19
+
20
+ class Book
21
+ include DataMapper::Resource
22
+
23
+ # Persevere only does id's as strings.
24
+ property :id, String, :serial => true
25
+ property :author, String
26
+ property :created_at, DateTime
27
+ property :title, String
28
+ end
data/tasks/install.rb ADDED
@@ -0,0 +1,13 @@
1
+ def sudo_gem(cmd)
2
+ sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
3
+ end
4
+
5
+ desc "Install #{GEM_NAME} #{GEM_VERSION}"
6
+ task :install => [ :package ] do
7
+ sudo_gem "install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
8
+ end
9
+
10
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
11
+ task :uninstall => [ :clobber ] do
12
+ sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -Ix"
13
+ end
data/tasks/spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ begin
2
+ gem 'rspec'
3
+ require 'spec'
4
+ require 'spec/rake/spectask'
5
+
6
+ task :default => [ :spec ]
7
+
8
+ desc 'Run specifications'
9
+ Spec::Rake::SpecTask.new(:spec) do |t|
10
+ t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
11
+ t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
12
+
13
+ begin
14
+ gem 'rcov', '~>0.8'
15
+ t.rcov = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
16
+ t.rcov_opts << '--exclude' << 'spec'
17
+ t.rcov_opts << '--text-summary'
18
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
19
+ rescue LoadError
20
+ # rcov not installed
21
+ end
22
+ end
23
+ rescue LoadError
24
+ # rspec not installed
25
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yogo-dm-persevere-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.10"
5
+ platform: ruby
6
+ authors:
7
+ - Ivan R. Judson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-19 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.9.10
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: persevere
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.3
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.8.3
44
+ version:
45
+ description: A DataMapper Adapter for persevere
46
+ email:
47
+ - irjudson [a] gmail [d] com
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ extra_rdoc_files:
53
+ - README.txt
54
+ - LICENSE.txt
55
+ - History.txt
56
+ files:
57
+ - History.txt
58
+ - LICENSE.txt
59
+ - Manifest.txt
60
+ - README.txt
61
+ - Rakefile
62
+ - lib/persevere_adapter.rb
63
+ - lib/persevere_adapter/version.rb
64
+ - spec/integration/persevere_adapter_spec.rb
65
+ - spec/spec.opts
66
+ - spec/spec_helper.rb
67
+ - tasks/install.rb
68
+ - tasks/spec.rb
69
+ has_rdoc: true
70
+ homepage: http://github.com/irjudson/dm-persevere-adapter
71
+ licenses:
72
+ post_install_message:
73
+ rdoc_options:
74
+ - --main
75
+ - README.txt
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ requirements: []
91
+
92
+ rubyforge_project: dm-persevere-adapter
93
+ rubygems_version: 1.3.5
94
+ signing_key:
95
+ specification_version: 2
96
+ summary: A DataMapper Adapter for persevere
97
+ test_files: []
98
+