dm-persevere-adapter 0.18.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  pkg
2
2
  tmp
3
3
  coverage
4
+ *.gemspec
data/README.txt CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  A DataMapper adapter for Persevere (http://www.persvr.org/)
4
4
 
5
- This requires the persevere gem (http://github.com/irjudson/persevere) which provides a ruby interface to Persevere.
6
-
7
5
  == Usage
8
6
 
9
7
  DM Persevere Adapter is very simple and very similar to the REST
@@ -21,6 +19,9 @@ DataMapper.setup(:default, {
21
19
  :host => 'localhost',
22
20
  :port => '8080'
23
21
  })
22
+
23
+ # Or,
24
+ # DataMapper.setup(:default, "persevere://localhost:8080")
24
25
 
25
26
  class MyUser
26
27
  include DataMapper::Resource
@@ -57,6 +58,8 @@ production:
57
58
 
58
59
  == Code
59
60
 
61
+ MyUser.auto_migrate!
62
+
60
63
  # Create
61
64
  user = MyUser.new(:username => "dmtest", :uuid => UUID.random_create().to_s,
62
65
  :name => "DataMapper Test", :homedirectory => "/home/dmtest",
@@ -81,5 +84,6 @@ puts "Result: #{result}"
81
84
 
82
85
  == To Do:
83
86
 
84
- - Make a do-adapter for persevere.
85
87
  - Cleanup Documentation
88
+ - Add more negative / failure tests
89
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.18.0
1
+ 0.21.0
@@ -11,41 +11,22 @@ module DataMapper
11
11
  schema_hash = {}
12
12
  schema_hash['id'] = self.storage_name(repository_name)
13
13
  properties_hash = {}
14
- usable_properties.each{|p| properties_hash[p.name] = p.to_json_schema_hash if p.name != :id }
14
+ usable_properties.each{|p| properties_hash[p.name] = p.to_json_schema_hash(repository_name) if p.name != :id }
15
15
  schema_hash['properties'] = properties_hash
16
+ schema_hash['prototype'] = {}
16
17
  return schema_hash
17
18
  end
18
-
19
19
  end
20
-
20
+
21
21
  class Property
22
- def to_json_schema_hash
23
- json_hash = { "type" => to_json_type }
24
- { "optional" => true }.merge(json_hash) unless required? == true
22
+ def to_json_schema_hash(repo)
23
+ tm = repository(repo).adapter.type_map
24
+ json_hash = { "type" => tm[type][:primitive] }
25
+ json_hash.merge!({ "format" => tm[type][:format]}) if tm[type].has_key?(:format)
26
+ json_hash.merge!({ "optional" => true }) unless required? == true
25
27
  # MIN
26
28
  # MAX
27
- end
28
-
29
- private
30
-
31
- def to_json_type
32
- # A case statement doesn't seem to be working when comparing classes.
33
- # That's why we're using a if elseif block.
34
- if type == DataMapper::Types::Serial
35
- return "string"
36
- elsif type == String
37
- return "string"
38
- elsif type == Float
39
- return "number"
40
- elsif type == DataMapper::Types::Boolean
41
- return "boolean"
42
- elsif type == DataMapper::Types::Text
43
- elsif type == "string"
44
- elsif type == Integer
45
- return "integer"
46
- else
47
- return"string"
48
- end
29
+ json_hash
49
30
  end
50
31
  end
51
32
  end
data/lib/persevere.rb CHANGED
@@ -13,6 +13,23 @@ require 'uri'
13
13
  require 'rubygems'
14
14
  require 'json'
15
15
 
16
+ # Ugly Monkey patching because Persever uses non-standard content-range headers.
17
+ module Net
18
+ module HTTPHeader
19
+ alias old_content_range content_range
20
+ # Returns a Range object which represents Content-Range: header field.
21
+ # This indicates, for a partial entity body, where this fragment
22
+ # fits inside the full entity body, as range of byte offsets.
23
+ def content_range
24
+ return nil unless @header['content-range']
25
+ m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(self['Content-Range']) or
26
+ return nil
27
+ m[1].to_i .. m[2].to_i + 1
28
+ end
29
+
30
+ end
31
+ end
32
+
16
33
  class PersevereResult
17
34
  attr_reader :location, :code, :message, :body
18
35
 
@@ -49,7 +66,7 @@ class Persevere
49
66
 
50
67
  # Pass in a resource hash
51
68
  def create(path, resource, headers = {})
52
- json_blob = resource.to_json
69
+ json_blob = resource.reject{|key,value| value.nil? }.to_json
53
70
  response = nil
54
71
  while response.nil?
55
72
  begin
@@ -69,7 +86,7 @@ class Persevere
69
86
  response = @persevere.send_request('GET', path, nil, HEADERS.merge(headers))
70
87
  rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
71
88
  Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
72
- puts "Persevere Create Failed: #{e}, Trying again."
89
+ puts "Persevere Retrieve Failed: #{e}, Trying again."
73
90
  end
74
91
  end
75
92
  return PersevereResult.make(response)
@@ -92,6 +109,7 @@ class Persevere
92
109
 
93
110
  def delete(path, headers = {})
94
111
  response = nil
112
+ # puts "DELETING #{path}"
95
113
  while response.nil?
96
114
  begin
97
115
  response = @persevere.send_request('DELETE', path, nil, HEADERS.merge(headers))
@@ -1,100 +1,223 @@
1
1
  require 'rubygems'
2
2
  require 'dm-core'
3
+ require 'dm-aggregates'
3
4
  require 'extlib'
4
5
  require 'json'
6
+ require 'bigdecimal'
5
7
 
6
8
  require 'model_json_support'
7
9
  require 'persevere'
8
10
 
11
+ class BigDecimal
12
+ alias to_json_old to_json
13
+
14
+ def to_json
15
+ to_s
16
+ end
17
+ end
18
+
9
19
  module DataMapper
10
- module Migrations
11
- module PersevereAdapter
12
- # @api private
13
- def self.included(base)
14
- DataMapper.extend(Migrations::SingletonMethods)
15
-
16
- [ :Repository, :Model ].each do |name|
17
- DataMapper.const_get(name).send(:include, Migrations.const_get(name))
20
+ module Aggregates
21
+ module PersevereAdapter
22
+ def aggregate(query)
23
+ records = []
24
+ fields = query.fields
25
+ field_size = fields.size
26
+
27
+ connect if @persevere.nil?
28
+ resources = Array.new
29
+ json_query = make_json_query(query)
30
+ path = "/#{query.model.storage_name}/#{json_query}"
31
+
32
+ response = @persevere.retrieve(path)
33
+
34
+ if response.code == "200"
35
+ # results = JSON.parse(response.body)
36
+ results = [response.body]
37
+ results.each do |row_of_results|
38
+ row = query.fields.zip([row_of_results].flatten).map do |field, value|
39
+ if field.respond_to?(:operator)
40
+ send(field.operator, field.target, value)
41
+ else
42
+ field.typecast(value)
43
+ end
44
+ end
45
+ records << (field_size > 1 ? row : row[0])
18
46
  end
19
47
  end
48
+ records
49
+ end # aggregate method
50
+
51
+ private
52
+
53
+ def count(property, value)
54
+ value.to_i
55
+ end
56
+
57
+ def min(property, value)
58
+ values = JSON.parse("[#{value}]").flatten.compact
59
+ if values.is_a?(Array)
60
+ values.map! { |v| property.typecast(v) }
61
+ return values.sort[0].new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24)) if property.type == DateTime
62
+ return values.sort[0] + Time.now.gmt_offset if property.type == Time
63
+ return values.sort[0]
64
+ end
65
+ property.typecast(value)
66
+ end
67
+
68
+ def max(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[-1].new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24)) if property.type == DateTime
73
+ return values.sort[-1] + Time.now.gmt_offset if property.type == Time
74
+ return values.sort[-1]
75
+ end
76
+ property.typecast(value)
77
+ end
78
+
79
+ def avg(property, value)
80
+ values = JSON.parse(value).compact
81
+ result = values.inject(0.0){|sum,i| sum+=i }/values.length
82
+ property.type == Integer ? result.to_f : property.typecast(result)
83
+ end
84
+
85
+ def sum(property, value)
86
+ property.typecast(value)
87
+ end
88
+ end # module PersevereAdapter
89
+ end # module Aggregates
20
90
 
