parse-stack 1.5.3 → 1.6.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/parse-ruby-sdk.png +0 -0
  3. data/Changes.md +25 -1
  4. data/Gemfile.lock +4 -4
  5. data/README.md +37 -31
  6. data/bin/console +3 -0
  7. data/lib/parse/api/all.rb +2 -1
  8. data/lib/parse/api/apps.rb +12 -0
  9. data/lib/parse/api/config.rb +5 -1
  10. data/lib/parse/api/files.rb +1 -0
  11. data/lib/parse/api/hooks.rb +1 -0
  12. data/lib/parse/api/objects.rb +4 -1
  13. data/lib/parse/api/push.rb +1 -0
  14. data/lib/parse/api/{schemas.rb → schema.rb} +7 -0
  15. data/lib/parse/api/server.rb +44 -0
  16. data/lib/parse/api/sessions.rb +1 -0
  17. data/lib/parse/api/users.rb +4 -1
  18. data/lib/parse/client.rb +109 -73
  19. data/lib/parse/client/authentication.rb +2 -1
  20. data/lib/parse/client/batch.rb +9 -1
  21. data/lib/parse/client/body_builder.rb +16 -1
  22. data/lib/parse/client/caching.rb +15 -13
  23. data/lib/parse/client/protocol.rb +27 -15
  24. data/lib/parse/client/response.rb +26 -8
  25. data/lib/parse/model/acl.rb +1 -1
  26. data/lib/parse/model/associations/belongs_to.rb +18 -19
  27. data/lib/parse/model/associations/collection_proxy.rb +6 -0
  28. data/lib/parse/model/associations/has_many.rb +5 -6
  29. data/lib/parse/model/bytes.rb +4 -1
  30. data/lib/parse/model/classes/user.rb +46 -44
  31. data/lib/parse/model/core/actions.rb +508 -460
  32. data/lib/parse/model/core/builder.rb +75 -0
  33. data/lib/parse/model/core/errors.rb +9 -0
  34. data/lib/parse/model/core/fetching.rb +42 -38
  35. data/lib/parse/model/core/properties.rb +46 -27
  36. data/lib/parse/model/core/querying.rb +231 -228
  37. data/lib/parse/model/core/schema.rb +76 -74
  38. data/lib/parse/model/date.rb +10 -2
  39. data/lib/parse/model/file.rb +16 -2
  40. data/lib/parse/model/geopoint.rb +9 -2
  41. data/lib/parse/model/model.rb +38 -7
  42. data/lib/parse/model/object.rb +60 -19
  43. data/lib/parse/model/pointer.rb +22 -1
  44. data/lib/parse/model/push.rb +6 -2
  45. data/lib/parse/query.rb +57 -11
  46. data/lib/parse/query/constraint.rb +5 -2
  47. data/lib/parse/query/constraints.rb +588 -589
  48. data/lib/parse/query/ordering.rb +2 -2
  49. data/lib/parse/stack.rb +1 -0
  50. data/lib/parse/stack/version.rb +1 -1
  51. data/lib/parse/webhooks.rb +30 -29
  52. data/lib/parse/webhooks/payload.rb +181 -168
  53. data/lib/parse/webhooks/registration.rb +1 -1
  54. data/parse-stack.gemspec +9 -9
  55. metadata +9 -12
@@ -4,90 +4,92 @@
4
4
  require_relative "properties"
5
5
 
6
6
  module Parse
7
- # Defines the Schema methods applied to a Parse::Object.
8
- module Schema
7
+ module Core
8
+ # Defines the Schema methods applied to a Parse::Object.
9
+ module Schema
9
10
 
