parse_resource 1.7.3 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.DS_Store +0 -0
- data/.travis.yml +1 -1
- data/Gemfile +4 -8
- data/Gemfile.lock +27 -23
- data/README.md +81 -2
- data/Rakefile +9 -8
- data/VERSION +1 -1
- data/fixtures/.DS_Store +0 -0
- data/fixtures/vcr_cassettes/.DS_Store +0 -0
- data/fixtures/vcr_cassettes/test_all.yml +319 -34
- data/fixtures/vcr_cassettes/test_attribute_getters.yml +256 -12
- data/fixtures/vcr_cassettes/test_attribute_setters.yml +256 -12
- data/fixtures/vcr_cassettes/test_authenticate.yml +260 -32
- data/fixtures/vcr_cassettes/test_chained_wheres.yml +320 -35
- data/fixtures/vcr_cassettes/test_chunk.yml +1359 -0
- data/fixtures/vcr_cassettes/test_count.yml +495 -84
- data/fixtures/vcr_cassettes/test_create.yml +154 -12
- data/fixtures/vcr_cassettes/test_created_at.yml +256 -12
- data/fixtures/vcr_cassettes/test_destroy.yml +364 -32
- data/fixtures/vcr_cassettes/test_destroy_all.yml +236 -48
- data/fixtures/vcr_cassettes/test_each.yml +488 -56
- data/fixtures/vcr_cassettes/test_fetching_closest_10.yml +1509 -0
- data/fixtures/vcr_cassettes/test_fetching_closest_by_kilometers.yml +1509 -0
- data/fixtures/vcr_cassettes/test_fetching_closest_by_miles.yml +1509 -0
- data/fixtures/vcr_cassettes/test_fetching_closest_by_radians.yml +1509 -0
- data/fixtures/vcr_cassettes/test_fetching_closest_within_box.yml +489 -0
- data/fixtures/vcr_cassettes/test_fetching_geopoint_field.yml +489 -0
- data/fixtures/vcr_cassettes/test_find.yml +312 -24
- data/fixtures/vcr_cassettes/test_find_all_by.yml +170 -34
- data/fixtures/vcr_cassettes/test_find_by.yml +174 -38
- data/fixtures/vcr_cassettes/test_first.yml +260 -23
- data/fixtures/vcr_cassettes/test_id.yml +256 -12
- data/fixtures/vcr_cassettes/test_installation_creation.yml +199 -0
- data/fixtures/vcr_cassettes/test_installation_creation_validation_check.yml +297 -0
- data/fixtures/vcr_cassettes/test_limit.yml +1138 -179
- data/fixtures/vcr_cassettes/test_map.yml +488 -56
- data/fixtures/vcr_cassettes/test_order_ascending.yml +395 -0
- data/fixtures/vcr_cassettes/test_order_descending.yml +446 -0
- data/fixtures/vcr_cassettes/test_save.yml +316 -24
- data/fixtures/vcr_cassettes/test_save_all_and_destroy_all.yml +869 -0
- data/fixtures/vcr_cassettes/test_saving_geo_point_with_quick_init.yml +395 -0
- data/fixtures/vcr_cassettes/test_saving_geopoint_with_coords.yml +395 -0
- data/fixtures/vcr_cassettes/test_skip.yml +120 -525
- data/fixtures/vcr_cassettes/test_update.yml +316 -24
- data/fixtures/vcr_cassettes/test_updated_at.yml +316 -24
- data/fixtures/vcr_cassettes/test_username_should_be_unique.yml +311 -21
- data/fixtures/vcr_cassettes/test_where.yml +117 -25
- data/lib/kaminari_extension.rb +60 -0
- data/lib/parse_resource.rb +4 -2
- data/lib/parse_resource/base.rb +262 -163
- data/lib/parse_resource/client.rb +8 -0
- data/lib/parse_resource/parse_error.rb +36 -22
- data/lib/parse_resource/query.rb +99 -7
- data/lib/parse_resource/query_methods.rb +64 -0
- data/lib/parse_resource/types/parse_geopoint.rb +19 -0
- data/parse_resource.gemspec +29 -9
- data/parse_resource.yml +2 -2
- data/test/active_model_lint_test.rb +0 -2
- data/test/helper.rb +13 -3
- data/test/test_parse_installation.rb +41 -0
- data/test/test_parse_resource.rb +108 -20
- data/test/test_parse_user.rb +4 -7
- data/test/test_query_options.rb +0 -38
- data/test/test_types.rb +186 -0
- metadata +38 -31
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
if defined?(Kaminari)
|
|
2
|
+
module KaminariExtension
|
|
3
|
+
module ParseBaseExt
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
include Kaminari::ConfigurationMethods
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def page(num)
|
|
9
|
+
Query.new(self).page(num)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module QueryExt
|
|
15
|
+
extend ActiveSupport::Concern
|
|
16
|
+
include Kaminari::PageScopeMethods
|
|
17
|
+
|
|
18
|
+
included do
|
|
19
|
+
alias :offset :skip
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def limit_value
|
|
23
|
+
criteria[:limit]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def offset_value
|
|
27
|
+
criteria[:skip]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def total_count
|
|
31
|
+
count
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def max_per_page
|
|
35
|
+
@klass.max_per_page
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def page(num)
|
|
39
|
+
limit(@klass.default_per_page).skip(@klass.default_per_page * ([num.to_i, 1].max - 1))
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def per(num)
|
|
44
|
+
if (n = num.to_i) <= 0
|
|
45
|
+
self
|
|
46
|
+
elsif max_per_page && max_per_page < n
|
|
47
|
+
new_offset_value = offset_value / limit_value * max_per_page
|
|
48
|
+
limit(max_per_page).offset(new_offset_value)
|
|
49
|
+
else
|
|
50
|
+
new_offset_value = offset_value / limit_value * n
|
|
51
|
+
limit(n).offset(new_offset_value)
|
|
52
|
+
end
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
ParseResource::Base.send :include, KaminariExtension::ParseBaseExt
|
|
59
|
+
Query.send :include, KaminariExtension::QueryExt
|
|
60
|
+
end
|
data/lib/parse_resource.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
require 'parse_resource/base'
|
|
2
|
+
require 'parse_resource/query_methods'
|
|
3
|
+
require 'parse_resource/client'
|
|
2
4
|
require 'parse_resource/query'
|
|
3
5
|
require 'parse_resource/parse_user'
|
|
4
6
|
require 'parse_resource/parse_user_validator'
|
|
5
7
|
require 'parse_resource/parse_error'
|
|
6
|
-
|
|
8
|
+
require 'kaminari_extension'
|
|
7
9
|
|
|
8
10
|
module ParseResource
|
|
9
|
-
end
|
|
11
|
+
end
|
data/lib/parse_resource/base.rb
CHANGED
|
@@ -6,8 +6,10 @@ require "rest-client"
|
|
|
6
6
|
require "json"
|
|
7
7
|
require "active_support/hash_with_indifferent_access"
|
|
8
8
|
require "parse_resource/query"
|
|
9
|
+
require "parse_resource/query_methods"
|
|
9
10
|
require "parse_resource/parse_error"
|
|
10
11
|
require "parse_resource/parse_exceptions"
|
|
12
|
+
require "parse_resource/types/parse_geopoint"
|
|
11
13
|
|
|
12
14
|
module ParseResource
|
|
13
15
|
|
|
@@ -26,6 +28,8 @@ module ParseResource
|
|
|
26
28
|
extend ActiveModel::Callbacks
|
|
27
29
|
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
|
|
28
30
|
|
|
31
|
+
attr_accessor :error_instances
|
|
32
|
+
|
|
29
33
|
define_model_callbacks :save, :create, :update, :destroy
|
|
30
34
|
|
|
31
35
|
# Instantiates a ParseResource::Base object
|
|
@@ -37,10 +41,12 @@ module ParseResource
|
|
|
37
41
|
|
|
38
42
|
if new
|
|
39
43
|
@unsaved_attributes = attributes
|
|
44
|
+
@unsaved_attributes.stringify_keys!
|
|
40
45
|
else
|
|
41
46
|
@unsaved_attributes = {}
|
|
42
47
|
end
|
|
43
48
|
self.attributes = {}
|
|
49
|
+
self.error_instances = []
|
|
44
50
|
|
|
45
51
|
self.attributes.merge!(attributes)
|
|
46
52
|
self.attributes unless self.attributes.empty?
|
|
@@ -51,18 +57,20 @@ module ParseResource
|
|
|
51
57
|
#
|
|
52
58
|
# @param [Symbol] name the name of the field, eg `:author`.
|
|
53
59
|
# @param [Boolean] val the return value of the field. Only use this within the class.
|
|
54
|
-
def self.field(
|
|
60
|
+
def self.field(fname, val=nil)
|
|
61
|
+
fname = fname.to_sym
|
|
55
62
|
class_eval do
|
|
56
|
-
define_method(
|
|
57
|
-
|
|
63
|
+
define_method(fname) do
|
|
64
|
+
get_attribute("#{fname}")
|
|
58
65
|
end
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
end
|
|
67
|
+
unless self.respond_to? "#{fname}="
|
|
68
|
+
class_eval do
|
|
69
|
+
define_method("#{fname}=") do |val|
|
|
70
|
+
set_attribute("#{fname}", val)
|
|
71
|
+
|
|
72
|
+
val
|
|
73
|
+
end
|
|
66
74
|
end
|
|
67
75
|
end
|
|
68
76
|
end
|
|
@@ -84,72 +92,59 @@ module ParseResource
|
|
|
84
92
|
def to_pointer
|
|
85
93
|
klass_name = self.class.model_name
|
|
86
94
|
klass_name = "_User" if klass_name == "User"
|
|
87
|
-
|
|
95
|
+
klass_name = "_Installation" if klass_name == "Installation"
|
|
96
|
+
{"__type" => "Pointer", "className" => klass_name.to_s, "objectId" => self.id}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.to_date_object(date)
|
|
100
|
+
date = date.to_time if date.respond_to?(:to_time)
|
|
101
|
+
{"__type" => "Date", "iso" => date.iso8601} if date && (date.is_a?(Date) || date.is_a?(DateTime) || date.is_a?(Time))
|
|
88
102
|
end
|
|
89
103
|
|
|
90
104
|
# Creates setter methods for model fields
|
|
91
105
|
def create_setters!(k,v)
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
val
|
|
106
|
+
unless self.respond_to? "#{k}="
|
|
107
|
+
self.class.send(:define_method, "#{k}=") do |val|
|
|
108
|
+
set_attribute("#{k}", val)
|
|
109
|
+
|
|
110
|
+
val
|
|
111
|
+
end
|
|
99
112
|
end
|
|
100
113
|
end
|
|
101
114
|
|
|
102
|
-
def self.method_missing(
|
|
103
|
-
|
|
104
|
-
if
|
|
105
|
-
|
|
106
|
-
finder_name = "find_all_by_#{
|
|
115
|
+
def self.method_missing(method_name, *args)
|
|
116
|
+
method_name = method_name.to_s
|
|
117
|
+
if method_name.start_with?("find_by_")
|
|
118
|
+
attrib = method_name.gsub(/^find_by_/,"")
|
|
119
|
+
finder_name = "find_all_by_#{attrib}"
|
|
107
120
|
|
|
108
121
|
define_singleton_method(finder_name) do |target_value|
|
|
109
|
-
where({
|
|
122
|
+
where({attrib.to_sym => target_value}).first
|
|
110
123
|
end
|
|
111
124
|
|
|
112
125
|
send(finder_name, args[0])
|
|
113
126
|
|
|
114
|
-
elsif
|
|
115
|
-
|
|
116
|
-
finder_name = "find_all_by_#{
|
|
127
|
+
elsif method_name.start_with?("find_all_by_")
|
|
128
|
+
attrib = method_name.gsub(/^find_all_by_/,"")
|
|
129
|
+
finder_name = "find_all_by_#{attrib}"
|
|
117
130
|
|
|
118
131
|
define_singleton_method(finder_name) do |target_value|
|
|
119
|
-
where({
|
|
132
|
+
where({attrib.to_sym => target_value}).all
|
|
120
133
|
end
|
|
121
134
|
|
|
122
135
|
send(finder_name, args[0])
|
|
123
136
|
else
|
|
124
|
-
super(
|
|
137
|
+
super(method_name.to_sym, *args)
|
|
125
138
|
end
|
|
126
139
|
end
|
|
127
140
|
|
|
128
141
|
# Creates getter methods for model fields
|
|
129
142
|
def create_getters!(k,v)
|
|
130
|
-
self.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
when Hash
|
|
134
|
-
|
|
135
|
-
klass_name = @attributes[k]["className"]
|
|
136
|
-
klass_name = "User" if klass_name == "_User"
|
|
137
|
-
|
|
138
|
-
case @attributes[k]["__type"]
|
|
139
|
-
when "Pointer"
|
|
140
|
-
result = klass_name.constantize.find(@attributes[k]["objectId"])
|
|
141
|
-
when "Object"
|
|
142
|
-
result = klass_name.constantize.new(@attributes[k], false)
|
|
143
|
-
when "File"
|
|
144
|
-
result = @attributes[k]["url"]
|
|
145
|
-
end #todo: support Dates and other types https://www.parse.com/docs/rest#objects-types
|
|
146
|
-
|
|
147
|
-
else
|
|
148
|
-
result = @attributes[k]
|
|
143
|
+
unless self.respond_to? "#{k}"
|
|
144
|
+
self.class.send(:define_method, "#{k}") do
|
|
145
|
+
get_attribute("#{k}")
|
|
149
146
|
end
|
|
150
|
-
|
|
151
|
-
result
|
|
152
|
-
end
|
|
147
|
+
end
|
|
153
148
|
end
|
|
154
149
|
|
|
155
150
|
def create_setters_and_getters!
|
|
@@ -170,47 +165,126 @@ module ParseResource
|
|
|
170
165
|
end
|
|
171
166
|
|
|
172
167
|
def self.settings
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
168
|
+
load_settings
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Gets the current class's model name for the URI
|
|
172
|
+
def self.model_name_uri
|
|
173
|
+
if self.model_name == "User"
|
|
174
|
+
"users"
|
|
175
|
+
elsif self.model_name == "Installation"
|
|
176
|
+
"installations"
|
|
177
|
+
else
|
|
178
|
+
"classes/#{self.model_name}"
|
|
178
179
|
end
|
|
179
|
-
@@settings
|
|
180
180
|
end
|
|
181
|
+
|
|
182
|
+
# Gets the current class's Parse.com base_uri
|
|
183
|
+
def self.model_base_uri
|
|
184
|
+
"https://api.parse.com/1/#{model_name_uri}"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Gets the current instance's parent class's Parse.com base_uri
|
|
188
|
+
def model_base_uri
|
|
189
|
+
self.class.send(:model_base_uri)
|
|
190
|
+
end
|
|
191
|
+
|
|
181
192
|
|
|
182
193
|
# Creates a RESTful resource
|
|
183
194
|
# sends requests to [base_uri]/[classname]
|
|
184
195
|
#
|
|
185
196
|
def self.resource
|
|
186
|
-
|
|
187
|
-
path = "config/parse_resource.yml"
|
|
188
|
-
environment = defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : ENV["RACK_ENV"]
|
|
189
|
-
@@settings = YAML.load(ERB.new(File.new(path).read).result)[environment]
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
if model_name == "User" #https://parse.com/docs/rest#users-signup
|
|
193
|
-
base_uri = "https://api.parse.com/1/users"
|
|
194
|
-
else
|
|
195
|
-
base_uri = "https://api.parse.com/1/classes/#{model_name}"
|
|
196
|
-
end
|
|
197
|
+
load_settings
|
|
197
198
|
|
|
198
199
|
#refactor to settings['app_id'] etc
|
|
199
200
|
app_id = @@settings['app_id']
|
|
200
201
|
master_key = @@settings['master_key']
|
|
201
|
-
RestClient::Resource.new(
|
|
202
|
+
RestClient::Resource.new(self.model_base_uri, app_id, master_key)
|
|
202
203
|
end
|
|
203
|
-
|
|
204
|
-
#
|
|
205
|
-
#
|
|
204
|
+
|
|
205
|
+
# Batch requests
|
|
206
|
+
# Sends multiple requests to /batch
|
|
207
|
+
# Set slice_size to send larger batches. Defaults to 20 to prevent timeouts.
|
|
208
|
+
# Parse doesn't support batches of over 20.
|
|
206
209
|
#
|
|
207
|
-
def self.
|
|
208
|
-
if
|
|
210
|
+
def self.batch_save(save_objects, slice_size = 20, method = nil)
|
|
211
|
+
return true if save_objects.blank?
|
|
212
|
+
load_settings
|
|
213
|
+
|
|
214
|
+
base_uri = "https://api.parse.com/1/batch"
|
|
215
|
+
app_id = @@settings['app_id']
|
|
216
|
+
master_key = @@settings['master_key']
|
|
217
|
+
|
|
218
|
+
res = RestClient::Resource.new(base_uri, app_id, master_key)
|
|
219
|
+
|
|
220
|
+
# Batch saves seem to fail if they're too big. We'll slice it up into multiple posts if they are.
|
|
221
|
+
save_objects.each_slice(slice_size) do |objects|
|
|
222
|
+
# attributes_for_saving
|
|
223
|
+
batch_json = { "requests" => [] }
|
|
224
|
+
|
|
225
|
+
objects.each do |item|
|
|
226
|
+
method ||= (item.new?) ? "POST" : "PUT"
|
|
227
|
+
object_path = "/1/#{item.class.model_name_uri}"
|
|
228
|
+
object_path = "#{object_path}/#{item.id}" if item.id
|
|
229
|
+
json = {
|
|
230
|
+
"method" => method,
|
|
231
|
+
"path" => object_path
|
|
232
|
+
}
|
|
233
|
+
json["body"] = item.attributes_for_saving unless method == "DELETE"
|
|
234
|
+
batch_json["requests"] << json
|
|
235
|
+
end
|
|
236
|
+
res.post(batch_json.to_json, :content_type => "application/json") do |resp, req, res, &block|
|
|
237
|
+
response = JSON.parse(resp) rescue nil
|
|
238
|
+
if resp.code == 400
|
|
239
|
+
puts resp
|
|
240
|
+
return false
|
|
241
|
+
end
|
|
242
|
+
if response && response.is_a?(Array) && response.length == objects.length
|
|
243
|
+
merge_all_attributes(objects, response) unless method == "DELETE"
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
true
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def self.merge_all_attributes(objects, response)
|
|
251
|
+
i = 0
|
|
252
|
+
objects.each do |item|
|
|
253
|
+
item.merge_attributes(response[i]["success"]) if response[i] && response[i]["success"]
|
|
254
|
+
i += 1
|
|
255
|
+
end
|
|
256
|
+
nil
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def self.save_all(objects)
|
|
260
|
+
batch_save(objects)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def self.destroy_all(objects=nil)
|
|
264
|
+
objects ||= self.all
|
|
265
|
+
batch_save(objects, 20, "DELETE")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def self.delete_all(o)
|
|
269
|
+
raise StandardError.new("Parse Resource: delete_all doesn't exist. Did you mean destroy_all?")
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def self.load_settings
|
|
273
|
+
@@settings ||= begin
|
|
209
274
|
path = "config/parse_resource.yml"
|
|
210
275
|
environment = defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : ENV["RACK_ENV"]
|
|
211
|
-
|
|
276
|
+
YAML.load(ERB.new(File.new(path).read).result)[environment]
|
|
212
277
|
end
|
|
278
|
+
@@settings
|
|
279
|
+
end
|
|
280
|
+
|
|
213
281
|
|
|
282
|
+
# Creates a RESTful resource for file uploads
|
|
283
|
+
# sends requests to [base_uri]/files
|
|
284
|
+
#
|
|
285
|
+
def self.upload(file_instance, filename, options={})
|
|
286
|
+
load_settings
|
|
287
|
+
|
|
214
288
|
base_uri = "https://api.parse.com/1/files"
|
|
215
289
|
|
|
216
290
|
#refactor to settings['app_id'] etc
|
|
@@ -220,10 +294,12 @@ module ParseResource
|
|
|
220
294
|
options[:content_type] ||= 'image/jpg' # TODO: Guess mime type here.
|
|
221
295
|
file_instance = File.new(file_instance, 'rb') if file_instance.is_a? String
|
|
222
296
|
|
|
297
|
+
filename = filename.parameterize
|
|
298
|
+
|
|
223
299
|
private_resource = RestClient::Resource.new "#{base_uri}/#{filename}", app_id, master_key
|
|
224
300
|
private_resource.post(file_instance, options) do |resp, req, res, &block|
|
|
225
301
|
return false if resp.code == 400
|
|
226
|
-
return JSON.parse(resp)
|
|
302
|
+
return JSON.parse(resp) rescue {"code" => 0, "error" => "unknown error"}
|
|
227
303
|
end
|
|
228
304
|
false
|
|
229
305
|
end
|
|
@@ -243,48 +319,14 @@ module ParseResource
|
|
|
243
319
|
Query.new(self).where(*args)
|
|
244
320
|
end
|
|
245
321
|
|
|
246
|
-
# Include the attributes of a parent ojbect in the results
|
|
247
|
-
# Similar to ActiveRecord eager loading
|
|
248
|
-
#
|
|
249
|
-
def self.include_object(parent)
|
|
250
|
-
Query.new(self).include_object(parent)
|
|
251
|
-
end
|
|
252
322
|
|
|
253
|
-
|
|
254
|
-
def self.count
|
|
255
|
-
#https://www.parse.com/docs/rest#queries-counting
|
|
256
|
-
Query.new(self).count(1)
|
|
257
|
-
end
|
|
323
|
+
include ParseResource::QueryMethods
|
|
258
324
|
|
|
259
|
-
# Find all ParseResource::Base objects for that model.
|
|
260
|
-
#
|
|
261
|
-
# @return [Array] an `Array` of objects that subclass `ParseResource`.
|
|
262
|
-
def self.all
|
|
263
|
-
Query.new(self).all
|
|
264
|
-
end
|
|
265
325
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def self.first
|
|
269
|
-
Query.new(self).limit(1).first
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
# Limits the number of objects returned
|
|
273
|
-
#
|
|
274
|
-
def self.limit(n)
|
|
275
|
-
Query.new(self).limit(n)
|
|
326
|
+
def self.chunk(attribute)
|
|
327
|
+
Query.new(self).chunk(attribute)
|
|
276
328
|
end
|
|
277
329
|
|
|
278
|
-
# Skip the number of objects
|
|
279
|
-
#
|
|
280
|
-
def self.skip(n)
|
|
281
|
-
Query.new(self).skip(n)
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
#def self.order(attribute)
|
|
285
|
-
# Query.new(self).order(attribute)
|
|
286
|
-
#end
|
|
287
|
-
|
|
288
330
|
# Create a ParseResource::Base object.
|
|
289
331
|
#
|
|
290
332
|
# @param [Hash] attributes a `Hash` of attributes
|
|
@@ -296,11 +338,12 @@ module ParseResource
|
|
|
296
338
|
obj
|
|
297
339
|
end
|
|
298
340
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
end
|
|
341
|
+
# Replaced with a batch destroy_all method.
|
|
342
|
+
# def self.destroy_all(all)
|
|
343
|
+
# all.each do |object|
|
|
344
|
+
# object.destroy
|
|
345
|
+
# end
|
|
346
|
+
# end
|
|
304
347
|
|
|
305
348
|
def self.class_attributes
|
|
306
349
|
@class_attributes ||= {}
|
|
@@ -329,28 +372,18 @@ module ParseResource
|
|
|
329
372
|
self.class.resource["#{self.id}"]
|
|
330
373
|
end
|
|
331
374
|
|
|
332
|
-
def
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
# https://www.parse.com/docs/ios/api/Classes/PFConstants.html
|
|
341
|
-
error_response = JSON.parse(resp)
|
|
342
|
-
pe = ParseError.new(error_response["code"]).to_array
|
|
343
|
-
self.errors.add(pe[0], pe[1])
|
|
344
|
-
return false
|
|
375
|
+
def pointerize(hash)
|
|
376
|
+
new_hash = {}
|
|
377
|
+
hash.each do |k, v|
|
|
378
|
+
if v.respond_to?(:to_pointer)
|
|
379
|
+
new_hash[k] = v.to_pointer
|
|
380
|
+
elsif v.is_a?(Date) || v.is_a?(Time) || v.is_a?(DateTime)
|
|
381
|
+
new_hash[k] = self.class.to_date_object(v)
|
|
345
382
|
else
|
|
346
|
-
|
|
347
|
-
@attributes.merge!(@unsaved_attributes)
|
|
348
|
-
attributes = HashWithIndifferentAccess.new(attributes)
|
|
349
|
-
@unsaved_attributes = {}
|
|
350
|
-
create_setters_and_getters!
|
|
351
|
-
return true
|
|
383
|
+
new_hash[k] = v
|
|
352
384
|
end
|
|
353
385
|
end
|
|
386
|
+
new_hash
|
|
354
387
|
end
|
|
355
388
|
|
|
356
389
|
def save
|
|
@@ -368,39 +401,60 @@ module ParseResource
|
|
|
368
401
|
rescue false
|
|
369
402
|
end
|
|
370
403
|
|
|
404
|
+
def create
|
|
405
|
+
attrs = attributes_for_saving.to_json
|
|
406
|
+
opts = {:content_type => "application/json"}
|
|
407
|
+
result = self.resource.post(attrs, opts) do |resp, req, res, &block|
|
|
408
|
+
return post_result(resp, req, res, &block)
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
371
412
|
def update(attributes = {})
|
|
372
413
|
|
|
373
414
|
attributes = HashWithIndifferentAccess.new(attributes)
|
|
374
415
|
|
|
375
416
|
@unsaved_attributes.merge!(attributes)
|
|
376
|
-
|
|
377
|
-
put_attrs = @unsaved_attributes
|
|
378
|
-
put_attrs.delete('objectId')
|
|
379
|
-
put_attrs.delete('createdAt')
|
|
380
|
-
put_attrs.delete('updatedAt')
|
|
381
|
-
put_attrs = put_attrs.to_json
|
|
417
|
+
put_attrs = attributes_for_saving.to_json
|
|
382
418
|
|
|
383
419
|
opts = {:content_type => "application/json"}
|
|
384
420
|
result = self.instance_resource.put(put_attrs, opts) do |resp, req, res, &block|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
421
|
+
return post_result(resp, req, res, &block)
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Merges in the return value of a save and resets the unsaved_attributes
|
|
426
|
+
def merge_attributes(results)
|
|
427
|
+
@attributes.merge!(results)
|
|
428
|
+
@attributes.merge!(@unsaved_attributes)
|
|
429
|
+
@unsaved_attributes = {}
|
|
430
|
+
create_setters_and_getters!
|
|
431
|
+
@attributes
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def post_result(resp, req, res, &block)
|
|
435
|
+
if resp.code.to_s == "200" || resp.code.to_s == "201"
|
|
436
|
+
merge_attributes(JSON.parse(resp))
|
|
437
|
+
return true
|
|
438
|
+
else
|
|
439
|
+
error_response = JSON.parse(resp)
|
|
440
|
+
if error_response["error"]
|
|
441
|
+
pe = ParseError.new(error_response["code"], error_response["error"])
|
|
394
442
|
else
|
|
395
|
-
|
|
396
|
-
@attributes.merge!(JSON.parse(resp))
|
|
397
|
-
@attributes.merge!(@unsaved_attributes)
|
|
398
|
-
@unsaved_attributes = {}
|
|
399
|
-
create_setters_and_getters!
|
|
400
|
-
|
|
401
|
-
return true
|
|
443
|
+
pe = ParseError.new(resp.code.to_s)
|
|
402
444
|
end
|
|
403
|
-
|
|
445
|
+
self.errors.add(pe.code.to_s.to_sym, pe.msg)
|
|
446
|
+
self.error_instances << pe
|
|
447
|
+
return false
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def attributes_for_saving
|
|
452
|
+
@unsaved_attributes = pointerize(@unsaved_attributes)
|
|
453
|
+
put_attrs = @unsaved_attributes
|
|
454
|
+
put_attrs.delete('objectId')
|
|
455
|
+
put_attrs.delete('createdAt')
|
|
456
|
+
put_attrs.delete('updatedAt')
|
|
457
|
+
put_attrs
|
|
404
458
|
end
|
|
405
459
|
|
|
406
460
|
def update_attributes(attributes = {})
|
|
@@ -425,6 +479,14 @@ module ParseResource
|
|
|
425
479
|
|
|
426
480
|
self
|
|
427
481
|
end
|
|
482
|
+
|
|
483
|
+
def dirty?
|
|
484
|
+
@unsaved_attributes.length > 0
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def clean?
|
|
488
|
+
!dirty?
|
|
489
|
+
end
|
|
428
490
|
|
|
429
491
|
# provides access to @attributes for getting and setting
|
|
430
492
|
def attributes
|
|
@@ -438,12 +500,49 @@ module ParseResource
|
|
|
438
500
|
@attributes
|
|
439
501
|
end
|
|
440
502
|
|
|
503
|
+
def get_attribute(k)
|
|
504
|
+
attrs = @unsaved_attributes[k.to_s] ? @unsaved_attributes : @attributes
|
|
505
|
+
case attrs[k]
|
|
506
|
+
when Hash
|
|
507
|
+
klass_name = attrs[k]["className"]
|
|
508
|
+
klass_name = "User" if klass_name == "_User"
|
|
509
|
+
case attrs[k]["__type"]
|
|
510
|
+
when "Pointer"
|
|
511
|
+
result = klass_name.constantize.find(attrs[k]["objectId"])
|
|
512
|
+
when "Object"
|
|
513
|
+
result = klass_name.constantize.new(attrs[k], false)
|
|
514
|
+
when "Date"
|
|
515
|
+
result = DateTime.parse(attrs[k]["iso"]).to_time_in_current_zone
|
|
516
|
+
when "File"
|
|
517
|
+
result = attrs[k]["url"]
|
|
518
|
+
when "GeoPoint"
|
|
519
|
+
result = ParseGeoPoint.new(attrs[k])
|
|
520
|
+
end #todo: support other types https://www.parse.com/docs/rest#objects-types
|
|
521
|
+
else
|
|
522
|
+
result = attrs["#{k}"]
|
|
523
|
+
end
|
|
524
|
+
result
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def set_attribute(k, v)
|
|
528
|
+
if v.is_a?(Date) || v.is_a?(Time) || v.is_a?(DateTime)
|
|
529
|
+
v = self.class.to_date_object(v)
|
|
530
|
+
elsif v.respond_to?(:to_pointer)
|
|
531
|
+
v = v.to_pointer
|
|
532
|
+
end
|
|
533
|
+
@unsaved_attributes[k.to_s] = v unless v == @attributes[k.to_s] # || @unsaved_attributes[k.to_s]
|
|
534
|
+
@attributes[k.to_s] = v
|
|
535
|
+
v
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
|
|
441
539
|
# aliasing for idiomatic Ruby
|
|
442
|
-
def id;
|
|
540
|
+
def id; get_attribute("objectId") rescue nil; end
|
|
541
|
+
def objectId; get_attribute("objectId") rescue nil; end
|
|
443
542
|
|
|
444
|
-
def created_at;
|
|
543
|
+
def created_at; get_attribute("createdAt"); end
|
|
445
544
|
|
|
446
|
-
def updated_at;
|
|
545
|
+
def updated_at; get_attribute("updatedAt"); rescue nil; end
|
|
447
546
|
|
|
448
547
|
def self.included(base)
|
|
449
548
|
base.extend(ClassMethods)
|