require "rubygems" require "bundler/setup" require "active_model" require "erb" require "rest-client" require "json" require "active_support" class ParseResource # ParseResource provides an easy way to use Ruby to interace with a Parse.com backend # Usage: # class Post < ParseResource # fields :title, :author, :body # end include ActiveModel::Validations include ActiveModel::Conversion include ActiveModel::AttributeMethods extend ActiveModel::Naming # instantiation! # p = Post.new(:title => "cool story") def initialize(attributes = {}) if new? @unsaved_attributes = attributes else @unsaved_attributes = {} end self.attributes = {} self.attributes.merge!(attributes) self.attributes unless self.attributes.empty? #@field_message = "set in #initialize" create_setters! end def self.field(name, val=nil) class_eval do define_method(name) do @attributes[name] ? @attributes[name] : @unsaved_attributes[name] end define_method("#{name}=") do |val| @attributes[name] = val @unsaved_attributes[name] = val val end end end def self.fields(*args) args.each {|f| field(f)} end def self.add_field(fieldname, val=nil) class_attributes.merge!({fieldname.to_sym => nil}) end # a sprinkle of metaprogramming # p = Post.new(:some_attr => "some value") # p.some_attr = "new value" def create_setters! @attributes.each_pair do |k,v| self.class.send(:define_method, "#{k}=") do |val| @attributes[k.to_sym] = val @unsaved_attributes[k.to_sym] = val val end end end class << self def load!(app_id, master_key) @@settings = {"app_id" => app_id, "master_key" => master_key} end # creates a RESTful resource # sends requests to [base_uri]/[classname] def resource if @@settings.nil? path = "config/parse_resource.yml" environment = defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : ENV["RACK_ENV"] @@settings = YAML.load(ERB.new(File.new(path).read).result)[environment] end base_uri = "https://api.parse.com/1/classes/#{model_name}" app_id = @@settings['app_id'] master_key = @@settings['master_key'] RestClient::Resource.new(base_uri, app_id, master_key) end # finders # Post.find("abcdf") def find(id) where(:objectId => id).first end # Post.where(:author => "Alan", :title => "Ipso Lorem") def where(parameters) resp = resource.get(:params => {:where => parameters.to_json}) results = JSON.parse(resp)['results'] results.map {|r| model_name.constantize.new(r)} end # Post.all def all resp = resource.get results = JSON.parse(resp)['results'] results.map {|r| model_name.constantize.new(r)} end # Post.create(:title => "new post") def create(attributes = {}) new(attributes).save end # Post.first def first all.first end def class_attributes @class_attributes ||= {} end end def persisted? !self.id.nil? end def new? !persisted? end # delegate from Class method def resource self.class.resource end # create RESTful resource for the specific Parse object # sends requests to [base_uri]/[classname]/[objectId] def instance_resource self.class.resource["#{self.id}"] end def create resp = self.resource.post(@unsaved_attributes.to_json, :content_type => "application/json") @attributes.merge!(JSON.parse(resp)) @attributes.merge!(@unsaved_attributes) @unsaved_attributes = {} create_setters! self end def save if valid? new? ? create : update else false end rescue false end def update(attributes = {}) @unsaved_attributes.merge!(attributes) put_attrs = @unsaved_attributes put_attrs.delete('objectId') put_attrs.delete('createdAt') put_attrs.delete('updatedAt') put_attrs = put_attrs.to_json resp = self.instance_resource.put(put_attrs, :content_type => "application/json") @attributes.merge!(JSON.parse(resp)) @attributes.merge!(@unsaved_attributes) @unsaved_attributes = {} create_setters! self end def update_attributes(attributes = {}) self.update(attributes) end def destroy self.instance_resource.delete @attributes = {} @unsaved_attributes = {} nil end # provides access to @attributes for getting and setting def attributes @attributes ||= self.class.class_attributes @attributes end def attributes=(n) @attributes = n @attributes end # aliasing for idiomatic Ruby def id; self.objectId rescue nil; end def created_at; self.createdAt; end def updated_at; self.updatedAt rescue nil; end # another sprinkle of metaprogramming # p = Post.new(:some_attr => "some value") # p.some_attr #=> "some value" def method_missing(meth, *args, &block) if self.attributes.has_key?(meth.to_sym) self.attributes[meth.to_sym] else super end end end