21
- # Returns whether the storage_name exists.
22
- #
23
- # @param [String] storage_name
24
- # a String defining the name of a storage, for example a table name.
25
- #
26
- # @return [Boolean]
27
- # true if the storage exists
28
- #
29
- # @api semipublic
30
- def storage_exists?(storage_name)
31
- class_names = JSON.parse(@persevere.retrieve('/Class/[=id]').body)
32
- return true if class_names.include?("Class/"+storage_name)
33
- false
91
+ module Migrations
92
+ module PersevereAdapter
93
+ # @api private
94
+ def self.included(base)
95
+ DataMapper.extend(Migrations::SingletonMethods)
96
+
97
+ [ :Repository, :Model ].each do |name|
98
+ DataMapper.const_get(name).send(:include, Migrations.const_get(name))
34
99
  end
100
+ end
35
101
 
36
- ##
37
- # Creates the persevere schema from the model.
38
- #
39
- # @param [DataMapper::Model] model
40
- # The model that corresponds to the storage schema that needs to be created.
41
- #
42
- # @api semipublic
43
- def create_model_storage(model)
44
- name = self.name
45
- properties = model.properties_with_subclasses(name)
46
-
47
- return false if storage_exists?(model.storage_name(name))
48
- return false if properties.empty?
49
-
50
- schema_hash = model.to_json_schema_compatible_hash
51
-
52
- return true unless put_schema(schema_hash).nil?
53
- false
54
- end
55
-
56
- ##
57
- # Updates the persevere schema from the model.
58
- #
59
- # @param [DataMapper::Model] model
60
- # The model that corresponds to the storage schema that needs to be updated.
61
- #
62
- # @api semipublic
63
- def upgrade_model_storage(model)
64
- name = self.name
65
- properties = model.properties_with_subclasses(name)
66
-
67
- if success = create_model_storage(model)
68
- return properties
69
- end
102
+ # Returns whether the storage_name exists.
103
+ #
104
+ # @param [String] storage_name
105
+ # a String defining the name of a storage, for example a table name.
106
+ #
107
+ # @return [Boolean]
108
+ # true if the storage exists
109
+ #
110
+ # @api semipublic
111
+ def storage_exists?(storage_name)
112
+ class_names = JSON.parse(@persevere.retrieve('/Class/[=id]').body)
113
+ return true if class_names.include?("Class/"+storage_name)
114
+ false
115
+ end
116
+
117
+ ##
118
+ # Creates the persevere schema from the model.
119
+ #
120
+ # @param [DataMapper::Model] model
121
+ # The model that corresponds to the storage schema that needs to be created.
122
+ #
123
+ # @api semipublic
124
+ def create_model_storage(model)
125
+ name = self.name
126
+ properties = model.properties_with_subclasses(name)
127
+
128
+ return false if storage_exists?(model.storage_name(name))
129
+ return false if properties.empty?
130
+
131
+ schema_hash = model.to_json_schema_compatible_hash
132
+
133
+ return true unless put_schema(schema_hash) == false
134
+ false
135
+ end
136
+
137
+ ##
138
+ # Updates the persevere schema from the model.
139
+ #
140
+ # @param [DataMapper::Model] model
141
+ # The model that corresponds to the storage schema that needs to be updated.
142
+ #
143
+ # @api semipublic
144
+ def upgrade_model_storage(model)
145
+ name = self.name
146
+ properties = model.properties_with_subclasses(name)
147
+
148
+ if success = create_model_storage(model)
149
+ return properties
150
+ end
151
+
152
+ table_name = model.storage_name(name)
153
+ schema_hash = model.to_json_schema_compatible_hash
154
+ end
155
+
156
+ ##
157
+ # Destroys the persevere schema from the model.
158
+ #
159
+ # @param [DataMapper::Model] model
160
+ # The model that corresponds to the storage schema that needs to be destroyed.
161
+ #
162
+ # @api semipublic
163
+ def destroy_model_storage(model)
164
+ return true unless storage_exists?(model.storage_name(name))
165
+ schema_hash = model.to_json_schema_compatible_hash
166
+ return true unless delete_schema(schema_hash) == false
167
+ false
168
+ end
169
+
170
+ end # module PersevereAdapter
171
+ end # module Migrations
172
+
173
+ class Reflection
174
+ module PersevereAdapter
175
+ @@reserved_classes = ['User','Transaction','Capability','File','Class']
176
+
177
+ # def reflect!
178
+ # fetch_models.map{|m| DataMapper::Factory.build(m) }
179
+ # end
180
+
181
+ def fetch_models
182
+ JSON.parse(self.get_schema).select{|schema| !@@reserved_classes.include?(schema['id'])}
183
+ end
184
+
185
+ end # module PersevereAdapter
186
+ end # class Reflection
70
187
 
