Class: ParseResource::Base

Inherits:
Object
  • Object
show all
Extended by:
ActiveModel::Callbacks, ActiveModel::Naming
Includes:
ActiveModel::AttributeMethods, ActiveModel::Conversion, ActiveModel::Validations, QueryMethods
Defined in:
lib/parse_resource/base.rb

Direct Known Subclasses

ParseUser

Defined Under Namespace

Modules: ClassMethods

Constant Summary collapse

HashWithIndifferentAccess =
ActiveSupport::HashWithIndifferentAccess

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}, new = true) ⇒ ParseResource::Base

Instantiates a ParseResource::Base object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/parse_resource/base.rb', line 39

def initialize(attributes = {}, new=true)
  #attributes = HashWithIndifferentAccess.new(attributes)

  if new
    @unsaved_attributes = attributes
    @unsaved_attributes.stringify_keys!
  else
    @unsaved_attributes = {}
  end
  self.attributes = {}
  self.error_instances = []
        
  self.attributes.merge!(attributes)
  self.attributes unless self.attributes.empty?
  create_setters_and_getters!
end

Instance Attribute Details

#error_instancesObject

Returns the value of attribute error_instances.



31
32
33
# File 'lib/parse_resource/base.rb', line 31

def error_instances
  @error_instances
end

Class Method Details

.batch_save(save_objects, slice_size = 20, method = nil) ⇒ Object

Batch requests Sends multiple requests to /batch Set slice_size to send larger batches. Defaults to 20 to prevent timeouts. Parse doesn’t support batches of over 20.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/parse_resource/base.rb', line 210

def self.batch_save(save_objects, slice_size = 20, method = nil)
  return true if save_objects.blank?
  load_settings
  
  base_uri = "https://api.parse.com/1/batch"
  app_id     = @@settings['app_id']
  master_key = @@settings['master_key']
  
  res = RestClient::Resource.new(base_uri, app_id, master_key)
    
  # Batch saves seem to fail if they're too big. We'll slice it up into multiple posts if they are.
  save_objects.each_slice(slice_size) do |objects|
    # attributes_for_saving
    batch_json = { "requests" => [] }
    
    objects.each do |item|
      method ||= (item.new?) ? "POST" : "PUT"
      object_path = "/1/#{item.class.model_name_uri}"
      object_path = "#{object_path}/#{item.id}" if item.id
      json = {
        "method" => method,
        "path" => object_path
      }
      json["body"] = item.attributes_for_saving unless method == "DELETE"
      batch_json["requests"] << json
    end
    res.post(batch_json.to_json, :content_type => "application/json") do |resp, req, res, &block|
      response = JSON.parse(resp) rescue nil
      if resp.code == 400
        puts resp
        return false
      end
      if response && response.is_a?(Array) && response.length == objects.length
        merge_all_attributes(objects, response) unless method == "DELETE"
      end
    end
  end
  true
end

.belongs_to(parent, options = {}) ⇒ Object

Similar to its ActiveRecord counterpart.

Parameters:

  • options (Hash) (defaults to: {})

    Added so that you can specify :class_name => ‘…’. It does nothing at all, but helps you write self-documenting code.



88
89
90
# File 'lib/parse_resource/base.rb', line 88

def self.belongs_to(parent, options = {})
  field(parent)
end

.chunk(attribute) ⇒ Object



326
327
328
# File 'lib/parse_resource/base.rb', line 326

def self.chunk(attribute)
  Query.new(self).chunk(attribute)
end

.class_attributesObject

Replaced with a batch destroy_all method. def self.destroy_all(all)

all.each do |object|
  object.destroy
end

end



348
349
350
# File 'lib/parse_resource/base.rb', line 348

def self.class_attributes
  @class_attributes ||= {}
end

.create(attributes = {}) ⇒ ParseResource

Create a ParseResource::Base object.

Parameters:

  • attributes (Hash) (defaults to: {})

    a ‘Hash` of attributes

Returns:

  • (ParseResource)

    an object that subclasses ‘ParseResource`. Or returns `false` if object fails to save.