10
- # Generate a Parse-server compatible schema hash for performing changes to the
11
- # structure of the remote collection.
12
- # @return [Hash] the schema for this Parse::Object subclass.
13
- def schema
14
- sch = { className: parse_class, fields: {} }
15
- #first go through all the attributes
16
- attributes.each do |k,v|
17
- # don't include the base Parse fields
18
- next if Parse::Properties::BASE.include?(k)
19
- next if v.nil?
20
- result = { type: v.to_s.camelize }
21
- # if it is a basic column property, find the right datatype
22
- case v
23
- when :integer, :float
24
- result[:type] = "Number"
25
- when :geopoint, :geo_point
26
- result[:type] = "GeoPoint"
27
- when :pointer
28
- result = { type: "Pointer", targetClass: references[k] }
29
- when :acl
30
- result[:type] = "ACL"
31
- else
32
- result[:type] = v.to_s.camelize
33
- end
11
+ # Generate a Parse-server compatible schema hash for performing changes to the
12
+ # structure of the remote collection.
13
+ # @return [Hash] the schema for this Parse::Object subclass.
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] = Parse::Model::TYPE_NUMBER
26
+ when :geopoint, :geo_point
27
+ result[:type] = Parse::Model::TYPE_GEOPOINT
28
+ when :pointer
29
+ result = { type: Parse::Model::TYPE_POINTER, targetClass: references[k] }
30
+ when :acl
31
+ result[:type] = Parse::Model::ACL
32
+ else
33
+ result[:type] = v.to_s.camelize
34
+ end
34
35
 
35
- sch[:fields][k] = result
36
+ sch[:fields][k] = result
36
37
 
38
+ end
39
+ #then add all the relational column attributes
40
+ relations.each do |k,v|
41
+ sch[:fields][k] = { type: Parse::Model::TYPE_RELATION, targetClass: relations[k] }
42
+ end
43
+ sch
37
44
  end
38
- #then add all the relational column attributes
39
- relations.each do |k,v|
40
- sch[:fields][k] = { type: "Relation", targetClass: relations[k] }
41
- end
42
- sch
43
- end
44
45
 
45
- # Update the remote schema for this Parse collection.
46
- # @param schema_updates [Hash] the changes to be made to the schema.
47
- # @return [Parse::Response]
48
- def update_schema(schema_updates = nil)
49
- schema_updates ||= schema
50
- client.update_schema parse_class, schema_updates
51
- end
46
+ # Update the remote schema for this Parse collection.
47
+ # @param schema_updates [Hash] the changes to be made to the schema.
48
+ # @return [Parse::Response]
49
+ def update_schema(schema_updates = nil)
50
+ schema_updates ||= schema
51
+ client.update_schema parse_class, schema_updates
52
+ end
52
53
 
53
- # Create a new collection for this model with the schema defined by the local
54
- # model.
55
- # @return [Parse::Response]
56
- # @see Schema.schema
57
- def create_schema
58
- client.create_schema parse_class, schema
59
- end
54
+ # Create a new collection for this model with the schema defined by the local
55
+ # model.
56
+ # @return [Parse::Response]
57
+ # @see Schema.schema
58
+ def create_schema
59
+ client.create_schema parse_class, schema
60
+ end
60
61
 
61
- # Fetche the current schema for this collection from Parse server.
62
- # @return [Parse::Response]
63
- def fetch_schema
64
- client.schema parse_class
65
- end
62
+ # Fetche the current schema for this collection from Parse server.
63
+ # @return [Parse::Response]
64
+ def fetch_schema
65
+ client.schema parse_class
66
+ end
66
67
 
67
- # A class method for non-destructive auto upgrading a remote schema based
68
- # on the properties and relations you have defined in your local model. If
69
- # the collection doesn't exist, we create the schema. If the collection already
70
- # exists, the current schema is fetched, and only add the additional fields
71
- # that are missing.
72
- # @note No columns or fields are removed, this is a safe non-destructive upgrade.
73
- # @return [Parse::Response] if the remote schema was modified.
74
- # @return [Boolean] if no changes were made to the schema, it returns true.
75
- def auto_upgrade!
76
- response = fetch_schema
77
- if response.success?
78
- #let's figure out the diff fields
79
- remote_fields = response.result["fields"]
80
- current_schema = schema
81
- current_schema[:fields] = current_schema[:fields].reduce({}) do |h,(k,v)|
82
- #if the field does not exist in Parse, then add it to the update list
83
- h[k] = v if remote_fields[k.to_s].nil?
84
- h
68
+ # A class method for non-destructive auto upgrading a remote schema based
69
+ # on the properties and relations you have defined in your local model. If
70
+ # the collection doesn't exist, we create the schema. If the collection already
71
+ # exists, the current schema is fetched, and only add the additional fields
72
+ # that are missing.
73
+ # @note No columns or fields are removed, this is a safe non-destructive upgrade.
74
+ # @return [Parse::Response] if the remote schema was modified.
75
+ # @return [Boolean] if no changes were made to the schema, it returns true.
76
+ def auto_upgrade!
77
+ response = fetch_schema
78
+ if response.success?
79
+ #let's figure out the diff fields
80
+ remote_fields = response.result["fields"]
81
+ current_schema = schema
82
+ current_schema[:fields] = current_schema[:fields].reduce({}) do |h,(k,v)|
83
+ #if the field does not exist in Parse, then add it to the update list
84
+ h[k] = v if remote_fields[k.to_s].nil?
85
+ h
86
+ end
87
+ return true if current_schema[:fields].empty?
88
+ return update_schema( current_schema )
85
89
  end