71
- table_name = model.storage_name(name)
72
- schema_hash = model.to_json_schema_compatible_hash
73
- end
74
188
 
75
- ##
76
- # Destroys the persevere schema from the model.
77
- #
78
- # @param [DataMapper::Model] model
79
- # The model that corresponds to the storage schema that needs to be destroyed.
80
- #
81
- # @api semipublic
82
- def destroy_model_storage(model)
83
- return true unless storage_exists?(model.storage_name(name))
84
- schema_hash = model.to_json_schema_compatible_hash
85
- return true unless delete_schema(schema_hash).nil?
86
- false
87
- end
88
-
89
- end # module PersevereAdapter
90
- end # module Migrations
91
-
92
189
  module Adapters
93
190
  class PersevereAdapter < AbstractAdapter
94
191
  extend Chainable
95
192
  extend Deprecate
96
-
193
+
97
194
  include Migrations::PersevereAdapter
195
+
196
+ # Default types for all data object based adapters.
197
+ #
198
+ # @return [Hash] default types for data objects adapters.
199
+ #
200
+ # @api private
201
+ def type_map
202
+ length = Property::DEFAULT_LENGTH
203
+ precision = Property::DEFAULT_PRECISION
204
+ scale = Property::DEFAULT_SCALE_BIGDECIMAL
205
+
206
+ @type_map ||= {
207
+ Types::Serial => { :primitive => 'string' },
208
+ Types::Boolean => { :primitive => 'boolean' },
209
+ Integer => { :primitive => 'integer'},
210
+ String => { :primitive => 'string'},
211
+ Class => { :primitive => 'string'},
212
+ BigDecimal => { :primitive => 'number'},
213
+ Float => { :primitive => 'number'},
214
+ DateTime => { :primitive => 'string', :format => 'date-time'},
215
+ Date => { :primitive => 'string', :format => 'date'},
216
+ Time => { :primitive => 'string', :format => 'time'},
217
+ TrueClass => { :primitive => 'boolean'},
218
+ Types::Text => { :primitive => 'string'}
219
+ }.freeze
220
+ end
98
221
 