334
335
336
337
338
339
# File 'lib/parse_resource/base.rb', line 334

def self.create(attributes = {})
  attributes = HashWithIndifferentAccess.new(attributes)
  obj = new(attributes)
  obj.save
  obj
end

.delete_all(o) ⇒ Object

Raises:

  • (StandardError)


268
269
270
# File 'lib/parse_resource/base.rb', line 268

def self.delete_all(o)
  raise StandardError.new("Parse Resource: delete_all doesn't exist. Did you mean destroy_all?")
end

.destroy_all(objects = nil) ⇒ Object



263
264
265
266
# File 'lib/parse_resource/base.rb', line 263

def self.destroy_all(objects=nil)
  objects ||= self.all
  batch_save(objects, 20, "DELETE")
end

.field(fname, val = nil) ⇒ Object

Explicitly adds a field to the model.

Parameters:

  • name (Symbol)

    the name of the field, eg ‘:author`.

  • val (Boolean) (defaults to: nil)

    the return value of the field. Only use this within the class.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/parse_resource/base.rb', line 60

def self.field(fname, val=nil)
  fname = fname.to_sym
  class_eval do
    define_method(fname) do
      get_attribute("#{fname}")
    end
  end
  unless self.respond_to? "#{fname}="
    class_eval do
      define_method("#{fname}=") do |val|
        set_attribute("#{fname}", val)
        
        val
      end
    end
  end
end

.fields(*args) ⇒ Object

Add multiple fields in one line. Same as ‘#field`, but accepts multiple args.