86
- return true if current_schema[:fields].empty?
87
- return update_schema( current_schema )
90
+ create_schema
88
91
  end
89
- create_schema
90
- end
91
92
 
93
+ end
92
94
  end
93
95
  end
@@ -23,6 +23,7 @@ module Parse
23
23
  # the Parse ISO hash format for these fields instead, set
24
24
  # `Parse::Object.disable_serialized_string_date = true`.
25
25
  class Date < ::DateTime
26
+ # The default attributes in a Parse Date hash.
26
27
  ATTRIBUTES = { __type: :string, iso: :string }.freeze
27
28
  include ::ActiveModel::Model
28
29
  include ::ActiveModel::Serializers::JSON
@@ -43,10 +44,15 @@ module Parse
43
44
  to_time.utc.iso8601(3)
44
45
  end
45
46
 
47
+ # @return (see #iso)
48
+ def to_s(*args)
49
+ args.empty? ? iso : super(*args)
50
+ end
51
+
46
52
  end
47
53
  end
48
54
 
49
-
55
+ # Adds extensions to Time class to be compatible with {Parse::Date}.
50
56
  class Time
51
57
  # @return [Parse::Date] Converts object to Parse::Date
52
58
  def parse_date
@@ -54,7 +60,7 @@ class Time
54
60
  end
55
61
 
56
62
  end
57
-
63
+ # Adds extensions to DateTime class to be compatible with {Parse::Date}.
58
64
  class DateTime
59
65
  # @return [Parse::Date] Converts object to Parse::Date
60
66
  def parse_date
@@ -62,7 +68,9 @@ class DateTime
62
68
  end
63
69
  end
64
70
 
71
+ # Adds extensions to ActiveSupport class to be compatible with {Parse::Date}.
65
72
  module ActiveSupport
73
+ # Adds extensions to ActiveSupport::TimeWithZone class to be compatible with {Parse::Date}.
66
74
  class TimeWithZone
67
75
  # @return [Parse::Date] Converts object to Parse::Date
68
76
  def parse_date
@@ -26,13 +26,16 @@ module Parse
26
26
  # @note The default MIME type for all files is _image/jpeg_. This can be default
27
27
  # can be changed by setting a value to `Parse::File.default_mime_type`.
28
28
  class File < Model
29
+ # Regular expression that matches the old legacy Parse hosted file name
30
+ LEGACY_FILE_RX = /^tfss-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-/
31
+ # The default attributes in a Parse File hash.
29
32
  ATTRIBUTES = { __type: :string, name: :string, url: :string }.freeze
30
33
  # @return [String] the name of the file including extension (if any)
31
34
  attr_accessor :name
32
35
  # @return [String] the url resource of the file.
33
36
  attr_accessor :url
34
37
 
35
- # You may set the contents of the file.
38
+ # @return [Object] the contents of the file.
36
39
  attr_accessor :contents
37
40
 
38
41
  # @return [String] the mime-type of the file whe
@@ -42,12 +45,16 @@ module Parse
42
45
  # @return [Model::TYPE_FILE]
43
46
  def parse_class; self.class.parse_class; end;
44
47
  alias_method :__type, :parse_class
48
+ # @!visibility private
45
49
  FIELD_NAME = "name"
50
+ # @!visibility private
46
51
  FIELD_URL = "url"
47
52
  class << self
48
53
 
54
+ # @return [String] the default mime-type
49
55
  attr_accessor :default_mime_type
50
56
 
57
+ # @return [Boolean] whether to force all urls to be https.
51
58
  attr_accessor :force_ssl
52
59
 
53
60
  # @return [String] The default mime type for created instances. Default: _'image/jpeg'_
@@ -135,6 +142,7 @@ module Parse
135
142
  @url == u.url