99
222
  ##
100
223
  # Used by DataMapper to put records into a data-store: "INSERT"
@@ -125,7 +248,8 @@ module DataMapper
125
248
  tblname = resource.model.storage_name
126
249
 
127
250
  path = "/#{tblname}/"
128
- payload = resource.attributes.reject{ |key,value| value.nil? }
251
+ payload = make_json_compatible_hash(resource)
252
+
129
253
  payload.delete(:id)
130
254
 
131
255
  response = @persevere.create(path, payload)
@@ -138,8 +262,11 @@ module DataMapper
138
262
  # Typecast attributes, DM expects them properly cast
139
263
  resource.model.properties.each do |prop|
140
264
  value = rsrc_hash[prop.field.to_s]
141
- if !value.nil?
142
- rsrc_hash[prop.field.to_s] = prop.typecast(value)
265
+ rsrc_hash[prop.field.to_s] = prop.typecast(value) unless value.nil?
266
+ # Shift date/time objects to the correct timezone because persevere is UTC
267
+ case prop
268
+ when DateTime then rsrc_hash[prop.field.to_s] = value.new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24))
269
+ when Time then rsrc_hash[prop.field.to_s] = value.getlocal
143
270
  end
144
271
  end
145
272
 
@@ -186,7 +313,7 @@ module DataMapper
186
313
  tblname = resource.model.storage_name