Parameters:

  • *args (Array)

    an array of ‘Symbol`s, `eg :author, :body, :title`.



81
82
83
# File 'lib/parse_resource/base.rb', line 81

def self.fields(*args)
  args.each {|f| field(f)}
end

.find(id) ⇒ ParseResource

Find a ParseResource::Base object by ID

Parameters:

  • id (String)

    the ID of the Parse object you want to find.

Returns:

Raises:



311
312
313
314
# File 'lib/parse_resource/base.rb', line 311

def self.find(id)
			raise RecordNotFound if id.blank?
  where(:objectId => id).first
end

.included(base) ⇒ Object



547
548
549
# File 'lib/parse_resource/base.rb', line 547

def self.included(base)
  base.extend(ClassMethods)
end

.load!(app_id, master_key) ⇒ Object

Explicitly set Parse.com API keys.

Parameters:

  • app_id (String)

    the Application ID of your Parse database

  • master_key (String)

    the Master Key of your Parse database



163
164
165
# File 'lib/parse_resource/base.rb', line 163

def self.load!(app_id, master_key)
  @@settings = {"app_id" => app_id, "master_key" => master_key}
end

.load_settingsObject



272
273
274
275
276
277
278
279
# File 'lib/parse_resource/base.rb', line 272

def self.load_settings
  @@settings ||= begin
    path = "config/parse_resource.yml"
    environment = defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : ENV["RACK_ENV"]
    YAML.load(ERB.new(File.new(path).read).result)[environment]
  end
  @@settings
end

.merge_all_attributes(objects, response) ⇒ Object



250
251
252
253
254
255
256
257
# File 'lib/parse_resource/base.rb', line 250

def self.merge_all_attributes(objects, response)
  i = 0
  objects.each do |item|
    item.merge_attributes(response[i]["success"]) if response[i] && response[i]["success"]
    i += 1
  end
  nil
end

.method_missing(method_name, *args) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/parse_resource/base.rb', line 115

def self.method_missing(method_name, *args)
  method_name = method_name.to_s
  if method_name.start_with?("find_by_")
    attrib   = method_name.gsub(/^find_by_/,"")
    finder_name = "find_all_by_#{attrib}"

    define_singleton_method(finder_name) do |target_value|
      where({attrib.to_sym => target_value}).first
    end

    send(finder_name, args[0])

  elsif method_name.start_with?("find_all_by_")
    attrib   = method_name.gsub(/^find_all_by_/,"")
    finder_name = "find_all_by_#{attrib}"

    define_singleton_method(finder_name) do |target_value|
      where({attrib.to_sym => target_value}).all
    end

    send(finder_name, args[0])
  else
    super(method_name.to_sym, *args)
  end
end

.model_base_uriObject

Gets the current class’s Parse.com base_uri



183
184
185
# File 'lib/parse_resource/base.rb', line 183

def self.model_base_uri
  "https://api.parse.com/1/#{model_name_uri}"
end

.model_name_uriObject

Gets the current class’s model name for the URI



172
173
174
175
176
177
178
179
180
# File 'lib/parse_resource/base.rb', line 172

def self.model_name_uri
  if self.model_name == "User"
    "users"
  elsif self.model_name == "Installation"
    "installations"
  else
    "classes/#{self.model_name}"
  end
end

.resourceObject

Creates a RESTful resource sends requests to [base_uri]/



196
197
198
199
200
201
202
203
# File 'lib/parse_resource/base.rb', line 196

def self.resource
  load_settings

  #refactor to settings['app_id'] etc
  app_id     = @@settings['app_id']
  master_key = @@settings['master_key']
  RestClient::Resource.new(self.model_base_uri, app_id, master_key)
end

.save_all(objects) ⇒ Object



259
260
261
# File 'lib/parse_resource/base.rb', line 259

def self.save_all(objects)
  batch_save(objects)
end

.settingsObject



167
168
169
# File 'lib/parse_resource/base.rb', line 167

def self.settings
  load_settings
end

.to_date_object(date) ⇒ Object



99
100
101
102
# File 'lib/parse_resource/base.rb', line 99

def self.to_date_object(date)
  date = date.to_time if date.respond_to?(:to_time)
  {"__type" => "Date", "iso" => date.iso8601} if date && (date.is_a?(Date) || date.is_a?(DateTime) || date.is_a?(Time))
end

.upload(file_instance, filename, options = {}) ⇒ Object

Creates a RESTful resource for file uploads sends requests to [base_uri]/files



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/parse_resource/base.rb', line 285

def self.upload(file_instance, filename, options={})
  load_settings
  
  base_uri = "https://api.parse.com/1/files"
  
  #refactor to settings['app_id'] etc
  app_id     = @@settings['app_id']
  master_key = @@settings['master_key']

  options[:content_type] ||= 'image/jpg' # TODO: Guess mime type here.
  file_instance = File.new(file_instance, 'rb') if file_instance.is_a? String

  filename = filename.parameterize

  private_resource = RestClient::Resource.new "#{base_uri}/#{filename}", app_id, master_key
  private_resource.post(file_instance, options) do |resp, req, res, &block|
    return false if resp.code == 400
    return JSON.parse(resp) rescue {"code" => 0, "error" => "unknown error"}
  end
  false
end

.where(*args) ⇒ Object

Find a ParseResource::Base object by chaining #where method calls.



318
319
320
# File 'lib/parse_resource/base.rb', line 318

def self.where(*args)
  Query.new(self).where(*args)
end

Instance Method Details

#attributesObject

provides access to @attributes for getting and setting



492
493
494
495
# File 'lib/parse_resource/base.rb', line 492

def attributes
  @attributes ||= self.class.class_attributes
  @attributes
end

#attributes=(n) ⇒ Object

AKN 2012-06-18: Shouldn’t this also be setting @unsaved_attributes?



498
499
500
501
# File 'lib/parse_resource/base.rb', line 498

def attributes=(n)
  @attributes = n
  @attributes
end

#attributes_for_savingObject



451
452
453
454
455
456
457
458
# File 'lib/parse_resource/base.rb', line 451

def attributes_for_saving
  @unsaved_attributes = pointerize(@unsaved_attributes)
  put_attrs = @unsaved_attributes
  put_attrs.delete('objectId')
  put_attrs.delete('createdAt')
  put_attrs.delete('updatedAt')
  put_attrs
end

#clean?Boolean

Returns:

  • (Boolean)


487
488
489
# File 'lib/parse_resource/base.rb', line 487

def clean?
  !dirty?
end

#createObject



404
405
406
407
408
409
410
# File 'lib/parse_resource/base.rb', line 404

def create
  attrs = attributes_for_saving.to_json
  opts = {:content_type => "application/json"}
  result = self.resource.post(attrs, opts) do |resp, req, res, &block|
    return post_result(resp, req, res, &block)
  end
end

#create_getters!(k, v) ⇒ Object

Creates getter methods for model fields



142
143
144
145
146
147
148
# File 'lib/parse_resource/base.rb', line 142

def create_getters!(k,v)
  unless self.respond_to? "#{k}"
    self.class.send(:define_method, "#{k}") do
      get_attribute("#{k}")
    end
  end
end

#create_setters!(k, v) ⇒ Object

Creates setter methods for model fields



105
106
107
108
109
110
111
112
113
# File 'lib/parse_resource/base.rb', line 105

def create_setters!(k,v)
  unless self.respond_to? "#{k}="
    self.class.send(:define_method, "#{k}=") do |val|
      set_attribute("#{k}", val)
      
      val
    end
  end
end

#create_setters_and_getters!Object



150
151
152
153
154
155
# File 'lib/parse_resource/base.rb', line 150

def create_setters_and_getters!
  @attributes.each_pair do |k,v|
    create_setters!(k,v)
    create_getters!(k,v)
  end
end

#created_atObject



543
# File 'lib/parse_resource/base.rb', line 543

def created_at; get_attribute("createdAt"); end

#destroyObject



464
465
466
467
468
469
470
471
# File 'lib/parse_resource/base.rb', line 464

def destroy
  if self.instance_resource.delete
    @attributes = {}
    @unsaved_attributes = {}
    return true
  end
  false
end

#dirty?Boolean

Returns:

  • (Boolean)


483
484
485
# File 'lib/parse_resource/base.rb', line 483

def dirty?
  @unsaved_attributes.length > 0
end

#get_attribute(k) ⇒ Object



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/parse_resource/base.rb', line 503

def get_attribute(k)
  attrs = @unsaved_attributes[k.to_s] ? @unsaved_attributes : @attributes
  case attrs[k]
  when Hash
    klass_name = attrs[k]["className"]
    klass_name = "User" if klass_name == "_User"
    case attrs[k]["__type"]
    when "Pointer"
      result = klass_name.constantize.find(attrs[k]["objectId"])
    when "Object"
      result = klass_name.constantize.new(attrs[k], false)
    when "Date"
      result = DateTime.parse(attrs[k]["iso"]).to_time_in_current_zone
    when "File"
      result = attrs[k]["url"]
    when "GeoPoint"
      result = ParseGeoPoint.new(attrs[k])
    end #todo: support other types https://www.parse.com/docs/rest#objects-types
  else
    result =  attrs["#{k}"]
  end          
  result
end

#idObject

aliasing for idiomatic Ruby



540
# File 'lib/parse_resource/base.rb', line 540

def id; get_attribute("objectId") rescue nil; end

#instance_resourceObject

create RESTful resource for the specific Parse object sends requests to [base_uri]//[objectId]



371
372
373
# File 'lib/parse_resource/base.rb', line 371

def instance_resource
  self.class.resource["#{self.id}"]
end

#merge_attributes(results) ⇒ Object

Merges in the return value of a save and resets the unsaved_attributes



426
427
428
429
430
431
432
# File 'lib/parse_resource/base.rb', line 426

def merge_attributes(results)
  @attributes.merge!(results)
  @attributes.merge!(@unsaved_attributes)
  @unsaved_attributes = {}
  create_setters_and_getters!
  @attributes
end

#model_base_uriObject

Gets the current instance’s parent class’s Parse.com base_uri



188
189
190
# File 'lib/parse_resource/base.rb', line 188

def model_base_uri
  self.class.send(:model_base_uri)
end

#new?Boolean

Returns:

  • (Boolean)


360
361
362
# File 'lib/parse_resource/base.rb', line 360

def new?
  !persisted?
end

#objectIdObject



541
# File 'lib/parse_resource/base.rb', line 541

def objectId; get_attribute("objectId") rescue nil; end

#persisted?Boolean

Returns:

  • (Boolean)


352
353
354
355
356
357
358
# File 'lib/parse_resource/base.rb', line 352

def persisted?
  if id
    true
  else
    false
  end
end

#pointerize(hash) ⇒ Object



375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/parse_resource/base.rb', line 375

def pointerize(hash)
  new_hash = {}
  hash.each do |k, v|
    if v.respond_to?(:to_pointer)
      new_hash[k] = v.to_pointer
    elsif v.is_a?(Date) || v.is_a?(Time) || v.is_a?(DateTime)
      new_hash[k] = self.class.to_date_object(v)
    else
      new_hash[k] = v
    end
  end
  new_hash
end

#post_result(resp, req, res, &block) ⇒ Object



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/parse_resource/base.rb', line 434

def post_result(resp, req, res, &block)
  if resp.code.to_s == "200" || resp.code.to_s == "201"
    merge_attributes(JSON.parse(resp))
    return true
  else
    error_response = JSON.parse(resp)
    if error_response["error"]
      pe = ParseError.new(error_response["code"], error_response["error"])
    else
      pe = ParseError.new(resp.code.to_s)
    end
    self.errors.add(pe.code.to_s.to_sym, pe.msg)
    self.error_instances << pe     
    return false
  end      
end

#reloadObject



473
474
475
476
477
478
479
480
481
# File 'lib/parse_resource/base.rb', line 473

def reload
  return false if new?
  
  fresh_object = self.class.find(id)
  @attributes.update(fresh_object.instance_variable_get('@attributes'))
  @unsaved_attributes = {}
  
  self
end

#resourceObject

delegate from Class method



365
366
367
# File 'lib/parse_resource/base.rb', line 365

def resource
  self.class.resource
end

#saveObject



389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/parse_resource/base.rb', line 389

def save
  if valid?
    run_callbacks :save do
      if new?
        return create 
      else
        return update
      end
    end
  else
    false
  end
  rescue false
end

#set_attribute(k, v) ⇒ Object



527
528
529
530
531
532
533
534
535
536
# File 'lib/parse_resource/base.rb', line 527

def set_attribute(k, v)
  if v.is_a?(Date) || v.is_a?(Time) || v.is_a?(DateTime)
    v = self.class.to_date_object(v)
  elsif v.respond_to?(:to_pointer)
    v = v.to_pointer 
  end
  @unsaved_attributes[k.to_s] = v unless v == @attributes[k.to_s] # || @unsaved_attributes[k.to_s]
  @attributes[k.to_s] = v
  v
end

#to_pointerObject



92
93
94
95
96
97
# File 'lib/parse_resource/base.rb', line 92

def to_pointer
  klass_name = self.class.model_name
  klass_name = "_User" if klass_name == "User"
  klass_name = "_Installation" if klass_name == "Installation"
  {"__type" => "Pointer", "className" => klass_name.to_s, "objectId" => self.id}
end

#update(attributes = {}) ⇒ Object



412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/parse_resource/base.rb', line 412

def update(attributes = {})
  
  attributes = HashWithIndifferentAccess.new(attributes)
    
  @unsaved_attributes.merge!(attributes)
  put_attrs = attributes_for_saving.to_json
  
  opts = {:content_type => "application/json"}
  result = self.instance_resource.put(put_attrs, opts) do |resp, req, res, &block|
    return post_result(resp, req, res, &block)
  end
end

#update_attributes(attributes = {}) ⇒ Object



460
461
462
# File 'lib/parse_resource/base.rb', line 460

def update_attributes(attributes = {})
  self.update(attributes)
end

#updated_atObject



545
# File 'lib/parse_resource/base.rb', line 545

def updated_at; get_attribute("updatedAt"); rescue nil; end