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 +1 -0
- data/README.txt +7 -3
- data/VERSION +1 -1
- data/lib/model_json_support.rb +9 -28
- data/lib/persevere.rb +20 -2
- data/lib/persevere_adapter.rb +331 -130
- data/spec/persevere_adapter_spec.rb +132 -84
- data/spec/persevere_spec.rb +143 -69
- data/spec/spec_helper.rb +7 -44
- metadata +66 -70
- data/spec/adapter_shared_spec.rb +0 -311
- data/spec/lib/adapter_helpers.rb +0 -105
data/.gitignore
CHANGED
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.
|
1
|
+
0.21.0
|
data/lib/model_json_support.rb
CHANGED
@@ -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
|
-
|
24
|
-
{ "
|
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
|
-
|
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
|
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))
|
data/lib/persevere_adapter.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
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
|
-
|
142
|
-
|
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
|
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
|
-
|
252
|
-
|
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
|
-
|
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
|
464
|
-
json_rsrc =
|
465
|
-
|
466
|
-
|
467
|
-
|
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
|
657
|
+
query.conditions.each do |condition|
|
658
|
+
query_terms << process_condition(condition)
|
659
|
+
end
|
482
660
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
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
|
-
|
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
|