136
143
  end
137
144
 
145
+ # Allows mass assignment from a Parse JSON hash.
138
146
  def attributes=(h)
139
147
  if h.is_a?(String)
140
148
  @url = h
@@ -172,6 +180,12 @@ module Parse
172
180
  saved?
173
181
  end
174
182
 
183
+ # @return [Boolean] true if this file is hosted by Parse's servers.
184
+ def parse_hosted_file?
185
+ return false if @url.blank?
186
+ ::File.basename(@url).starts_with?('tfss-') || @url.starts_with?('http://files.parsetfss.com')
187
+ end
188
+
175
189
  # @!visibility private
176
190
  def inspect
177
191
  "<Parse::File @name='#{@name}' @mime_type='#{@mime_type}' @contents=#{@contents.nil?} @url='#{@url}'>"
@@ -187,7 +201,7 @@ module Parse
187
201
 
188
202
  end
189
203
 
190
-
204
+ # Adds extensions to Hash class.
191
205
  class Hash
192
206
  # Determines if the hash contains Parse File json metadata fields. This is determined whether
193
207
  # the key `__type` exists and is of type `__File` and whether the `name` field matches the File.basename
@@ -23,18 +23,25 @@ module Parse
23
23
  # place.save
24
24
  #
25
25
  class GeoPoint < Model
26
+ # The default attributes in a Parse GeoPoint hash.
26
27
  ATTRIBUTES = { __type: :string, latitude: :float, longitude: :float }.freeze
27
28
 
28
29
  # @return [Float] latitude value between -90.0 and 90.0
29
30
  attr_accessor :latitude
30
31
  # @return [Float] longitude value between -180.0 and 180.0
31
32
  attr_accessor :longitude
32
- FIELD_LAT = "latitude"
33
- FIELD_LNG = "longitude"
33
+ # The key field for latitude
34
+ FIELD_LAT = "latitude".freeze
35
+ # The key field for longitude
36
+ FIELD_LNG = "longitude".freeze
34
37
 
38
+ # The minimum latitude value.
35
39
  LAT_MIN = -90.0
40
+ # The maximum latitude value.
36
41
  LAT_MAX = 90.0
42
+ # The minimum longitude value.
37
43
  LNG_MIN = -180.0
44
+ # The maximum longitude value.
38
45
  LNG_MAX = 180.0
39
46
 
40
47
  alias_method :lat, :latitude
@@ -30,24 +30,50 @@ module Parse
30
30
  extend ::ActiveModel::Callbacks # callback support on save, update, delete, etc.
31
31
  extend ::ActiveModel::Naming # provides the methods for getting class names from Model classes
32
32
 
33
- ID = "id".freeze
33
+ # The name of the field in a hash that contains information about the type
34
+ # of data the hash represents.
35
+ TYPE_FIELD = '__type'.freeze
36
+
37
+ # The objectId field in Parse Objects.
34
38
  OBJECT_ID = 'objectId'.freeze
39
+ # @see OBJECT_ID
40
+ ID = "id".freeze
41
+
42
+ # The key field for getting class information.
35
43
  KEY_CLASS_NAME = 'className'.freeze
44
+ # @deprecated Use OBJECT_ID instead.
36
45
  KEY_OBJECT_ID = 'objectId'.freeze
46
+ # The key field for getting the created at date of an object hash.
37
47
  KEY_CREATED_AT = 'createdAt'
48
+ # The key field for getting the updated at date of an object hash.
38
49
  KEY_UPDATED_AT = 'updatedAt'
50
+ # The collection for Users in Parse. Used by Parse::User.
39
51
  CLASS_USER = '_User'
52
+ # The collection for Installations in Parse. Used by Parse::Installation.
40
53
  CLASS_INSTALLATION = '_Installation'
54
+ # The collection for revocable Sessions in Parse. Used by Parse::Session.
41
55
  CLASS_SESSION = '_Session'
56
+ # The collection for revocable Roles in Parse. Used by Parse::Role.
42
57
  CLASS_ROLE = '_Role'
58
+ # The type label for hashes containing file data. Used by Parse::File.
43
59
  TYPE_FILE = 'File'
60
+ # The type label for hashes containing geopoints. Used by Parse::GeoPoint.
44
61
  TYPE_GEOPOINT = 'GeoPoint'
