dm-persevere-adapter 0.71.4 → 0.72.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,5 @@ tmp
3
3
  coverage
4
4
  *.gemspec
5
5
  .rvmrc
6
+ .DS_Store
7
+ .bundle/*
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source :rubyforge
2
+
3
+ gem "dm-core", "~> 1.0"
4
+ gem "dm-aggregates", "~> 1.0"
5
+ gem "dm-types", "~> 1.0"
6
+ gem "dm-migrations"
7
+ gem "dm-validations"
8
+
9
+ group :development do
10
+ gem "jeweler"
11
+ gem "rspec"
12
+ gem "yard"
13
+ gem "ruby-debug"
14
+ gem "addressable"
15
+ gem "rake", :require => nil
16
+ end
data/README.txt CHANGED
@@ -41,7 +41,7 @@ end
41
41
  To use with Rails, you can put this in your environment.rb:
42
42
  config.gem "dm-core"
43
43
  config.gem "data_objects"
44
- config.gem "dm-persevere-adapter", :lib => 'persevere_adapter'
44
+ config.gem "dm-persevere-adapter", :require => nil
45
45
 
46
46
  With a database.yml:
47
47
 
data/Rakefile CHANGED
@@ -2,6 +2,12 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'pathname'
4
4
 
5
+ begin
6
+ require 'bundler'
7
+ rescue LoadError
8
+ puts "Bundler is not intalled. Install with: gem install bundler"
9
+ end
10
+
5
11
  begin
6
12
  require 'jeweler'
7
13
  Jeweler::Tasks.new do |gemspec|
@@ -10,10 +16,9 @@ begin
10
16
  gemspec.description = %q{A DataMapper Adapter for persevere}
11
17
  gemspec.email = ["irjudson [a] gmail [d] com"]
12
18
  gemspec.homepage = %q{http://github.com/yogo/dm-persevere-adapter}
13
- gemspec.authors = ["Ivan R. Judson", "The Yogo Data Management Development Team" ]
19
+ gemspec.authors = ["Ivan R. Judson", "Ryan Heimbuch", "The Yogo Data Management Development Team" ]
14
20
  gemspec.rdoc_options = ["--main", "README.txt"]
15
- gemspec.add_dependency(%q<dm-core>, [">= 0.10.2"])
16
- gemspec.add_dependency(%q<extlib>)
21
+ gemspec.add_bundler_dependencies
17
22
  end
18
23
 
19
24
  Jeweler::GemcutterTasks.new
@@ -21,6 +26,15 @@ rescue LoadError
21
26
  puts "Jeweler not available. Install it with: gem install jeweler"
22
27
  end
23
28
 
29
+ begin
30
+ require 'yard'
31
+ YARD::Rake::YardocTask.new
32
+ rescue LoadError
33
+ task :yardoc do
34
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
35
+ end
36
+ end
37
+
24
38
  FileList['tasks/**/*.rake'].each { |task| import task }
25
39
 
26
40
  ROOT = Pathname(__FILE__).dirname.expand_path
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.71.4
1
+ 0.72.0
@@ -2,789 +2,24 @@ require 'rubygems'
2
2
  require 'dm-core'
3
3
  require 'dm-aggregates'
4
4
  require 'dm-types'
5
- require 'extlib'
5
+ require 'dm-migrations'
6
+ require 'dm-migrations/auto_migration'
7
+ require 'dm-validations'
6
8
  require 'bigdecimal'
7
9
  require 'digest/md5'
8
10
 
9
- # Things we add or override in DataMapper
10
- require 'dm/associations/many_to_many'
11
- require 'dm/associations/relationship'
12
- require 'dm/model'
13
- require 'dm/property'
14
- require 'dm/query'
15
- require 'dm/resource'
11
+ # Require Persevere http client
12
+ require 'persevere_client'
16
13
 
17
- require 'persevere'
14
+ # Require in Adapter modules
15
+ require 'persevere_adapter/query'
18
16
 
19
- #
20
- # Override BigDecimal to_json because it's ugly and doesn't work for us
21
- #
22
- class BigDecimal
23
- alias to_json_old to_json
24
-
25
- def to_json
26
- to_s
27
- end
28
- end
17
+ require 'persevere_adapter/support/big_decimal'
29
18
 