187
314
  path = "/#{tblname}/#{resource.id}"
188
315
 
189
- payload = resource.attributes.reject{ |key,value| value.nil? }
316
+ payload = make_json_compatible_hash(resource)
190
317
 
191
318
  result = @persevere.update(path, payload)
192
319
 
@@ -235,21 +362,23 @@ module DataMapper
235
362
  connect if @persevere.nil?
236
363
 
237
364
  resources = Array.new
238
- json_query = make_json_query(query)
365
+ json_query, headers = make_json_query(query)
239
366
 
240
367
  tblname = query.model.storage_name
241
368
  path = "/#{tblname}/#{json_query}"
242
-
243
- response = @persevere.retrieve(path)
244
-
245
- if response.code == "200"
369
+
370
+ response = @persevere.retrieve(path, headers)
371
+ if response.code.match(/20?/)
246
372
  results = JSON.parse(response.body)
247
373
  results.each do |rsrc_hash|
248
374
  # Typecast attributes, DM expects them properly cast
249
375
  query.model.properties.each do |prop|
250
376
  value = rsrc_hash[prop.field.to_s]
251
- if !value.nil?
252
- rsrc_hash[prop.field.to_s] = prop.typecast(value)
377
+ rsrc_hash[prop.field.to_s] = prop.typecast(value) unless value.nil?
378
+ # Shift date/time objects to the correct timezone because persevere is UTC
379
+ case prop
380
+ when DateTime then rsrc_hash[prop.field.to_s] = value.new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24))
381
+ when Time then rsrc_hash[prop.field.to_s] = value.getlocal
253
382
  end
254
383
  end
255
384
  end
@@ -257,7 +386,9 @@ module DataMapper
257
386
  resources = query.model.load(results, query)
258
387
  end
259
388
 
389
+ # We could almost elimate this if regexp was working in persevere.
260
390
  query.filter_records(resources)
391
+ # resources
261
392
  end
262
393
 
263
394
  alias :read :read_many
@@ -314,7 +445,7 @@ module DataMapper
314
445
  end
315
446
 
316
447
  result = @persevere.retrieve(path)
317
-
448
+
318
449
  if result.code == "200"
319
450
  return result.body
320
451
  else
@@ -324,7 +455,7 @@ module DataMapper
324
455
 
325
456
  def put_schema(schema_hash, project = nil)
326
457
  path = "/Class/"
327
-
458
+
328
459
  if ! project.nil?
329
460
  if schema_hash.has_key?("id")
330
461
  if ! schema_hash['id'].index(project)
@@ -334,7 +465,6 @@ module DataMapper
334
465
  puts "You need an id key/value in the hash"
335
466
  end
336
467
  end
337
-
338
468
  result = @persevere.create(path, schema_hash)