62
+ # The type label for hashes containing a Parse object. Used by Parse::Object and subclasses.
45
63
  TYPE_OBJECT = 'Object'
64
+ # The type label for hashes containing a Parse date object. Used by Parse::Date.
46
65
  TYPE_DATE = 'Date'
66
+ # The type label for hashes containing 'byte' data. Used by Parse::Bytes.
47
67
  TYPE_BYTES = 'Bytes'
68
+ # The type label for hashes containing ACL data. Used by Parse::ACL
69
+ TYPE_ACL = 'ACL'
70
+ # The type label for hashes storing numeric data.
71
+ TYPE_NUMBER = 'Number'
72
+ # The type label for hashes containing a Parse pointer.
48
73
  TYPE_POINTER = 'Pointer'
74
+ # The type label for hashes representing relational data.
49
75
  TYPE_RELATION = 'Relation'
50
- TYPE_FIELD = '__type'
76
+
51
77
 
52
78
  # To support being able to have different ruby class names from the 'table'
53
79
  # names used in Parse, we will need to have a dynamic lookup system where
@@ -58,21 +84,25 @@ module Parse
58
84
  # @!visibility private
59
85
  @@model_cache = {}
60
86
 
61
- # If set to true, a call to first_or_create will automatically save the object.
87
+ # @!attribute self.autosave_on_create
88
+ # If set to true, a call to {Parse::Object.first_or_create} will automatically save the object.
89
+ # Default is false.
62
90
  # @return [Boolean]
63
91
  def self.autosave_on_create
64
92
  @@autosave_on_create ||= false
65
93
  end
66
94
 
95
+ # @!visibility private
67
96
  def self.autosave_on_create=(bool)
68
97
  @@autosave_on_create = bool
69
98
  end
70
99
 
71
100
  class << self
101
+ # @!attribute self.raise_on_save_failure
72
102
  # By default, we return `true` or `false` for save and destroy operations.
73
103
  # If you prefer to have `Parse::Object` raise an exception instead, you
74
104
  # can tell to do so either globally or on a per-model basis. When a save
75
- # fails, it will raise a `Parse::SaveFailureError`.
105
+ # fails, it will raise a `Parse::RecordNotSaved`.
76
106
  #
77
107
  # @example
78
108
  # Parse::Model.raise_on_save_failure = true # globally across all models
@@ -84,15 +114,15 @@ module Parse
84
114
  # When enabled, if an error is returned by Parse due to saving or
85
115
  # destroying a record, due to your `before_save` or `before_delete`
86
116
  # validation cloud code triggers, `Parse::Object` will return the a
87
- # `Parse::SaveFailureError` exception type. This exception has an
117
+ # `Parse::RecordNotSaved` exception type. This exception has an
88
118
  # instance method of `#object` which contains the object that failed to save.
89
119
  #
90
120
  # @return [Boolean]
91
- attr_accessor :raise_on_save_failure
92
121
 
93
122
  def raise_on_save_failure
94
123
  @global_raise_on_save_failure ||= false
95
124
  end
125
+
96
126
  def raise_on_save_failure=(bool)
97
127
  @global_raise_on_save_failure = bool
98
128
  end
@@ -137,7 +167,7 @@ module Parse
137
167
 
138
168
  end
139
169
 
140
-
170
+ # Add extensions to the String class.
141
171
  class String
142
172
  # This method returns a camel-cased version of the string with the first letter
143
173
  # of the string in lower case. This is the standard naming convention for Parse columns
@@ -181,6 +211,7 @@ class String
181
211
  end
182
212
  end
183
213
 
214
+ # Add extensions to the Symbol class.
184
215
  class Symbol
185
216
  # @return [String] a lower-first-camelcased version of the symbol
186
217
  # @see String#columnize
@@ -11,23 +11,25 @@ require 'active_model_serializers'
11
11
  require 'time'
12
12
  require 'open-uri'
13
13
 
14
- require_relative "../client"
15
- require_relative "model"
16
- require_relative "pointer"
17
- require_relative "geopoint"
18
- require_relative "file"
19
- require_relative "bytes"
20
- require_relative "date"
21
- require_relative "acl"
22
- require_relative "push"
14
+ require_relative '../client'
15
+ require_relative 'model'
16
+ require_relative 'pointer'
17
+ require_relative 'geopoint'
18
+ require_relative 'file'
19
+ require_relative 'bytes'
20
+ require_relative 'date'
21
+ require_relative 'acl'
22
+ require_relative 'push'
23
23
  require_relative 'core/actions'
