parse-stack 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +77 -0
- data/LICENSE +20 -0
- data/README.md +1281 -0
- data/Rakefile +12 -0
- data/bin/console +20 -0
- data/bin/server +10 -0
- data/bin/setup +7 -0
- data/lib/parse/api/all.rb +13 -0
- data/lib/parse/api/analytics.rb +16 -0
- data/lib/parse/api/apps.rb +37 -0
- data/lib/parse/api/batch.rb +148 -0
- data/lib/parse/api/cloud_functions.rb +18 -0
- data/lib/parse/api/config.rb +22 -0
- data/lib/parse/api/files.rb +21 -0
- data/lib/parse/api/hooks.rb +68 -0
- data/lib/parse/api/objects.rb +77 -0
- data/lib/parse/api/push.rb +16 -0
- data/lib/parse/api/schemas.rb +25 -0
- data/lib/parse/api/sessions.rb +11 -0
- data/lib/parse/api/users.rb +43 -0
- data/lib/parse/client.rb +225 -0
- data/lib/parse/client/authentication.rb +59 -0
- data/lib/parse/client/body_builder.rb +69 -0
- data/lib/parse/client/caching.rb +103 -0
- data/lib/parse/client/protocol.rb +15 -0
- data/lib/parse/client/request.rb +43 -0
- data/lib/parse/client/response.rb +116 -0
- data/lib/parse/model/acl.rb +182 -0
- data/lib/parse/model/associations/belongs_to.rb +121 -0
- data/lib/parse/model/associations/collection_proxy.rb +202 -0
- data/lib/parse/model/associations/has_many.rb +218 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +71 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +134 -0
- data/lib/parse/model/bytes.rb +50 -0
- data/lib/parse/model/core/actions.rb +499 -0
- data/lib/parse/model/core/properties.rb +377 -0
- data/lib/parse/model/core/querying.rb +100 -0
- data/lib/parse/model/core/schema.rb +92 -0
- data/lib/parse/model/date.rb +50 -0
- data/lib/parse/model/file.rb +127 -0
- data/lib/parse/model/geopoint.rb +98 -0
- data/lib/parse/model/model.rb +120 -0
- data/lib/parse/model/object.rb +347 -0
- data/lib/parse/model/pointer.rb +106 -0
- data/lib/parse/model/push.rb +99 -0
- data/lib/parse/query.rb +378 -0
- data/lib/parse/query/constraint.rb +130 -0
- data/lib/parse/query/constraints.rb +176 -0
- data/lib/parse/query/operation.rb +66 -0
- data/lib/parse/query/ordering.rb +49 -0
- data/lib/parse/stack.rb +11 -0
- data/lib/parse/stack/version.rb +5 -0
- data/lib/parse/webhooks.rb +228 -0
- data/lib/parse/webhooks/payload.rb +115 -0
- data/lib/parse/webhooks/registration.rb +139 -0
- data/parse-stack.gemspec +45 -0
- metadata +340 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative "properties"
|
2
|
+
# This class adds methods to Parse::Objects in order to create a JSON Parse schema
|
3
|
+
# in order to support table creation and table alterations.
|
4
|
+
module Parse
|
5
|
+
module Schema
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# returns the schema of the defined class in the Parse JSON format.
|
14
|
+
def schema
|
15
|
+
sch = { className: parse_class, fields: {} }
|
16
|
+
#first go through all the attributes
|
17
|
+
attributes.each do |k,v|
|
18
|
+
# don't include the base Parse fields
|
19
|
+
next if Parse::Properties::BASE.include?(k)
|
20
|
+
next if v.nil?
|
21
|
+
result = { type: v.to_s.camelize }
|
22
|
+
# if it is a basic column property, find the right datatype
|
23
|
+
case v
|
24
|
+
when :integer, :float
|
25
|
+
result[:type] = "Number".freeze
|
26
|
+
when :geopoint, :geo_point
|
27
|
+
result[:type] = "GeoPoint".freeze
|
28
|
+
when :pointer
|
29
|
+
result = { type: "Pointer".freeze, targetClass: references[k] }
|
30
|
+
when :acl
|
31
|
+
result[:type] = "ACL".freeze
|
32
|
+
else
|
33
|
+
result[:type] = v.to_s.camelize
|
34
|
+
end
|
35
|
+
|
36
|
+
sch[:fields][k] = result
|
37
|
+
|
38
|
+
end
|
39
|
+
#then add all the relational column attributes
|
40
|
+
relations.each do |k,v|
|
41
|
+
sch[:fields][k] = { type: "Relation".freeze, targetClass: relations[k] }
|
42
|
+
end
|
43
|
+
sch
|
44
|
+
end
|
45
|
+
|
46
|
+
# updates the remote schema using Parse::Client
|
47
|
+
def update_schema(schema_updates = nil)
|
48
|
+
schema_updates ||= schema
|
49
|
+
client.update_schema parse_class, schema_updates
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_schema
|
53
|
+
client.create_schema parse_class, schema
|
54
|
+
end
|
55
|
+
|
56
|
+
# fetches the current schema of this table.
|
57
|
+
def fetch_schema
|
58
|
+
client.schema parse_class
|
59
|
+
end
|
60
|
+
|
61
|
+
# A class method for non-destructive auto upgrading a remote schema based on the properties
|
62
|
+
# and relations you have defined. If the table doesn't exist, we create the schema
|
63
|
+
# from scratch - otherwise we fetched the current schema, calculate the differences
|
64
|
+
# and add the missing columns. WE DO NOT REMOVE any columns.
|
65
|
+
def auto_upgrade!
|
66
|
+
response = fetch_schema
|
67
|
+
if response.success?
|
68
|
+
#let's figure out the diff fields
|
69
|
+
remote_fields = response.result["fields"]
|
70
|
+
current_schema = schema
|
71
|
+
current_schema[:fields] = current_schema[:fields].reduce({}) do |h,(k,v)|
|
72
|
+
#if the field does not exist in Parse, then add it to the update list
|
73
|
+
h[k] = v if remote_fields[k.to_s].nil?
|
74
|
+
h
|
75
|
+
end
|
76
|
+
return if current_schema[:fields].empty?
|
77
|
+
return update_schema( current_schema )
|
78
|
+
else
|
79
|
+
return create_schema
|
80
|
+
end
|
81
|
+
#fetch_schema.success? ? update_schema : create_schema
|
82
|
+
end
|
83
|
+
#def diff(h2);self.dup.delete_if { |k, v| h2[k] == v }.merge(h2.dup.delete_if { |k, v| self.has_key?(k) }); end;
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
def schema
|
88
|
+
self.class.schema
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'active_model'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require 'active_support/core_ext/object'
|
6
|
+
require 'active_model_serializers'
|
7
|
+
require_relative 'model'
|
8
|
+
|
9
|
+
# Parse has a specific date format. One of the supported types is a date string in
|
10
|
+
# ISO 8601 format (including milliseconds). The other is a hash object that contains
|
11
|
+
# the similar information. When sending data to Parse, we need to use the hash object,
|
12
|
+
# but when receiving data from Parse, we may either get the string version or the hash version.
|
13
|
+
# To make things easier to use in ruby, th Parse::Date class inherits from the DateTime class.
|
14
|
+
# This will allow us to use all the great ActiveSupport methods for date (ex. 3.days.ago) while
|
15
|
+
# providing our own encoding for sending to Parse.
|
16
|
+
module Parse
|
17
|
+
class Date < ::DateTime
|
18
|
+
include ::ActiveModel::Model
|
19
|
+
include ::ActiveModel::Serializers::JSON
|
20
|
+
def self.parse_class; Parse::Model::TYPE_DATE; end;
|
21
|
+
def parse_class; self.class.parse_class; end;
|
22
|
+
alias_method :__type, :parse_class
|
23
|
+
|
24
|
+
# called when encoding to JSON.
|
25
|
+
def attributes
|
26
|
+
{ __type: :string, iso: :string }.freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
# this method is defined because it is used by JSON encoding
|
30
|
+
def iso
|
31
|
+
to_time.utc.iso8601(3) #include milliseconds
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# To enable conversion of other date class objects, we will add a mixin to turn
|
38
|
+
# Time and DateTime objects to Parse::Date objects
|
39
|
+
class Time
|
40
|
+
def parse_date
|
41
|
+
Parse::Date.parse self.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
class DateTime
|
47
|
+
def parse_date
|
48
|
+
Parse::Date.parse self.to_s
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/object'
|
3
|
+
require_relative "model"
|
4
|
+
require 'open-uri'
|
5
|
+
# Parse File objects are the only non Parse::Object subclass that has a save method.
|
6
|
+
# In general, a Parse File needs content and a specific mime-type to be created. When it is saved, it
|
7
|
+
# is sent to AWS/S3 to be saved (by Parse), and the result is a new URL pointing to that file.
|
8
|
+
# This however is return as a type of File pointer object (hash format)
|
9
|
+
# It only has two fields, the absolute URL of the file and the basename of the file.
|
10
|
+
# The contents and mime_type are only present when creating a new file locally and are not
|
11
|
+
# stored as part of the parse pointer.
|
12
|
+
module Parse
|
13
|
+
|
14
|
+
class File < Model
|
15
|
+
|
16
|
+
attr_accessor :name, :url
|
17
|
+
attr_accessor :contents, :mime_type
|
18
|
+
def self.parse_class; TYPE_FILE; end;
|
19
|
+
def parse_class; self.class.parse_class; end;
|
20
|
+
alias_method :__type, :parse_class
|
21
|
+
FIELD_NAME = "name".freeze
|
22
|
+
FIELD_URL = "url".freeze
|
23
|
+
|
24
|
+
# The initializer to create a new file supports different inputs.
|
25
|
+
# If the first paramter is a string which starts with 'http', we then download
|
26
|
+
# the content of the file (and use the detected mime-type) to set the content and mime_type fields.
|
27
|
+
# If the first parameter is a hash, we assume it might be the Parse File hash format which contains url and name fields only.
|
28
|
+
# If the first paramter is a Parse::File, then we copy fields over
|
29
|
+
# Otherwise, creating a new file requires a name, the actual contents (usually from a File.open("local.jpg").read ) and the mime-type
|
30
|
+
def initialize(name, contents = nil, mime_type = "application/octet-stream".freeze)
|
31
|
+
|
32
|
+
if name.is_a?(String) && name.start_with?("http".freeze) #could be url string
|
33
|
+
file = open( name )
|
34
|
+
@contents = file.read
|
35
|
+
@name = File.basename file.base_uri.to_s
|
36
|
+
@mime_type = file.content_type
|
37
|
+
elsif name.is_a?(Hash)
|
38
|
+
self.attributes = name
|
39
|
+
elsif name.is_a?(::File)
|
40
|
+
@contents = contents || name.read
|
41
|
+
@name = File.basename name.to_path
|
42
|
+
elsif name.is_a?(File)
|
43
|
+
@name = name.name
|
44
|
+
@url = name.url
|
45
|
+
else
|
46
|
+
@name = name
|
47
|
+
@contents = contents
|
48
|
+
end
|
49
|
+
if @name.blank?
|
50
|
+
raise "Invalid Parse::File initialization with name '#{@name}'"
|
51
|
+
end
|
52
|
+
|
53
|
+
@mime_type ||= mime_type
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
# This creates a new Parse File Object with from a URL, saves it and returns it
|
58
|
+
def self.create(url)
|
59
|
+
url = url.url if url.is_a?(Parse::File)
|
60
|
+
file = self.new(url)
|
61
|
+
file.save
|
62
|
+
file
|
63
|
+
end
|
64
|
+
|
65
|
+
# A File object is considered saved if the basename of the URL and the name parameters are equal and
|
66
|
+
# the name of the file begins with 'tfss'
|
67
|
+
def saved?
|
68
|
+
@url.present? && @name.present? && @name == File.basename(@url) && @name.start_with?("tfss".freeze)
|
69
|
+
end
|
70
|
+
|
71
|
+
def attributes
|
72
|
+
{ __type: :string, name: :string, url: :string }.freeze
|
73
|
+
end
|
74
|
+
|
75
|
+
def ==(u)
|
76
|
+
return false unless u.is_a?(self.class)
|
77
|
+
@url == u.url
|
78
|
+
end
|
79
|
+
|
80
|
+
def attributes=(h)
|
81
|
+
if h.is_a?(String)
|
82
|
+
@url = h
|
83
|
+
@name = File.basename(h)
|
84
|
+
elsif h.is_a?(Hash)
|
85
|
+
@url = h[FIELD_URL] || h[:url] || @url
|
86
|
+
@name = h[FIELD_NAME] || h[:name] || @name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# This is a proxy to the ruby ::File.basename method
|
91
|
+
def self.basename(file_name, suffix = nil)
|
92
|
+
if suffix.nil?
|
93
|
+
::File.basename(file_name)
|
94
|
+
else
|
95
|
+
::File.basename(file_name, suffix)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# save (create) the file if it has all the proper fields. You cannot update Parse Files.
|
100
|
+
def save
|
101
|
+
unless saved? || @contents.nil? || @name.nil?
|
102
|
+
response = client.create_file(@name, @contents, @mime_type)
|
103
|
+
unless response.error?
|
104
|
+
result = response.result
|
105
|
+
@name = result[FIELD_NAME] || File.basename(result[FIELD_URL])
|
106
|
+
@url = result[FIELD_URL]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
saved?
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
class Hash
|
118
|
+
# {"name"=>"tfss-cat.jpg", "url"=>"http://files.parsetfss.com/bcf638bb-3db0-4042-b846-7840b345b0d6/tfss-cat.jpg"}
|
119
|
+
# This is a helper method that determines whether a hash looks like a Parse::File hash
|
120
|
+
def parse_file?
|
121
|
+
url = self[Parse::File::FIELD_URL]
|
122
|
+
name = self[Parse::File::FIELD_NAME]
|
123
|
+
(count == 2 || self["__type".freeze] == Parse::File.parse_class) &&
|
124
|
+
url.present? && name.present? &&
|
125
|
+
name == ::File.basename(url) && name.start_with?("tfss".freeze)
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
require_relative "model"
|
3
|
+
|
4
|
+
module Parse
|
5
|
+
|
6
|
+
# A basic geo location object in Parse. It represents a location on a map through a
|
7
|
+
# latitude and longitue.
|
8
|
+
class GeoPoint < Model
|
9
|
+
|
10
|
+
attr_accessor :latitude, :longitude
|
11
|
+
FIELD_LAT = "latitude".freeze
|
12
|
+
FIELD_LNG = "longitude".freeze
|
13
|
+
alias_method :lat, :latitude
|
14
|
+
alias_method :lng, :longitude
|
15
|
+
def self.parse_class; TYPE_GEOPOINT; end;
|
16
|
+
def parse_class; self.class.parse_class; end;
|
17
|
+
alias_method :__type, :parse_class
|
18
|
+
# TODO: Validate the ranges of the geo point for valid lat and lng numbers.
|
19
|
+
# To create a GeoPoint, you can either pass a hash (ex. {latitude: 32, longitue: -117})
|
20
|
+
# or an array (ex. [32,-117]) as the first parameter.
|
21
|
+
# You may also pass a GeoPoint object or both a lat/lng pair (Ex. GeoPoint.new(32, -117) )
|
22
|
+
def initialize(latitude = nil, longitude = nil)
|
23
|
+
@latitude = @longitude = 0.0
|
24
|
+
if latitude.is_a?(Hash) || latitude.is_a?(Array)
|
25
|
+
self.attributes = latitude
|
26
|
+
elsif latitude.is_a?(Numeric) && longitude.is_a?(Numeric)
|
27
|
+
@latitude = latitude
|
28
|
+
@longitude = longitude
|
29
|
+
elsif latitude.is_a?(GeoPoint)
|
30
|
+
@latitude = latitude.latitude
|
31
|
+
@longitude = latitude.longitude
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def attributes
|
36
|
+
{ __type: :string, latitude: :float, longitude: :float }.freeze
|
37
|
+
end
|
38
|
+
|
39
|
+
def max_miles(m)
|
40
|
+
[@latitude,@longitude,m]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Setting lat and lng for an GeoPoint can be done using a hash with the attributes set
|
44
|
+
# or with an array of two items where the first is the lat and the second is the lng (ex. [32.22,-118.81])
|
45
|
+
def attributes=(h)
|
46
|
+
if h.is_a?(Hash)
|
47
|
+
h.symbolize_keys!
|
48
|
+
@latitude = h[:latitude].to_f || h[:lat].to_f || @latitude
|
49
|
+
@longitude = h[:longitude].to_f || h[:lng].to_f || @longitude
|
50
|
+
elsif h.is_a?(Array) && h.count == 2
|
51
|
+
@latitude = h.first.to_f
|
52
|
+
@longitude = h.last.to_f
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def ==(g)
|
57
|
+
return false unless g.is_a?(GeoPoint)
|
58
|
+
@latitude == g.latitude && @longitude == g.longitude
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_a
|
62
|
+
[@latitude,@longitude]
|
63
|
+
end
|
64
|
+
|
65
|
+
def inspect
|
66
|
+
"#<GeoPoint [#{@latitude},#{@longitude}]>"
|
67
|
+
end
|
68
|
+
|
69
|
+
# either GeoPoint, array or lat,lng
|
70
|
+
def distance_in_miles(geopoint,lng = nil)
|
71
|
+
distance_in_km(geopoint, lng) * 0.621371
|
72
|
+
end
|
73
|
+
|
74
|
+
def distance_in_km(geopoint,lng = nil)
|
75
|
+
unless geopoint.is_a?(Parse::GeoPoint)
|
76
|
+
geopoint = Parse::GeoPoint.new(geopoint, lng)
|
77
|
+
end
|
78
|
+
|
79
|
+
dtor = Math::PI/180
|
80
|
+
r = 6378.14
|
81
|
+
r_lat1 = self.latitude * dtor
|
82
|
+
r_lng1 = self.longitude * dtor
|
83
|
+
r_lat2 = geopoint.latitude * dtor
|
84
|
+
r_lng2 = geopoint.longitude * dtor
|
85
|
+
|
86
|
+
delta_lat = r_lat1 - r_lat2
|
87
|
+
delta_lng = r_lng1 - r_lng2
|
88
|
+
|
89
|
+
a = (Math::sin(delta_lat/2.0) ** 2).to_f + (Math::cos(r_lat1) * Math::cos(r_lat2) * ( Math::sin(delta_lng/2.0) ** 2 ) )
|
90
|
+
c = 2.0 * Math::atan2(Math::sqrt(a), Math::sqrt(1.0-a))
|
91
|
+
d = r * c
|
92
|
+
d
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/inflector'
|
4
|
+
require 'active_support/core_ext/object'
|
5
|
+
require 'active_model_serializers'
|
6
|
+
require_relative '../client'
|
7
|
+
|
8
|
+
# This is the base model for all Parse object-type classes.
|
9
|
+
|
10
|
+
module Parse
|
11
|
+
|
12
|
+
class Model
|
13
|
+
|
14
|
+
include Client::Connectable # allows easy default Parse::Client access
|
15
|
+
include ::ActiveModel::Model
|
16
|
+
include ::ActiveModel::Serializers::JSON # support for JSON Serializers
|
17
|
+
include ::ActiveModel::Dirty # adds dirty tracking support
|
18
|
+
include ::ActiveModel::Conversion
|
19
|
+
extend ::ActiveModel::Callbacks # callback support on save, update, delete, etc.
|
20
|
+
extend ::ActiveModel::Naming # provides the methods for getting class names from Model classes
|
21
|
+
|
22
|
+
# General Parse constants
|
23
|
+
KEY_CLASS_NAME = 'className'.freeze
|
24
|
+
KEY_OBJECT_ID = 'objectId'.freeze
|
25
|
+
KEY_CREATED_AT = 'createdAt'.freeze
|
26
|
+
KEY_UPDATED_AT = 'updatedAt'.freeze
|
27
|
+
CLASS_USER = '_User'.freeze
|
28
|
+
CLASS_INSTALLATION = '_Installation'.freeze
|
29
|
+
TYPE_FILE = "File".freeze
|
30
|
+
TYPE_GEOPOINT = "GeoPoint".freeze
|
31
|
+
TYPE_OBJECT = "Object".freeze
|
32
|
+
TYPE_DATE = "Date".freeze
|
33
|
+
TYPE_BYTES = "Bytes".freeze
|
34
|
+
TYPE_POINTER = "Pointer".freeze
|
35
|
+
TYPE_RELATION = "Relation".freeze
|
36
|
+
TYPE_FIELD = "__type".freeze
|
37
|
+
|
38
|
+
# To support being able to have different ruby class names from the 'table'
|
39
|
+
# names used in Parse, we will need to have a dynamic lookup system where
|
40
|
+
# when a parse class name received, we go through all of our subclasses to determine
|
41
|
+
# which Parse::Object subclass is responsible for handling this Parse table class.
|
42
|
+
# we use @@model_cache to cache the results of the algorithm since we do this frequently
|
43
|
+
# when encoding and decoding objects.
|
44
|
+
@@model_cache = {}
|
45
|
+
def self.autosave_on_create
|
46
|
+
@@autosave_on_create ||= false
|
47
|
+
end
|
48
|
+
def self.autosave_on_create=(bool)
|
49
|
+
@@autosave_on_create = bool
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
|
54
|
+
def raise_on_save_failure
|
55
|
+
@global_raise_on_save_failure ||= false
|
56
|
+
end
|
57
|
+
def raise_on_save_failure=(bool)
|
58
|
+
@global_raise_on_save_failure = bool
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
# class method to find the responsible ruby Parse::Object subclass that handles
|
64
|
+
# the provided parse class (str).
|
65
|
+
def self.find_class(str)
|
66
|
+
return Parse::File if str == TYPE_FILE.freeze
|
67
|
+
return Parse::GeoPoint if str == TYPE_GEOPOINT.freeze
|
68
|
+
return Parse::Date if str == TYPE_DATE.freeze
|
69
|
+
# return Parse::User if str == "User".freeze
|
70
|
+
# return Parse::Installation if str == "Installation".freeze
|
71
|
+
|
72
|
+
str = str.to_s
|
73
|
+
# Basically go through all Parse::Object subclasses and see who is has a parse_class
|
74
|
+
# set to this string. We will cache the results for future use.
|
75
|
+
@@model_cache[str] ||= Parse::Object.descendants.find do |f|
|
76
|
+
f.parse_class == str || f.parse_class == "_#{str}"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
class String
|
87
|
+
# short helper method to provide lower-first-camelcase
|
88
|
+
def columnize
|
89
|
+
return "objectId" if self == "id"
|
90
|
+
camelize(:lower)
|
91
|
+
end;
|
92
|
+
|
93
|
+
#users for properties: ex. :users -> "_User" or :songs -> Song
|
94
|
+
def to_parse_class
|
95
|
+
final_class = self.singularize.camelize
|
96
|
+
klass = Parse::Model.find_class(final_class) || Parse::Model.find_class(self)
|
97
|
+
#handles the case that a class has a custom parse table
|
98
|
+
final_class = klass.parse_class if klass.present?
|
99
|
+
final_class
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Symbol
|
104
|
+
# for compatibility
|
105
|
+
def columnize
|
106
|
+
to_s.columnize.to_sym
|
107
|
+
end
|
108
|
+
|
109
|
+
def singularize
|
110
|
+
to_s.singularize
|
111
|
+
end
|
112
|
+
|
113
|
+
def camelize
|
114
|
+
to_s.camelize
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_parse_class
|
118
|
+
to_s.to_parse_class
|
119
|
+
end
|
120
|
+
end
|