30
- module DataMapper
31
- module Aggregates
32
- module PersevereAdapter
33
- def aggregate(query)
34
- records = []
35
- fields = query.fields
36
- field_size = fields.size
37
-
38
- connect if @persevere.nil?
39
- resources = Array.new
19
+ require 'persevere_adapter/json_support'
20
+ require 'persevere_adapter/enhance'
40
21
 
41
- json_query, headers = query.to_json_query
42
- path = "/#{query.model.storage_name}/#{json_query}"
43
-
44
- response = @persevere.retrieve(path, headers)
22
+ require 'persevere_adapter/adapter'
23
+ require 'persevere_adapter/migrations'
24
+ require 'persevere_adapter/aggregates'
45
25
 
46
- if response.code == "200"
47
- results = [response.body]
48
- results.each do |row_of_results|
49
- row = query.fields.zip([row_of_results].flatten).map do |field, value|
50
- if field.respond_to?(:operator)
51
- send(field.operator, field.target, value)
52
- else
53
- field.typecast(value)
54
- end
55
- end
56
- records << (field_size > 1 ? row : row[0])
57
- end
58
- end
59
- records
60
- end # aggregate method
61
-
62
- private
63
-
64
- def count(property, value)
65
- value.to_i
66
- end
67
-
68
- def min(property, value)
69
- values = JSON.parse("[#{value}]").flatten.compact
70
- if values.is_a?(Array)
71
- values.map! { |v| property.typecast(v) }
72
- return values.sort[0].new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24)) if property.type == DateTime
73
- return values.sort[0]
74
- end
75
- property.typecast(value)
76
- end
77
-
78
- def max(property, value)
79
- values = JSON.parse("[#{value}]").flatten.compact
80
- if values.is_a?(Array)
81
- values.map! { |v| property.typecast(v) }
82
- return values.sort[-1].new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24)) if property.type == DateTime
83
- return values.sort[-1]
84
- end
85
- property.typecast(value)
86
- end
87
-
88
- def avg(property, value)
89
- values = JSON.parse(value).compact
90
- result = values.inject(0.0){|sum,i| sum+=i }/values.length
91
- property.type == Integer ? result.to_f : property.typecast(result)
92
- end
93
-
94
- def sum(property, value)
95
- property.typecast(value)
96
- end
97
- end # module PersevereAdapter
98
- end # module Aggregates
99
-
100
- module Migrations
101
- module PersevereAdapter
102
- # @api private
103
- def self.included(base)
104
- DataMapper.extend(Migrations::SingletonMethods)
105
-
106
- [ :Repository, :Model ].each do |name|
107
- DataMapper.const_get(name).send(:include, Migrations.const_get(name))
108
- end
109
- end
110
-
111
- # Returns whether the storage_name exists.
112
- #
113
- # @param [String] storage_name
114
- # a String defining the name of a storage, for example a table name.
115
- #
116
- # @return [Boolean]
117
- # true if the storage exists
118
- #
119
- # @api semipublic
120
- def storage_exists?(storage_name)
121
- class_names = JSON.parse(@persevere.retrieve('/Class/[=id]').body)
122
- return true if class_names.include?("Class/"+storage_name)
123
- false
124
- end
125
-
126
- ##
127
- # Creates the persevere schema from the model.
128
- #
129
- # @param [DataMapper::Model] model
130
- # The model that corresponds to the storage schema that needs to be created.
131
- #
132
- # @api semipublic
133
- def create_model_storage(model)
134
- name = self.name
135
- properties = model.properties_with_subclasses(name)
136
-
137
- return false if storage_exists?(model.storage_name(name))
138
- return false if properties.empty?
139
-
140
- # Make sure storage for referenced objects exists
141
- model.relationships.each_pair do |n, r|
142
- if ! storage_exists?(r.child_model.storage_name)
143
- put_schema({'id' => r.child_model.storage_name, 'properties' => {}})
144
- end
145
- end
146
- schema_hash = model.to_json_schema_hash()
147
-
148
- return true unless put_schema(schema_hash) == false
149
- false
150
- end
151
-
152
- ##
153
- # Updates the persevere schema from the model.
154
- #
155
- # @param [DataMapper::Model] model
156
- # The model that corresponds to the storage schema that needs to be updated.
157
- #
158
- # @api semipublic
159
- def upgrade_model_storage(model)
160
- name = self.name
161
- properties = model.properties_with_subclasses(name)
162
-
163
- DataMapper.logger.debug("Upgrading #{model.name}")
164
-
165
- if success = create_model_storage(model)
166
- return properties
167
- end
168
-
169
- new_schema_hash = model.to_json_schema_hash()
170
- current_schema_hash = get_schema(new_schema_hash['id'])[0]
171
- # TODO: Diff of what is there and what will be added.
172
-
173
- new_properties = properties.map do |property|
174
- prop_name = property.name.to_s
175
- prop_type = property.type
176
- next if prop_name == 'id' ||
177
- (current_schema_hash['properties'].has_key?(prop_name) &&
178
- new_schema_hash['properties'][prop_name]['type'] == current_schema_hash['properties'][prop_name]['type'] )
179
- property
180
- end.compact
181
-
182
- return new_properties unless update_schema(new_schema_hash) == false
183
- return nil
184
- end
185
-
186
- ##
187
- # Destroys the persevere schema from the model.
188
- #
189
- # @param [DataMapper::Model] model
190
- # The model that corresponds to the storage schema that needs to be destroyed.
191
- #
192
- # @api semipublic
193
- def destroy_model_storage(model)
194
- return true unless storage_exists?(model.storage_name(name))
195
- schema_hash = model.to_json_schema_hash()
196
- return true unless delete_schema(schema_hash) == false
197
- false
198
- end
199
-
200
- end # module PersevereAdapter
201
- end # module Migrations
202
-
203
- module Adapters
204
- class PersevereAdapter < AbstractAdapter
205
- extend Chainable
206
- extend Deprecate
207
-
208
- RESERVED_CLASSNAMES = ['User','Transaction','Capability','File','Class', 'Object', 'Versioned']
209
-
210
- include Migrations::PersevereAdapter
211
-
212
- # Default types for all data object based adapters.
213
- #
214
- # @return [Hash] default types for data objects adapters.
215
- #
216
- # @api private
217
- chainable do
218
- def type_map
219
- length = Property::DEFAULT_LENGTH
220
- precision = Property::DEFAULT_PRECISION
221
- scale = Property::DEFAULT_SCALE_BIGDECIMAL
222
-
223
- @type_map ||= {
224
- Types::Serial => { :primitive => 'integer' },
225
- Types::Boolean => { :primitive => 'boolean' },
226
- Integer => { :primitive => 'integer'},
227
- String => { :primitive => 'string'},
228
- Class => { :primitive => 'string'},
229
- BigDecimal => { :primitive => 'number'},
230
- Float => { :primitive => 'number'},
231
- DateTime => { :primitive => 'string', :format => 'date-time'},
232
- Date => { :primitive => 'string', :format => 'date'},
233
- Time => { :primitive => 'string', :format => 'time'},
234
- TrueClass => { :primitive => 'boolean'},
235
- Types::Text => { :primitive => 'string'},
236
- DataMapper::Types::Object => { :primitive => 'string'},
237
- DataMapper::Types::URI => { :primitive => 'string', :format => 'uri'}
238
- }.freeze
239
- end
240
- end
241
-
242
- # This should go away when we have more methods exposed to retrieve versioned data (and schemas)
243
- attr_accessor :persevere
244
-
245
- ##
246
- # Used by DataMapper to put records into a data-store: "INSERT"
247
- # in SQL-speak. It takes an array of the resources (model
248
- # instances) to be saved. Resources each have a key that can be
249
- # used to quickly look them up later without searching, if the
250
- # adapter supports it.
251
- #
252
- # @param [Array<DataMapper::Resource>] resources
253
- # The set of resources (model instances)
254
- #
255
- # @return [Integer]
256
- # The number of records that were actually saved into the
257
- # data-store
258
- #
259
- # @api semipublic
260
- def create(resources)
261
- connect if @persevere.nil?
262
- created = 0
263
-
264
- check_schemas
265
-
266
- resources.each do |resource|
267
- serial = resource.model.serial(self.name)
268
- path = "/#{resource.model.storage_name}/"
269
- # Invoke to_json_hash with a boolean to indicate this is a create
270
- # We might want to make this a post-to_json_hash cleanup instead
271
- payload = resource.to_json_hash(false).delete_if{|key,value| value.nil? }
272
- DataMapper.logger.debug("--> PATH/PAYLOAD: #{path} #{payload.inspect}")
273
- response = @persevere.create(path, payload)
274
-
275
- # Check the response, this needs to be more robust and raise
276
- # exceptions when there's a problem
277
- if response.code == "201"# good:
278
- rsrc_hash = JSON.parse(response.body)
279
- # Typecast attributes, DM expects them properly cast
280
- resource.model.properties.each do |prop|
281
- value = rsrc_hash[prop.field.to_s]
282
- rsrc_hash[prop.field.to_s] = prop.typecast(value) unless value.nil?
283
- # Shift date/time objects to the correct timezone because persevere is UTC
284
- case prop
285
- when DateTime then rsrc_hash[prop.field.to_s] = value.new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24))
286
- when Time then rsrc_hash[prop.field.to_s] = value.getlocal
287
- end
288
- end
289
-
290
- serial.set!(resource, rsrc_hash["id"]) unless serial.nil?
291
-
292
- created += 1
293
- else
294
- return false
295
- end
296
- end
297
-
298
- # Return the number of resources created in persevere.
299
- return created
300
- end
301
-
302
- ##
303
- # Used by DataMapper to update the attributes on existing
304
- # records in a data-store: "UPDATE" in SQL-speak. It takes a
305
- # hash of the attributes to update with, as well as a query
306
- # object that specifies which resources should be updated.
307
- #
308
- # @param [Hash] attributes
309
- # A set of key-value pairs of the attributes to update the
310
- # resources with.
311
- # @param [DataMapper::Query] query
312
- # The query that should be used to find the resource(s) to
313
- # update.
314
- #
315
- # @return [Integer]
316
- # the number of records that were successfully updated
317
- #
318
- # @api semipublic
319
- def update(attributes, query)
320
- connect if @persevere.nil?
321
- updated = 0
322
-
323
- check_schemas
324
-
325
- if ! query.is_a?(DataMapper::Query)
326
- resources = [query].flatten
327
- else
328
- resources = read_many(query)
329
- end
330
-
331
- resources.each do |resource|
332
- tblname = resource.model.storage_name
333
- path = "/#{tblname}/#{resource.key.first}"
334
- payload = resource.to_json_hash()
335
- DataMapper.logger.debug("--> PATH/PAYLOAD: #{path} #{payload.inspect}")
336
- result = @persevere.update(path, payload)
337
-
338
- if result.code == "200"
339
- updated += 1
340
- else
341
- return false
342
- end
343
- end
344
- return updated
345
- end
346
-
347
- ##
348
- # Look up a single record from the data-store. "SELECT ... LIMIT
349
- # 1" in SQL. Used by Model#get to find a record by its
350
- # identifier(s), and Model#first to find a single record by some
351
- # search query.
352
- #
353
- # @param [DataMapper::Query] query
354
- # The query to be used to locate the resource.
355
- #
356
- # @return [DataMapper::Resource]
357
- # A Resource object representing the record that was found, or
358
- # nil for no matching records.
359
- #
360
- # @api semipublic
361
-
362
- def read_one(query)
363
- # TODO: This would be more efficient if it modified the query to limit = 1,
364
- # rather than getting all of them and only returning the first one.
365
- results = read_many(query)
366
- results[0,1]
367
- end
368
-
369
- ##
370
- # Looks up a collection of records from the data-store: "SELECT"
371
- # in SQL. Used by Model#all to search for a set of records;
372
- # that set is in a DataMapper::Collection object.
373
- #
374
- # @param [DataMapper::Query] query
375
- # The query to be used to seach for the resources
376
- #
377
- # @return [DataMapper::Collection]
378
- # A collection of all the resources found by the query.
379
- #
380
- # @api semipublic
381
- def read_many(query)
382
- connect if @persevere.nil?
383
-
384
- resources = Array.new
385
- tblname = query.model.storage_name
386
-
387
- json_query, headers = query.to_json_query
388
-
389
- path = "/#{tblname}/#{json_query}"
390
- DataMapper.logger.debug("--> PATH/QUERY: #{path}")
391
-
392
- response = @persevere.retrieve(path, headers)
393
-
394
- if response.code.match(/20?/)
395
- results = JSON.parse(response.body)
396
- results.each do |rsrc_hash|
397
- # Typecast attributes, DM expects them properly cast
398
- query.fields.each do |prop|
399
- object_reference = false
400
- pname = prop.field.to_s
401
- if pname[-3,3] == "_id"
402
- pname = pname[0..-4]
403
- object_reference = true
404
- end
405
- value = rsrc_hash[pname]
406
- # Dereference references
407
- unless value.nil?
408
- if value.is_a?(Hash)
409
- if value.has_key?("$ref")
410
- value = value["$ref"].split("/")[-1]
411
- end
412
- elsif value.is_a?(Array)
413
- value = value.map do |v|
414
- if v.has_key?("$ref")
415
- v = v["$ref"].split("/")[-1]
416
- else
417
- v
418
- end
419
- end
420
- end
421
- if prop.field == 'id'
422
- rsrc_hash[pname] = prop.typecast(value.to_s.match(/(#{tblname})?\/?([a-zA-Z0-9_-]+$)/)[2])
423
- else
424
- rsrc_hash[pname] = prop.typecast(value)
425
- end
426
- end
427
- # Shift date/time objects to the correct timezone because persevere is UTC
428
- case prop
429
- when DateTime then rsrc_hash[pname] = value.new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24))
430
- when Time then rsrc_hash[pname] = value.getlocal
431
- end
432
- end
433
- end
434
- resources = query.model.load(results, query)
435
- end
436
- # We could almost elimate this if regexp was working in persevere.
437
-
438
- # This won't work if the RegExp is nested more then 1 layer deep.
439
- if query.conditions.class == DataMapper::Query::Conditions::AndOperation
440
- regexp_conds = query.conditions.operands.select do |obj|
441
- obj.is_a?(DataMapper::Query::Conditions::RegexpComparison) ||
442
- ( obj.is_a?(DataMapper::Query::Conditions::NotOperation) && obj.operand.is_a?(DataMapper::Query::Conditions::RegexpComparison) )
443
- end
444
- regexp_conds.each{|cond| resources = resources.select{|resource| cond.matches?(resource)} }
445
-
446
- end
447
-
448
- # query.match_records(resources)
449
- resources
450
- end
451
-
452
- alias :read :read_many
453
-
454
- ##
455
- # Destroys all the records matching the given query. "DELETE" in SQL.
456
- #
457
- # @param [DataMapper::Query] query
458
- # The query used to locate the resources to be deleted.
459
- #
460
- # @return [Integer]
461
- # The number of records that were deleted.
462
- #
463
- # @api semipublic
464
- def delete(query)
465
- connect if @persevere.nil?
466
-
467
- deleted = 0
468
-
469
- if ! query.is_a?(DataMapper::Query)
470
- resources = [query].flatten
471
- else
472
- resources = read_many(query)
473
- end
474
-
475
- resources.each do |resource|
476
- tblname = resource.model.storage_name
477
- path = "/#{tblname}/#{resource.id}"
478
-
479
- result = @persevere.delete(path)
480
-
481
- if result.code == "204" # ok
482
- deleted += 1
483
- end
484
- end
485
- return deleted
486
- end
487
-
488
- ##
489
- #
490
- # Other methods for the Yogo Data Management Toolkit
491
- #
492
- ##
493
- def get_schema(name = nil, project = nil)
494
- path = nil
495
- single = false
496
-
497
- if name.nil? & project.nil?
498
- path = "/Class/"
499
- elsif project.nil?
500
- path = "/Class/#{name}"
501
- elsif name.nil?
502
- path = "/Class/#{project}/"
503
- else
504
- path = "/Class/#{project}/#{name}"
505
- end
506
- result = @persevere.retrieve(path)
507
- if result.code == "200"
508
- schemas = [JSON.parse(result.body)].flatten.select{ |schema| not RESERVED_CLASSNAMES.include?(schema['id']) }
509
- schemas.each do |schema|
510
- if schema.has_key?('properties')
511
- schema['properties']['id'] = { 'type' => "serial", 'index' => true }
512
- end
513
- end
514
-
515
- return name.nil? ? schemas : schemas[0..0]
516
- else
517
- return false
518
- end
519
- end
520
-
521
- ##
522
- #
523
- def put_schema(schema_hash, project = nil)
524
- path = "/Class/"
525
- if ! project.nil?
526
- if schema_hash.has_key?("id")
527
- if ! schema_hash['id'].index(project)
528
- schema_hash['id'] = "#{project}/#{schema_hash['id']}"
529
- end
530
- else
531
- DataMapper.logger.error("You need an id key/value in the hash")
532
- end
533
- end
534
-
535
- scrub_schema(schema_hash['properties'])
536
- properties = schema_hash.delete('properties')
537
- schema_hash['extends'] = { "$ref" => "/Class/Versioned" } if @options[:versioned]
538
- schema_hash.delete_if{|key,value| value.nil? }
539
- result = @persevere.create(path, schema_hash)
540
- if result.code == '201'
541
- # return JSON.parse(result.body)
542
- schema_hash['properties'] = properties
543
- return update_schema(schema_hash)
544
- else
545
- return false
546
- end
547
- end
548
-
549
- ##
550
- #
551
- def update_schema(schema_hash, project = nil)
552
- id = schema_hash['id']
553
- payload = schema_hash.reject{|key,value| key.to_sym.eql?(:id) }
554
- scrub_schema(payload['properties'])
555
- payload['extends'] = { "$ref" => "/Class/Versioned" } if @options[:versioned]
556
-
557
- if project.nil?
558
- path = "/Class/#{id}"
559
- else
560
- path = "/Class/#{project}/#{id}"
561
- end
562
-
563
- result = @persevere.update(path, payload)
564
-
565
- if result.code == '200'
566
- return result.body
567
- else
568
- return false
569
- end
570
- end
571
-
572
- ##
573
- #
574
- def delete_schema(schema_hash, project = nil)
575
- if ! project.nil?
576
- if schema_hash.has_key?("id")
577
- if ! schema_hash['id'].index(project)
578
- schema_hash['id'] = "#{project}/#{schema_hash['id']}"
579
- end
580
- else
581
- DataMapper.logger.error("You need an id key/value in the hash")
582
- end
583
- end
584
-
585
- path = "/Class/#{schema_hash['id']}"
586
- result = @persevere.delete(path)
587
-
588
- if result.code == "204"
589
- return true
590
- else
591
- return false
592
- end
593
- end
594
-
595
- private
596
-
597
- ##
598
- # Make a new instance of the adapter. The @model_records ivar is
599
- # the 'data-store' for this adapter. It is not shared amongst
600
- # multiple incarnations of this adapter, eg
601
- # DataMapper.setup(:default, :adapter => :in_memory);
602
- # DataMapper.setup(:alternate, :adapter => :in_memory) do not
603
- # share the data-store between them.
604
- #
605
- # @param [String, Symbol] name
606
- # The name of the DataMapper::Repository using this adapter.
607
- # @param [String, Hash] uri_or_options
608
- # The connection uri string, or a hash of options to set up
609
- # the adapter
610
- #
611
- # @api semipublic
612
- def initialize(name, uri_or_options)
613
- super
614
-
615
- if uri_or_options.class
616
- @identity_maps = {}
617
- end
618
-
619
- @options = Hash.new
620
-
621
- uri_or_options.each do |k,v|
622
- @options[k.to_sym] = v
623
- end
624
-
625
- @options[:scheme] = @options[:adapter]
626
- @options.delete(:scheme)
627
-
628
- # @resource_naming_convention = NamingConventions::Resource::Underscored
629
- @resource_naming_convention = lambda do |value|
630
- # value.split('::').map{ |val| Extlib::Inflection.underscore(val) }.join('__')
631
- Extlib::Inflection.underscore(value).gsub('/', '__')
632
- end
633
-
634
- @identity_maps = {}
635
- @persevere = nil
636
- @prepped = false
637
- @schema_backups = Array.new
638
- @last_backup = nil
639
-
640
- connect
641
- end
642
-
643
- private
644
-
645
- ##
646
- #
647
- def connect
648
- if ! @prepped
649
- uri = URI::HTTP.build(@options).to_s
650
- @persevere = Persevere.new(uri)
651
- prep_persvr unless @prepped
652
- end
653
- end
654
-
655
- def scrub_data(json_hash)
656
- items = [DataMapper::Model.descendants.map{|c| "#{c.name.downcase}_id"}].flatten
657
- items.each { |item| json_hash.delete(item) if json_hash.has_key?(item) }
658
- json_hash.reject! { |k,v| v.nil? }
659
- json_hash
660
- end
661
-
662
- ##
663
- #
664
- def scrub_schema(json_hash)
665
- items = [DataMapper::Model.descendants.map{|c| "#{c.name.downcase}_id"}, 'id'].flatten
666
- items.each { |item| json_hash.delete(item) if json_hash.has_key?(item) }
667
- json_hash
668
- end
669
-
670
- def check_schemas
671
- schemas = @persevere.retrieve("/Class").body
672
- md5 = Digest::MD5.hexdigest(schemas)
673
-
674
- if ! @last_backup.nil?
675
- if @last_backup[:hash] != md5
676
- DataMapper.logger.debug("Schemas changed, do you know why? (#{md5} :: #{@last_backup[:hash]})")
677
- @schema_backups.each do |sb|
678
- if sb[:hash] == md5
679
- DataMapper.logger.debug("Schemas reverted to #{sb.inspect}")
680
- end
681
- end
682
- end
683
- end
684
- end
685
-
686
- def save_schemas
687
- schemas = @persevere.retrieve("/Class").body
688
- md5 = Digest::MD5.hexdigest(schemas)
689
- @last_backup = { :hash => md5, :schemas => schemas, :timestamp => Time.now }
690
- @schema_backups << @last_backup
691
- # Dump to filesystem
692
- end
693
-
694
- def get_classes
695
- # Because this is an AbstractAdapter and not a
696
- # DataObjectAdapter, we can't assume there are any schemas
697
- # present, so we retrieve the ones that exist and keep them up
698
- # to date
699
- classes = Array.new
700
- result = @persevere.retrieve('/Class[=id]')
701
- if result.code == "200"
702
- hresult = JSON.parse(result.body)
703
- hresult.each do |cname|
704
- junk,name = cname.split("/")
705
- classes << name
706
- end
707
- else
708
- DataMapper.logger.error("Error retrieving existing tables: #{result}")
709
- end
710
- classes
711
- end
712
-
713
- ##
714
- #
715
- def prep_persvr
716
- #
717
- # If the user specified a versioned datastore load the versioning REST code
718
- #
719
- unless get_classes.include?("Versioned") && @options[:versioned]
720
- versioned_class =<<-EOF
721
- {
722
- id: "Versioned",
723
- prototype: {
724
- getVersionMethod: function() {
725
- return java.lang.Class.forName("org.persvr.data.Persistable").getMethod("getVersion");
726
- },
727
- isCurrentVersion: function() {
728
- return this.getVersionMethod().invoke(this).isCurrent();
729
- },
730
- getVersionNumber: function() {
731
- return this.getVersionMethod().invoke(this).getVersionNumber();
732
- },
733
- getPrevious: function() {
734
- var prev = this.getVersionMethod().invoke(this).getPreviousVersion();
735
- return prev;
736
- },
737
- getAllPrevious: function() {
738
-
739
- var current = this;
740
- var prev = current && current.getPrevious();
741
-
742
- var versions = []
743
- while(current && prev) {
744
- versions.push(prev);
745
- current = prev;
746
- prev = current.getPrevious();
747
- }
748
-
749
- return versions;
750
- },
751
- "representation:application/json+versioned": {
752
- quality: 0.2,
753
- output: function(object) {
754
- var previous = object.getAllPrevious();
755
- response.setContentType("application/json+versioned");
756
- response.getOutputStream().print(JSON.stringify({
757
- version: object.getVersionNumber(),
758
- current: object,
759
- versions: previous
760
- }));
761
- }
762
- }
763
- }
764
- }
765
- EOF
766
-
767
- response = @persevere.create('/Class/', versioned_class, { 'Content-Type' => 'application/javascript' } )
768
-
769
- # Check the response, this needs to be more robust and raise
770
- # exceptions when there's a problem
771
- if response.code == "201"# good:
772
- DataMapper.logger.info("Created versioned class.")
773
- else
774
- DataMapper.logger.info("Failed to create versioned class.")
775
- end
776
-
777
- # headers = { 'Content-Type' => 'application/javascript', 'Accept' => 'application/json' }
778
- # begin
779
- # puts "POST #{URI.encode('/Class')}, #{versioned_class}, #{headers.inspect}"
780
- # response = @persevere.persevere.send_request('POST', URI.encode('/Class/'), versioned_class, headers )
781
- # rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
782
- # Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
783
- # DataMapper.logger.error("Persevere Create Failed: #{e}, Trying again.")
784
- # end
785
- end
786
- end
787
- end # class PersevereAdapter
788
- const_added(:PersevereAdapter)
789
- end # module Adapters
790
- end # module DataMapper