339
469
  if result.code == '201'
340
470
  return JSON.parse(result.body)
@@ -342,7 +472,7 @@ module DataMapper
342
472
  return false
343
473
  end
344
474
  end
345
-
475
+
346
476
  def update_schema(schema_hash, project = nil)
347
477
 
348
478
  id = schema_hash['id']
@@ -354,16 +484,16 @@ module DataMapper
354
484
  else
355
485
  path = "/Class/#{project}/#{id}"
356
486
  end
357
- # debugger
487
+
358
488
  result = @persevere.update(path, payload)
359
-
489
+
360
490
  if result.code == '200'
361
491
  return result.body
362
492
  else
363
493
  return false
364
494
  end
365
495
  end
366
-
496
+
367
497
  def delete_schema(schema_hash, project = nil)
368
498
  if ! project.nil?
369
499
  if schema_hash.has_key?("id")
@@ -376,7 +506,7 @@ module DataMapper
376
506
  end
377
507
  path = "/Class/#{schema_hash['id']}"
378
508
  result = @persevere.delete(path)
379
-
509
+
380
510
  if result.code == "204"
381
511
  return true
382
512
  else
@@ -460,11 +590,18 @@ module DataMapper
460
590
  # The DataMapper query object passed in
461
591
  #
462
592
  # @api semipublic
463
- def make_json(resource)
464
- json_rsrc = nil
465
-
466
- # Gather up all the attributes
467
- json_rsrc = resource.attributes.to_json
593
+ def make_json_compatible_hash(resource)
594
+ json_rsrc = Hash.new
595
+ resource.attributes(:property).each do |property, value|
596
+ next if value.nil?
597
+ json_rsrc[property.field] = case value
598
+ when DateTime then value.new_offset(0).strftime("%Y-%m-%dT%H:%M:%SZ")
599
+ when Date then value.to_s
600
+ when Time then value.getutc.strftime("%H:%M:%S")
601
+ else value
602
+ end
603
+ end
604
+ json_rsrc
468
605
  end
469
606
 
470
607
  ##
@@ -474,46 +611,110 @@ module DataMapper
474
611
  # The DataMapper query object passed in
475
612
  #
476
613
  # @api semipublic
477
-
478
614
  def make_json_query(query)
615
+ def process_in(value, candidate_set)
616
+ result_string = Array.new
617
+ candidate_set.to_a.each do |candidate|
618
+ result_string << "#{value}=#{candidate}"
619
+ end
620
+ if result_string.length > 0
621
+ "(#{result_string.join("|")})"
622
+ else
623
+ "#{value}=''"
624
+ end
625
+ end
626
+
627
+ def process_condition(condition)
628
+ case condition
629
+ # Persevere 1.0 regular expressions are disable for security so we pass them back for DataMapper query filtering
630
+ # without regular expressions, the like operator is inordinately challenging hence we pass it back
631
+ # when :like then "RegExp(\"#{condition.value.gsub!('%', '*')}\").test(#{condition.subject.name})"
632
+ # when :regexp then "RegExp(\"#{condition.value.source}\").test(#{condition.subject.name})"
633
+ when DataMapper::Query::Conditions::RegexpComparison then []
634
+ when DataMapper::Query::Conditions::LikeComparison then []
635
+ when DataMapper::Query::Conditions::AndOperation then "(#{condition.operands.map { |op| process_condition(op) }.join("&")})"
636
+ when DataMapper::Query::Conditions::OrOperation then "(#{condition.operands.map { |op| process_condition(op) }.join("|")})"
637
+ when DataMapper::Query::Conditions::NotOperation then
638
+ inside = process_condition(condition.operand)
639
+ inside.empty? ? [] : "!(%s)" % inside
640
+ when DataMapper::Query::Conditions::InclusionComparison then process_in(condition.subject.name, condition.value)
641
+ when DataMapper::Query::Conditions::EqualToComparison then condition.to_s.gsub(' ', '').gsub('nil', 'undefined')
642
+ when Array
643
+ old_statement, bind_values = condition
644
+ statement = old_statement.dup
645
+ bind_values.each{ |bind_value| statement.sub!('?', bind_value.to_s) }
646
+ statement.gsub(' ', '')
647
+ else condition.to_s.gsub(' ', '')
648
+ end
649
+ end
650
+
651
+ json_query = ""
479
652
  query_terms = Array.new