24
24
  require_relative 'core/fetching'
25
25
  require_relative 'core/querying'
26
- require_relative "core/schema"
27
- require_relative "core/properties"
28
- require_relative "associations/has_one"
29
- require_relative "associations/belongs_to"
30
- require_relative "associations/has_many"
26
+ require_relative 'core/schema'
27
+ require_relative 'core/properties'
28
+ require_relative 'core/errors'
29
+ require_relative 'core/builder'
30
+ require_relative 'associations/has_one'
31
+ require_relative 'associations/belongs_to'
32
+ require_relative 'associations/has_many'
31
33
 
32
34
 
33
35
  module Parse
@@ -36,6 +38,19 @@ module Parse
36
38
  Parse::Object.descendants.map(&:parse_class).uniq
37
39
  end
38
40
 
41
+ # @return [Array<Hash>] the list of all schemas for this application.
42
+ def self.schemas
43
+ client.schemas.results
44
+ end
45
+
46
+ # Fetch the schema for a specific collection name.
47
+ # @param className [String] the name collection
48
+ # @return [Hash] the schema document of this collection.
49
+ # @see Parse::Core::ClassBuilder.build!
50
+ def self.schema(className)
51
+ client.schema(className).result
52
+ end
53
+
39
54
  # Perform a non-destructive upgrade of all your Parse schemas in the backend
40
55
  # based on the property definitions of your local {Parse::Object} subclasses.
41
56
  def self.auto_upgrade!
@@ -91,10 +106,11 @@ module Parse
91
106
  include Associations::HasOne
92
107
  include Associations::BelongsTo
93
108
  include Associations::HasMany
94
- extend Querying
95
- extend Schema
96
- include Fetching
97
- include Actions
109
+ extend Core::Querying
110
+ extend Core::Schema
111
+ include Core::Fetching
112
+ include Core::Actions
113
+ # @!visibility private
98
114
  BASE_OBJECT_CLASS = "Parse::Object".freeze
99
115
 
100
116
  # @return [Model::TYPE_OBJECT]
@@ -192,7 +208,7 @@ module Parse
192
208
  alias_method :className, :parse_class
193
209
 
194
210
  # @return [Hash] the schema structure for this Parse collection from the server.
195
- # @see Parse::Schema
211
+ # @see Parse::Core::Schema
196
212
  def schema
197
213
  self.class.schema
198
214
  end
@@ -426,16 +442,41 @@ module Parse
426
442
  property :updated_at, :date
427
443
  property :acl, :acl, field: :ACL
428
444
 
445
+ # Alias to {created_at}
446
+ # @return (see #created_at)
429
447
  def createdAt
430
448
  return @created_at if Parse::Object.disable_serialized_string_date.present?
431
449
  @created_at.to_time.utc.iso8601(3) if @created_at.present?
432
450
  end
433
451
 
452
+ # Alias to {updated_at}
453
+ # @return (see #updated_at)
434
454
  def updatedAt
435
455
  return @updated_at if Parse::Object.disable_serialized_string_date.present?
436
456
  @updated_at.to_time.utc.iso8601(3) if @updated_at.present?
437
457
  end
438
458
 
459
+ # Access the value for a defined property through hash accessor. This method
460
+ # returns nil if the key is not one of the defined properties for this Parse::Object
461
+ # subclass.
462
+ # @param key [String] the name of the property. This key must be in the {fields} hash.
463
+ # @return [Object] the value for this key.
464
+ def [](key)
465
+ return nil unless self.class.fields[key.to_sym].present?
466
+ send(key)
467
+ end
468
+
469
+ # Set the value for a specific property through a hash accessor. This method
470
+ # does nothing if key is not one of the defined properties for this Parse::Object
471
+ # subclass.
472
+ # @param key (see Parse::Object#[])
473
+ # @param value [Object] the value to set this property.
474
+ # @return [Object] the value passed in.
475
+ def []=(key,value)
476
+ return unless self.class.fields[key.to_sym].present?
477
+ send("#{key}=",value)
478
+ end
479
+
439
480
  end
440
481
 
441
482