653
+ order_operations = Array.new
654
+ field_ops = Array.new
655
+ headers = Hash.new
480
656
 
481
- conditions = query.conditions
657
+ query.conditions.each do |condition|
658
+ query_terms << process_condition(condition)
659
+ end
482
660
 
483
- conditions.each do |condition|
484
- operator, property, bind_value = condition
485
- if ! property.nil? && !bind_value.nil?
486
- v = property.typecast(bind_value)
487
- if v.is_a?(String)
488
- value = "'#{bind_value}'"
489
- else
490
- value = "#{bind_value}"
661
+ if query_terms.flatten.length != 0
662
+ json_query += "[?#{query_terms.join("][?")}]"
663
+ end
664
+
665
+ query.fields.each do |field|
666
+ if field.respond_to?(:operator)
667
+ field_ops << case field.operator
668
+ when :count then
669
+ if field.target.is_a?(DataMapper::Property)
670
+ "[?#{field.target.name}!=undefined].length"
671
+ else # field.target is all.
672
+ ".length"
673
+ end
674
+ when :min
675
+ if field.target.type == DateTime || field.target.type == Time || field.target.type == Date
676
+ "[=#{field.target.name}]"
677
+ else
678
+ ".min(?#{field.target.name})"
679
+ end
680
+ when :max
681
+ if field.target.type == DateTime || field.target.type == Time || field.target.type == Date
682
+ "[=#{field.target.name}]"
683
+ else
684
+ ".max(?#{field.target.name})"
685
+ end
686
+ when :sum
687
+ ".sum(?#{field.target.name})"
688
+ when :avg
689
+ "[=#{field.target.name}]"
690
+ end
691
+ end
692
+ end
693
+
694
+ json_query += field_ops.join("")
695
+
696
+ if query.order && query.order.any?
697
+ query.order.map do |direction|
698
+ order_operations << case direction.operator
699
+ when :asc then "[\/#{direction.target.field}]"
700
+ when :desc then "[\\#{direction.target.field}]"
491
701
  end
492
-
493
- query_terms << case operator
494
- when :eql then "#{property.field()}=#{value}"
495
- when :lt then "#{property.field()}<#{value}"
496
- when :gt then "#{property.field()}>#{value}"
497
- when :lte then "#{property.field()}<=#{value}"
498
- when :gte then "#{property.field()}=>#{value}"
499
- when :not then "#{property.field()}!=#{value}"
500
- when :like then "#{property.field()}~'*#{value}*'"
501
- else puts "Unknown condition: #{operator}"
502
- end
503
702
  end
504
703
  end
505
704
 
506
- if query_terms.length != 0
507
- query = "?#{query_terms.join("&")}"
508
- else
509
- query = ""
510
- end
705
+ json_query += order_operations.join("")
511
706
 
512
- query
707
+ offset = query.offset.to_i
708
+ limit = query.limit.nil? ? nil : query.limit.to_i + offset - 1
709
+
710
+ if offset != 0 || !limit.nil?
711
+ headers.merge!({"Range", "items=#{offset}-#{limit}"})
712
+ end
713
+ # puts "#{query.inspect}"
714
+ # puts json_query
715
+ return json_query, headers
513
716
  end
514
717
  end # class PersevereAdapter
515
718
  const_added(:PersevereAdapter)
516
719
  end # module Adapters
517
-
518
-
519
- end # module DataMapper
720
+ end # module DataMapper