parse-stack-next 4.5.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 (178) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/config +2 -0
  3. data/.env.sample +112 -0
  4. data/.env.test +10 -0
  5. data/.github/workflows/ruby.yml +36 -0
  6. data/.gitignore +49 -0
  7. data/.ruby-version +1 -0
  8. data/.solargraph.yml +22 -0
  9. data/CHANGELOG.md +5816 -0
  10. data/Gemfile +30 -0
  11. data/Gemfile.lock +175 -0
  12. data/LICENSE.txt +23 -0
  13. data/Makefile +63 -0
  14. data/README.md +5655 -0
  15. data/Rakefile +573 -0
  16. data/bin/console +38 -0
  17. data/bin/parse-console +136 -0
  18. data/bin/server +17 -0
  19. data/bin/setup +7 -0
  20. data/config/parse-config.json +12 -0
  21. data/docs/TEST_SERVER.md +271 -0
  22. data/docs/_config.yml +1 -0
  23. data/docs/mcp_guide.md +3484 -0
  24. data/docs/mongodb_direct_guide.md +1348 -0
  25. data/docs/mongodb_index_optimization_guide.md +631 -0
  26. data/examples/transaction_example.rb +219 -0
  27. data/lib/parse/acl_scope.rb +728 -0
  28. data/lib/parse/agent/cancellation_token.rb +80 -0
  29. data/lib/parse/agent/constraint_translator.rb +480 -0
  30. data/lib/parse/agent/describe.rb +420 -0
  31. data/lib/parse/agent/errors.rb +133 -0
  32. data/lib/parse/agent/mcp_client.rb +557 -0
  33. data/lib/parse/agent/mcp_dispatcher.rb +1023 -0
  34. data/lib/parse/agent/mcp_rack_app.rb +1143 -0
  35. data/lib/parse/agent/mcp_server.rb +376 -0
  36. data/lib/parse/agent/metadata_audit.rb +259 -0
  37. data/lib/parse/agent/metadata_dsl.rb +733 -0
  38. data/lib/parse/agent/metadata_registry.rb +794 -0
  39. data/lib/parse/agent/pipeline_validator.rb +82 -0
  40. data/lib/parse/agent/prompts.rb +351 -0
  41. data/lib/parse/agent/rate_limiter.rb +158 -0
  42. data/lib/parse/agent/relation_graph.rb +162 -0
  43. data/lib/parse/agent/result_formatter.rb +453 -0
  44. data/lib/parse/agent/tools.rb +5489 -0
  45. data/lib/parse/agent.rb +3249 -0
  46. data/lib/parse/api/aggregate.rb +79 -0
  47. data/lib/parse/api/all.rb +26 -0
  48. data/lib/parse/api/analytics.rb +18 -0
  49. data/lib/parse/api/batch.rb +33 -0
  50. data/lib/parse/api/cloud_functions.rb +58 -0
  51. data/lib/parse/api/config.rb +125 -0
  52. data/lib/parse/api/files.rb +29 -0
  53. data/lib/parse/api/hooks.rb +117 -0
  54. data/lib/parse/api/objects.rb +146 -0
  55. data/lib/parse/api/path_segment.rb +75 -0
  56. data/lib/parse/api/push.rb +20 -0
  57. data/lib/parse/api/schema.rb +49 -0
  58. data/lib/parse/api/server.rb +50 -0
  59. data/lib/parse/api/sessions.rb +24 -0
  60. data/lib/parse/api/users.rb +250 -0
  61. data/lib/parse/atlas_search/index_manager.rb +353 -0
  62. data/lib/parse/atlas_search/result.rb +204 -0
  63. data/lib/parse/atlas_search/search_builder.rb +604 -0
  64. data/lib/parse/atlas_search/session.rb +253 -0
  65. data/lib/parse/atlas_search.rb +995 -0
  66. data/lib/parse/client/authentication.rb +97 -0
  67. data/lib/parse/client/batch.rb +234 -0
  68. data/lib/parse/client/body_builder.rb +240 -0
  69. data/lib/parse/client/caching.rb +203 -0
  70. data/lib/parse/client/logging.rb +293 -0
  71. data/lib/parse/client/profiling.rb +181 -0
  72. data/lib/parse/client/protocol.rb +91 -0
  73. data/lib/parse/client/request.rb +233 -0
  74. data/lib/parse/client/response.rb +208 -0
  75. data/lib/parse/client.rb +1104 -0
  76. data/lib/parse/clp_scope.rb +361 -0
  77. data/lib/parse/live_query/circuit_breaker.rb +256 -0
  78. data/lib/parse/live_query/client.rb +1001 -0
  79. data/lib/parse/live_query/configuration.rb +224 -0
  80. data/lib/parse/live_query/event.rb +115 -0
  81. data/lib/parse/live_query/event_queue.rb +272 -0
  82. data/lib/parse/live_query/health_monitor.rb +214 -0
  83. data/lib/parse/live_query/logging.rb +149 -0
  84. data/lib/parse/live_query/subscription.rb +294 -0
  85. data/lib/parse/live_query.rb +163 -0
  86. data/lib/parse/lookup_rewriter.rb +445 -0
  87. data/lib/parse/model/acl.rb +968 -0
  88. data/lib/parse/model/associations/belongs_to.rb +275 -0
  89. data/lib/parse/model/associations/collection_proxy.rb +435 -0
  90. data/lib/parse/model/associations/has_many.rb +597 -0
  91. data/lib/parse/model/associations/has_one.rb +158 -0
  92. data/lib/parse/model/associations/pointer_collection_proxy.rb +134 -0
  93. data/lib/parse/model/associations/relation_collection_proxy.rb +177 -0
  94. data/lib/parse/model/bytes.rb +62 -0
  95. data/lib/parse/model/classes/audience.rb +262 -0
  96. data/lib/parse/model/classes/installation.rb +363 -0
  97. data/lib/parse/model/classes/job_schedule.rb +153 -0
  98. data/lib/parse/model/classes/job_status.rb +264 -0
  99. data/lib/parse/model/classes/product.rb +75 -0
  100. data/lib/parse/model/classes/push_status.rb +263 -0
  101. data/lib/parse/model/classes/role.rb +751 -0
  102. data/lib/parse/model/classes/session.rb +201 -0
  103. data/lib/parse/model/classes/user.rb +943 -0
  104. data/lib/parse/model/clp.rb +544 -0
  105. data/lib/parse/model/core/actions.rb +1268 -0
  106. data/lib/parse/model/core/builder.rb +139 -0
  107. data/lib/parse/model/core/create_lock.rb +386 -0
  108. data/lib/parse/model/core/describe.rb +382 -0
  109. data/lib/parse/model/core/enhanced_change_tracking.rb +159 -0
  110. data/lib/parse/model/core/errors.rb +38 -0
  111. data/lib/parse/model/core/fetching.rb +566 -0
  112. data/lib/parse/model/core/field_guards.rb +220 -0
  113. data/lib/parse/model/core/indexing.rb +382 -0
  114. data/lib/parse/model/core/parse_reference.rb +407 -0
  115. data/lib/parse/model/core/properties.rb +809 -0
  116. data/lib/parse/model/core/querying.rb +491 -0
  117. data/lib/parse/model/core/schema.rb +202 -0
  118. data/lib/parse/model/core/search_indexing.rb +174 -0
  119. data/lib/parse/model/date.rb +88 -0
  120. data/lib/parse/model/email.rb +213 -0
  121. data/lib/parse/model/file.rb +527 -0
  122. data/lib/parse/model/geojson.rb +271 -0
  123. data/lib/parse/model/geopoint.rb +261 -0
  124. data/lib/parse/model/model.rb +260 -0
  125. data/lib/parse/model/object.rb +2068 -0
  126. data/lib/parse/model/phone.rb +520 -0
  127. data/lib/parse/model/pointer.rb +443 -0
  128. data/lib/parse/model/polygon.rb +406 -0
  129. data/lib/parse/model/push.rb +975 -0
  130. data/lib/parse/model/shortnames.rb +8 -0
  131. data/lib/parse/model/time_zone.rb +141 -0
  132. data/lib/parse/model/validations/uniqueness_validator.rb +97 -0
  133. data/lib/parse/model/validations.rb +96 -0
  134. data/lib/parse/mongodb.rb +2300 -0
  135. data/lib/parse/pipeline_security.rb +554 -0
  136. data/lib/parse/query/constraint.rb +198 -0
  137. data/lib/parse/query/constraints.rb +3279 -0
  138. data/lib/parse/query/cursor.rb +434 -0
  139. data/lib/parse/query/n_plus_one_detector.rb +445 -0
  140. data/lib/parse/query/operation.rb +104 -0
  141. data/lib/parse/query/ordering.rb +66 -0
  142. data/lib/parse/query.rb +7028 -0
  143. data/lib/parse/schema/index_migrator.rb +291 -0
  144. data/lib/parse/schema/search_index_migrator.rb +289 -0
  145. data/lib/parse/schema.rb +494 -0
  146. data/lib/parse/stack/generators/rails.rb +40 -0
  147. data/lib/parse/stack/generators/templates/model.erb +51 -0
  148. data/lib/parse/stack/generators/templates/model_installation.rb +4 -0
  149. data/lib/parse/stack/generators/templates/model_role.rb +4 -0
  150. data/lib/parse/stack/generators/templates/model_session.rb +4 -0
  151. data/lib/parse/stack/generators/templates/model_user.rb +11 -0
  152. data/lib/parse/stack/generators/templates/parse.rb +12 -0
  153. data/lib/parse/stack/generators/templates/webhooks.rb +10 -0
  154. data/lib/parse/stack/railtie.rb +18 -0
  155. data/lib/parse/stack/tasks.rb +563 -0
  156. data/lib/parse/stack/version.rb +11 -0
  157. data/lib/parse/stack.rb +455 -0
  158. data/lib/parse/two_factor_auth/user_extension.rb +449 -0
  159. data/lib/parse/two_factor_auth.rb +310 -0
  160. data/lib/parse/webhooks/payload.rb +360 -0
  161. data/lib/parse/webhooks/registration.rb +199 -0
  162. data/lib/parse/webhooks/replay_protection.rb +189 -0
  163. data/lib/parse/webhooks.rb +510 -0
  164. data/lib/parse-stack-next.rb +5 -0
  165. data/lib/parse-stack.rb +5 -0
  166. data/parse-stack-next.gemspec +82 -0
  167. data/parse-stack.png +0 -0
  168. data/scripts/debug-ips.js +35 -0
  169. data/scripts/docker/Dockerfile.parse +13 -0
  170. data/scripts/docker/atlas-init.js +284 -0
  171. data/scripts/docker/docker-compose.atlas.yml +76 -0
  172. data/scripts/docker/docker-compose.test.yml +106 -0
  173. data/scripts/docker/mongo-init.js +21 -0
  174. data/scripts/eval_mcp_with_lm_studio.rb +274 -0
  175. data/scripts/start-parse.sh +90 -0
  176. data/scripts/start_mcp_server.rb +78 -0
  177. data/scripts/test_server_connection.rb +82 -0
  178. metadata +377 -0
@@ -0,0 +1,968 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module Parse
5
+
6
+ # This class allows you to define custom data types for your model fields. You
7
+ # can define a subclass that implements the {DataType.typecast} method to
8
+ # convert to and from a value for serialization. The {Parse::ACL} class is
9
+ # implemented in this fashion.
10
+ class DataType
11
+ include ::ActiveModel::Model
12
+ include ::ActiveModel::Serializers::JSON
13
+
14
+ # @return [Hash] the set of attributes for this data type.
15
+ def attributes
16
+ {}
17
+ end
18
+
19
+ # Transform an incoming value to another. The default implementation does
20
+ # returns the original value. This method should return an instance of a
21
+ # DataType subclass.
22
+ # @param value [Object] the input value to be typecasted.
23
+ # @param opts [Hash] a set of options to be used when typecasting.
24
+ # @return [Object]
25
+ def self.typecast(value, **opts)
26
+ value
27
+ end
28
+
29
+ # Serialize this DataType into an JSON object hash format to be saved to Parse.
30
+ # The default implementation returns an empty hash.
31
+ # @return [Hash]
32
+ def as_json(*args)
33
+ {}.as_json
34
+ end
35
+ end
36
+
37
+ # An ACL represents the dirty-trackable Parse Permissions object used for
38
+ # each record. In Parse, it is composed a hash-like object that represent
39
+ # {Parse::User} objectIds and/or a set of {Parse::Role} names. For each entity
40
+ # (ex. User/Role/Public), you can define read/write privileges on a particular
41
+ # record through a {Parse::ACL::Permission} instance.
42
+ #
43
+ # If you want to give privileges for an action (ex. read/write),
44
+ # you set that particular permission it to true. If you want to deny a
45
+ # permission, then you set it to false. Any denied permissions will not be
46
+ # part of the final hash structure that is sent to parse, as omission of a permission
47
+ # means denial.
48
+ #
49
+ # An ACL is represented by a JSON object with the keys being Parse::User object
50
+ # ids or the special key of "*", which indicates the public access permissions.
51
+ # The value of each key in the hash is a {Parse::ACL::Permission} object which
52
+ # defines the boolean permission state for read and write.
53
+ # The example below illustrates a Parse ACL JSON object where there is a *public*
54
+ # read permission, but public write is prevented. In addition, the user with
55
+ # id "*3KmCvT7Zsb*" is allowed to both read and write this record, and the "*Admins*"
56
+ # role is also allowed write access.
57
+ #
58
+ # {
59
+ # "*": { "read": true },
60
+ # "3KmCvT7Zsb": { "read": true, "write": true },
61
+ # "role:Admins": { "write": true }
62
+ # }
63
+ #
64
+ # All Parse::Object subclasses have an acl property by default. With this
65
+ # property, you can apply and delete permissions for this particular Parse
66
+ # object record.
67
+ # user = Parse::User.first
68
+ # artist = Artist.first
69
+ #
70
+ # artist.acl # "*": { "read": true, "write": true }
71
+ #
72
+ # # apply public read, but no public write
73
+ # artist.acl.everyone true, false
74
+ #
75
+ #
76
+ # # allow user to have read and write access
77
+ # artist.acl.apply user.id, true, true
78
+ #
79
+ # # remove all permissions for this user id
80
+ # artist.acl.delete user.id
81
+ #
82
+ # # allow the 'Admins' role read and write
83
+ # artist.acl.apply_role "Admins", true, true
84
+ #
85
+ # # remove write from all attached privileges
86
+ # artist.acl.no_write!
87
+ #
88
+ # # remove all attached privileges
89
+ # artist.acl.master_key_only!
90
+ #
91
+ # artist.save
92
+ #
93
+ # You may also set default ACLs for your subclasses by using {Parse::Object.set_default_acl}.
94
+ # These will be get applied for newly created instances. All subclasses have
95
+ # public read and write enabled by default.
96
+ #
97
+ # class AdminData < Parse::Object
98
+ #
99
+ # # Disable public read and write
100
+ # set_default_acl :public, read: true, write: false
101
+ #
102
+ # # Allow Admin roles to read/write
103
+ # set_default_acl 'Admin', role: true, read: true, write: true
104
+ #
105
+ # end
106
+ #
107
+ # data = AdminData.new
108
+ # data.acl # => ACL({"role:Admin"=>{"read"=>true, "write"=>true}})
109
+ #
110
+ # For more information about Parse record ACLs, see the documentation on
111
+ # {http://docs.parseplatform.org/rest/guide/#security Security}.
112
+ class ACL < DataType
113
+
114
+ # @!attribute delegate
115
+ # The instance object to be notified of changes. The delegate must support
116
+ # receiving a {Parse::Object#acl_will_change!} method.
117
+ # @return [Parse::Object]
118
+ attr_writer :permissions
119
+ attr_accessor :delegate
120
+
121
+ # @!attribute [rw] permissions
122
+ # Contains a hash structure of permissions, with keys mapping to either Public '*',
123
+ # a role name or an objectId for a user and values of type {ACL::Permission}. If you
124
+ # modify this attribute directly, you should call {Parse::Object#acl_will_change!}
125
+ # on the target object in order for dirty tracking to register changes.
126
+ # @example
127
+ # object.acl.permissions
128
+ # # => { "*": { "read": true }, "3KmCvT7Zsb": { "read": true, "write": true } }
129
+ # @return [Hash] a hash of permissions.
130
+ def permissions
131
+ @permissions ||= {}
132
+ end
133
+
134
+ # The key field value for public permissions.
135
+ PUBLIC = "*".freeze
136
+
137
+ # Create a new ACL with default Public read/write permissions and any
138
+ # overrides from the input hash format.
139
+ # @param acls [Hash] a Parse-compatible hash acl format.
140
+ # @param owner [Parse::Object] a delegate to receive notifications of acl changes.
141
+ # This delegate must support receiving `acl_will_change!` method.
142
+ def initialize(acls = nil, owner: nil)
143
+ acls = acls.as_json if acls.is_a?(ACL)
144
+ self.attributes = acls if acls.is_a?(Hash)
145
+ @delegate = owner
146
+ end
147
+
148
+ # Create a new ACL with default Public read/write permissions and any
149
+ # overrides from the input hash format.
150
+ # @param read [Boolean] the read permissions for PUBLIC (default: true)
151
+ # @param write [Boolean] the write permissions for PUBLIC (default: true)
152
+ # @version 1.7.0
153
+ def self.everyone(read = true, write = true)
154
+ acl = Parse::ACL.new
155
+ acl.everyone(read, write)
156
+ acl
157
+ end
158
+
159
+ # Create a new private ACL with no public access.
160
+ # Objects with this ACL can only be accessed with the master key.
161
+ # @return [Parse::ACL] an empty ACL with no permissions.
162
+ # @version 3.1.3
163
+ # @example
164
+ # acl = Parse::ACL.private
165
+ # acl.as_json # => {}
166
+ def self.private
167
+ Parse::ACL.new
168
+ end
169
+
170
+ # Create a new ACL::Permission instance with the supplied read and write values.
171
+ # @param read [Boolean] the read permission value
172
+ # @param write [Boolean] the write permission value.
173
+ # @return [ACL::Permission]
174
+ # @see ACL::Permission
175
+ def self.permission(read, write = nil)
176
+ ACL::Permission.new(read, write)
177
+ end
178
+
179
+ # Build a MongoDB +$match+-shaped predicate that matches documents
180
+ # readable by any of +permissions+. The canonical shape Parse
181
+ # Server enforces at the REST/SDK layer:
182
+ #
183
+ # { "$or" => [
184
+ # { "_rperm" => { "$in" => permissions } },
185
+ # { "_rperm" => { "$exists" => false } }
186
+ # ]}
187
+ #
188
+ # The +$exists: false+ branch is mandatory. Parse Server treats a
189
+ # missing +_rperm+ field as publicly readable (the field is only
190
+ # written when an ACL exists), so dropping that branch silently
191
+ # hides every public document.
192
+ #
193
+ # Used by:
194
+ # * {Parse::Query::ACLReadableByConstraint} and
195
+ # {Parse::Query::ACLReadableByRoleConstraint} to compile
196
+ # +:ACL.readable_by+ / +:ACL.readable_by_role+ query
197
+ # constraints into aggregation pipelines.
198
+ # * {Parse::AtlasSearch} to enforce ACL on +$search+ pipelines
199
+ # that bypass Parse Server and query MongoDB directly.
200
+ #
201
+ # @param permissions [Array<String>] permission strings — user
202
+ # objectIds, +"role:RoleName"+, or +"*"+ for public.
203
+ # @param include_public [Boolean] when +true+ (default), +"*"+ is
204
+ # appended to +permissions+ if missing. Set +false+ for a
205
+ # strict-permissions check (e.g., +:ACL.readable_by => "none"+
206
+ # semantics).
207
+ # @return [Hash] a MongoDB +$or+ subexpression suitable for use
208
+ # inside a +$match+ stage.
209
+ # @example
210
+ # permissions = ["*", user.id] + user.acl_roles.to_a.map { |n| "role:#{n}" }
211
+ # pipeline << { "$match" => Parse::ACL.read_predicate(permissions) }
212
+ def self.read_predicate(permissions, include_public: true)
213
+ permission_predicate("_rperm", permissions, include_public: include_public)
214
+ end
215
+
216
+ # Build a MongoDB +$match+-shaped predicate that matches documents
217
+ # writable by any of +permissions+. Mirrors {.read_predicate} on
218
+ # the +_wperm+ field. See {.read_predicate} for the shape and the
219
+ # significance of the +$exists: false+ branch.
220
+ # @param permissions [Array<String>] permission strings.
221
+ # @param include_public [Boolean] whether to append +"*"+.
222
+ # @return [Hash] a MongoDB +$or+ subexpression.
223
+ def self.write_predicate(permissions, include_public: true)
224
+ permission_predicate("_wperm", permissions, include_public: include_public)
225
+ end
226
+
227
+ # @!visibility private
228
+ # Shared implementation for {.read_predicate} and {.write_predicate}.
229
+ # Normalizes the permissions array (string-coerced, deduplicated,
230
+ # +"*"+ appended when +include_public+) and returns the +$or+
231
+ # subexpression.
232
+ def self.permission_predicate(field, permissions, include_public: true)
233
+ perms = Array(permissions).map(&:to_s).reject(&:empty?).uniq
234
+ perms << "*" if include_public && !perms.include?("*")
235
+ {
236
+ "$or" => [
237
+ { field => { "$in" => perms } },
238
+ { field => { "$exists" => false } },
239
+ ],
240
+ }
241
+ end
242
+ # Determines whether two ACLs or a Parse-ACL hash is equivalent to this object.
243
+ # @example
244
+ # acl = Parse::ACL.new
245
+ # # create a public read-only ACL
246
+ # acl.apply :public, true, false
247
+ # acl.as_json # => {'*' => { "read" => true }}
248
+ #
249
+ # create a second instance with similar privileges
250
+ # acl2 = Parse::ACL.everyone(true, false)
251
+ # acl2.as_json # => {'*' => { "read" => true }}
252
+ #
253
+ # acl == acl2 # => true
254
+ # acl == {'*' => { "read" => true }} # hash ok too
255
+ #
256
+ # acl2.apply_role 'Admin', true, true # rw for Admins
257
+ # acl == acl2 # => false
258
+ #
259
+ # @return [Boolean] whether two ACLs have the same set of privileges.
260
+ def ==(other_acl)
261
+ return false unless other_acl.is_a?(self.class) || other_acl.is_a?(Hash)
262
+ as_json == other_acl.as_json
263
+ end
264
+
265
+ # Set the public read and write permissions.
266
+ # @param read [Boolean] the read permission state.
267
+ # @param write [Boolean] the write permission state.
268
+ # @return [Hash] the current public permissions.
269
+ def everyone(read, write)
270
+ apply(PUBLIC, read, write)
271
+ permissions[PUBLIC]
272
+ end
273
+
274
+ alias_method :world, :everyone
275
+
276
+ # Calls `acl_will_change!` on the delegate when the permissions have changed.
277
+ # All {Parse::Object} subclasses implement this method.
278
+ def will_change!
279
+ @delegate.acl_will_change! if @delegate.respond_to?(:acl_will_change!)
280
+ end
281
+
282
+ # Removes a permission for an objectId or user.
283
+ # @overload delete(object)
284
+ # @param object [Parse::User] the user to revoke permissions.
285
+ # @overload delete(id)
286
+ # @param id [String] the objectId to revoke permissions.
287
+ def delete(id)
288
+ id = id.id if id.is_a?(Parse::Pointer)
289
+ if id.present? && permissions.has_key?(id)
290
+ will_change!
291
+ permissions.delete(id)
292
+ end
293
+ end
294
+
295
+ # Apply a new permission with a given objectId, tag or :public.
296
+ # @overload apply(user, read = nil, write = nil)
297
+ # Set the read and write permissions for this user on this ACL.
298
+ # @param user [Parse::User] the user object.
299
+ # @param read [Boolean] the read permission.
300
+ # @param write [Boolean] the write permission.
301
+ # @overload apply(role, read = nil, write = nil)
302
+ # Set the read and write permissions for this role object on this ACL.
303
+ # @param role [Parse::Role] the role object.
304
+ # @param read [Boolean] the read permission.
305
+ # @param write [Boolean] the write permission.
306
+ # @overload apply(id, read = nil, write = nil)
307
+ # Set the read and write permissions for this objectId on this ACL.
308
+ # @param id [String|:public] the objectId for a {Parse::User}. If :public is passed,
309
+ # then the {Parse::ACL::PUBLIC} read and write permissions will be modified.
310
+ # @param read [Boolean] the read permission.
311
+ # @param write [Boolean] the write permission.
312
+ # @return [Hash] the current set of permissions.
313
+ # @see #apply_role
314
+ def apply(id, read = nil, write = nil)
315
+ return apply_role(id, read, write) if id.is_a?(Parse::Role)
316
+ id = id.id if id.is_a?(Parse::Pointer)
317
+ unless id.present?
318
+ raise ArgumentError, "Invalid argument applying ACLs: must be either objectId, role or :public"
319
+ end
320
+ id = PUBLIC if id.to_sym == :public
321
+ # create a new Permissions
322
+ permission = ACL.permission(read, write)
323
+ # if the input is already an Permission object, then set it directly
324
+ permission = read if read.is_a?(Parse::ACL::Permission)
325
+ if permission.is_a?(ACL::Permission)
326
+ if permissions[id.to_s] != permission
327
+ will_change! # dirty track
328
+ permissions[id.to_s] = permission
329
+ end
330
+ end
331
+
332
+ permissions
333
+ end
334
+
335
+ alias_method :add, :apply
336
+
337
+ # Apply a {Parse::Role} to this ACL.
338
+ # @overload apply_role(role, read = nil, write = nil)
339
+ # @param role [Parse::Role] the role object.
340
+ # @param read [Boolean] the read permission.
341
+ # @param write [Boolean] the write permission.
342
+ # @overload apply_role(role_name, read = nil, write = nil)
343
+ # @param role_name [String] the name of the role.
344
+ # @param read [Boolean] the read permission.
345
+ # @param write [Boolean] the write permission.
346
+ def apply_role(name, read = nil, write = nil)
347
+ name = name.name if name.is_a?(Parse::Role)
348
+ apply("role:#{name}", read, write)
349
+ end
350
+
351
+ alias_method :add_role, :apply_role
352
+
353
+ # Used for object conversion when formatting the input/output value in
354
+ # Parse::Object properties
355
+ # @param value [Hash] a Parse ACL hash to construct a Parse::ACL instance.
356
+ # @param delegate [Object] any object that would need to be notified of changes.
357
+ # @return [ACL]
358
+ # @see Parse::DataType
359
+ def self.typecast(value, delegate = nil)
360
+ ACL.new(value, owner: delegate)
361
+ end
362
+
363
+ # Used for JSON serialization. Only if an attribute is non-nil, do we allow it
364
+ # in the Permissions hash, since omission means denial of priviledge. If the
365
+ # permission value has neither read or write, then the entire record has been denied
366
+ # all privileges
367
+ # @return [Hash]
368
+ def attributes
369
+ permissions.select { |k, v| v.present? }.as_json
370
+ end
371
+
372
+ # @!visibility private
373
+ def attributes=(h)
374
+ return unless h.is_a?(Hash)
375
+ will_change!
376
+ @permissions ||= {}
377
+ h.each do |k, v|
378
+ apply(k, v)
379
+ end
380
+ end
381
+
382
+ # @!visibility private
383
+ def inspect
384
+ "ACL(#{as_json.inspect})"
385
+ end
386
+
387
+ # @return [Hash]
388
+ def as_json(*args)
389
+ permissions.select { |k, v| v.present? }.as_json
390
+ end
391
+
392
+ # @return [Boolean] true if there are any permissions.
393
+ def present?
394
+ permissions.values.any? { |v| v.present? }
395
+ end
396
+
397
+ # Removes all ACLs, which only allows requests using the Parse Server master key
398
+ # to query and modify the object.
399
+ # @example
400
+ # object.acl
401
+ # # { "*": { "read" : true },
402
+ # # "3KmCvT7Zsb": { "read" : true, "write": true },
403
+ # # "role:Admins": { "write": true }
404
+ # # }
405
+ # object.acl.master_key_only!
406
+ # # Outcome:
407
+ # # { }
408
+ # @version 1.7.2
409
+ # @return [Hash] The cleared permissions hash
410
+ def master_key_only!
411
+ will_change!
412
+ @permissions = {}
413
+ end
414
+
415
+ alias_method :clear!, :master_key_only!
416
+
417
+ # Grants read permission on all existing users and roles attached to this object.
418
+ # @example
419
+ # object.acl
420
+ # # { "*": { "read" : true },
421
+ # # "3KmCvT7Zsb": { "read" : true, "write": true },
422
+ # # "role:Admins": { "write": true }
423
+ # # }
424
+ # object.acl.all_read!
425
+ # # Outcome:
426
+ # # { "*": { "read" : true },
427
+ # # "3KmCvT7Zsb": { "read" : true, "write": true },
428
+ # # "role:Admins": { "read" : true, "write": true}
429
+ # # }
430
+ # @version 1.7.2
431
+ # @return [Array] list of ACL keys
432
+ def all_read!
433
+ will_change!
434
+ permissions.keys.each do |perm|
435
+ permissions[perm].read! true
436
+ end
437
+ end
438
+
439
+ # Grants write permission on all existing users and roles attached to this object.
440
+ # @example
441
+ # object.acl
442
+ # # { "*": { "read" : true },
443
+ # # "3KmCvT7Zsb": { "read" : true, "write": true },
444
+ # # "role:Admins": { "write": true }
445
+ # # }
446
+ # object.acl.all_write!
447
+ # # Outcome:
448
+ # # { "*": { "read" : true, "write": true },
449
+ # # "3KmCvT7Zsb": { "read" : true, "write": true },
450
+ # # "role:Admins": { "write": true }
451
+ # # }
452
+ # @version 1.7.2
453
+ # @return [Array] list of ACL keys
454
+ def all_write!
455
+ will_change!
456
+ permissions.keys.each do |perm|
457
+ permissions[perm].write! true
458
+ end
459
+ end
460
+
461
+ # Denies read permission on all existing users and roles attached to this object.
462
+ # @example
463
+ # object.acl
464
+ # # { "*": { "read" : true },
465
+ # # "3KmCvT7Zsb": { "read" : true, "write": true },
466
+ # # "role:Admins": { "write": true }
467
+ # # }
468
+ # object.acl.no_read!
469
+ # # Outcome:
470
+ # # { "*": nil,
471
+ # # "3KmCvT7Zsb": { "write": true },
472
+ # # "role:Admins": { "write": true }
473
+ # # }
474
+ # @version 1.7.2
475
+ # @return [Array] list of ACL keys
476
+ def no_read!
477
+ will_change!
478
+ permissions.keys.each do |perm|
479
+ permissions[perm].read! false
480
+ end
481
+ end
482
+
483
+ # Denies write permission on all existing users and roles attached to this object.
484
+ # @example
485
+ # object.acl
486
+ # # { "*": { "read" : true },
487
+ # # "3KmCvT7Zsb": { "read" : true, "write": true },
488
+ # # "role:Admins": { "write": true }
489
+ # # }
490
+ # object.acl.no_write!
491
+ # # Outcome:
492
+ # # { "*": { "read" : true },
493
+ # # "3KmCvT7Zsb": { "read" : true },
494
+ # # "role:Admins": nil
495
+ # # }
496
+ # @version 1.7.2
497
+ # @return [Array] list of ACL keys
498
+ def no_write!
499
+ will_change!
500
+ permissions.keys.each do |perm|
501
+ permissions[perm].write! false
502
+ end
503
+ end
504
+
505
+ # Returns an array of all user IDs and role names that have read access to this object.
506
+ # @return [Array<String>] list of user IDs and role names (e.g., ["*", "user123", "role:Admin"])
507
+ def readable_by
508
+ permissions.select { |k, v| v.read }.keys
509
+ end
510
+
511
+ # Returns an array of all user IDs and role names that have write access to this object.
512
+ # @return [Array<String>] list of user IDs and role names (e.g., ["*", "user123", "role:Admin"])
513
+ def writeable_by
514
+ permissions.select { |k, v| v.write }.keys
515
+ end
516
+
517
+ alias_method :writable_by, :writeable_by
518
+
519
+ # Checks if a specific user or role (or any in an array) has read access to this object.
520
+ # When passed an array of strings, returns true if ANY of the users/roles have read access (OR logic).
521
+ # When passed a Parse::User object or pointer, automatically fetches and checks the user's roles as well.
522
+ # @param user_or_role [String, Parse::User, Parse::Pointer, Array<String>] the user ID, role name, user object, user pointer, or array of user IDs/role names
523
+ # @return [Boolean] true if the user/role (or any in the array) has read access
524
+ # @example
525
+ # acl.readable_by?("user123") # Check single user ID
526
+ # acl.readable_by?("Admin") # Check single role name
527
+ # acl.readable_by?(user_object) # Check user + their roles
528
+ # acl.readable_by?(user_pointer) # Check user pointer + their roles
529
+ # acl.readable_by?(["user123", "Admin"]) # Check array (OR logic)
530
+ def readable_by?(user_or_role)
531
+ # Handle arrays - check if ANY item in the array has read access (OR logic)
532
+ if user_or_role.is_a?(Array)
533
+ # For arrays, just check each string value directly (no User object expansion)
534
+ return user_or_role.any? do |item|
535
+ key = normalize_permission_key(item)
536
+ key && permissions[key]&.read == true
537
+ end
538
+ end
539
+
540
+ # Handle Parse::Pointer to User - expand to include user ID and roles
541
+ if user_or_role.is_a?(Parse::Pointer) || (user_or_role.respond_to?(:parse_class) && user_or_role.respond_to?(:id))
542
+ # Check if it's a pointer to a User
543
+ if user_or_role.respond_to?(:parse_class) && (user_or_role.parse_class == "User" || user_or_role.parse_class == "_User")
544
+ permissions_to_check = []
545
+
546
+ # Add the user ID from the pointer
547
+ user_id = user_or_role.respond_to?(:id) ? user_or_role.id : nil
548
+ permissions_to_check << user_id if user_id.present?
549
+
550
+ # Query roles directly using the user pointer (no need to fetch the full user)
551
+ begin
552
+ if user_id.present? && defined?(Parse::Role)
553
+ user_roles = Parse::Role.all(users: user_or_role)
554
+ user_roles.each do |role|
555
+ permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
556
+ end
557
+ end
558
+ rescue
559
+ # If role fetching fails, continue with just the user ID
560
+ end
561
+
562
+ # Check if any of the user's permissions (user ID or roles) have read access
563
+ return readable_by?(permissions_to_check) if permissions_to_check.any?
564
+ return false
565
+ end
566
+ end
567
+
568
+ # If it's a User object, expand it to include the user ID and all their roles
569
+ if user_or_role.is_a?(Parse::User) || (user_or_role.respond_to?(:is_a?) && user_or_role.is_a?(Parse::User))
570
+ permissions_to_check = []
571
+
572
+ # Add the user ID
573
+ permissions_to_check << user_or_role.id if user_or_role.respond_to?(:id) && user_or_role.id.present?
574
+
575
+ # Fetch and add all the user's roles
576
+ begin
577
+ if user_or_role.respond_to?(:id) && user_or_role.id.present? && defined?(Parse::Role)
578
+ user_roles = Parse::Role.all(users: user_or_role)
579
+ user_roles.each do |role|
580
+ permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
581
+ end
582
+ end
583
+ rescue
584
+ # If role fetching fails, continue with just the user ID
585
+ end
586
+
587
+ # Check if any of the user's permissions (user ID or roles) have read access
588
+ # Use array checking logic (OR)
589
+ return readable_by?(permissions_to_check) if permissions_to_check.any?
590
+ return false
591
+ end
592
+
593
+ # Single string value - check directly
594
+ key = normalize_permission_key(user_or_role)
595
+ return false unless key
596
+ permissions[key]&.read == true
597
+ end
598
+
599
+ # Checks if a specific user or role (or any in an array) has write access to this object.
600
+ # When passed an array of strings, returns true if ANY of the users/roles have write access (OR logic).
601
+ # When passed a Parse::User object or pointer, automatically fetches and checks the user's roles as well.
602
+ # @param user_or_role [String, Parse::User, Parse::Pointer, Array<String>] the user ID, role name, user object, user pointer, or array of user IDs/role names
603
+ # @return [Boolean] true if the user/role (or any in the array) has write access
604
+ # @example
605
+ # acl.writeable_by?("user123") # Check single user ID
606
+ # acl.writeable_by?("Admin") # Check single role name
607
+ # acl.writeable_by?(user_object) # Check user + their roles
608
+ # acl.writeable_by?(user_pointer) # Check user pointer + their roles
609
+ # acl.writeable_by?(["user123", "Admin"]) # Check array (OR logic)
610
+ def writeable_by?(user_or_role)
611
+ # Handle arrays - check if ANY item in the array has write access (OR logic)
612
+ if user_or_role.is_a?(Array)
613
+ # For arrays, just check each string value directly (no User object expansion)
614
+ return user_or_role.any? do |item|
615
+ key = normalize_permission_key(item)
616
+ key && permissions[key]&.write == true
617
+ end
618
+ end
619
+
620
+ # Handle Parse::Pointer to User - expand to include user ID and roles
621
+ if user_or_role.is_a?(Parse::Pointer) || (user_or_role.respond_to?(:parse_class) && user_or_role.respond_to?(:id))
622
+ # Check if it's a pointer to a User
623
+ if user_or_role.respond_to?(:parse_class) && (user_or_role.parse_class == "User" || user_or_role.parse_class == "_User")
624
+ permissions_to_check = []
625
+
626
+ # Add the user ID from the pointer
627
+ user_id = user_or_role.respond_to?(:id) ? user_or_role.id : nil
628
+ permissions_to_check << user_id if user_id.present?
629
+
630
+ # Query roles directly using the user pointer (no need to fetch the full user)
631
+ begin
632
+ if user_id.present? && defined?(Parse::Role)
633
+ user_roles = Parse::Role.all(users: user_or_role)
634
+ user_roles.each do |role|
635
+ permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
636
+ end
637
+ end
638
+ rescue
639
+ # If role fetching fails, continue with just the user ID
640
+ end
641
+
642
+ # Check if any of the user's permissions (user ID or roles) have write access
643
+ return writeable_by?(permissions_to_check) if permissions_to_check.any?
644
+ return false
645
+ end
646
+ end
647
+
648
+ # If it's a User object, expand it to include the user ID and all their roles
649
+ if user_or_role.is_a?(Parse::User) || (user_or_role.respond_to?(:is_a?) && user_or_role.is_a?(Parse::User))
650
+ permissions_to_check = []
651
+
652
+ # Add the user ID
653
+ permissions_to_check << user_or_role.id if user_or_role.respond_to?(:id) && user_or_role.id.present?
654
+
655
+ # Fetch and add all the user's roles
656
+ begin
657
+ if user_or_role.respond_to?(:id) && user_or_role.id.present? && defined?(Parse::Role)
658
+ user_roles = Parse::Role.all(users: user_or_role)
659
+ user_roles.each do |role|
660
+ permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
661
+ end
662
+ end
663
+ rescue
664
+ # If role fetching fails, continue with just the user ID
665
+ end
666
+
667
+ # Check if any of the user's permissions (user ID or roles) have write access
668
+ # Use array checking logic (OR)
669
+ return writeable_by?(permissions_to_check) if permissions_to_check.any?
670
+ return false
671
+ end
672
+
673
+ # Single string value - check directly
674
+ key = normalize_permission_key(user_or_role)
675
+ return false unless key
676
+ permissions[key]&.write == true
677
+ end
678
+
679
+ alias_method :writable_by?, :writeable_by?
680
+ alias_method :can_read?, :readable_by?
681
+ alias_method :can_write?, :writeable_by?
682
+
683
+ # Checks if the object has public read access.
684
+ # @return [Boolean] true if public can read this object
685
+ def public_read?
686
+ permissions[PUBLIC]&.read == true
687
+ end
688
+
689
+ # Checks if the object has public write access.
690
+ # @return [Boolean] true if public can write to this object
691
+ def public_write?
692
+ permissions[PUBLIC]&.write == true
693
+ end
694
+
695
+ # Checks if the object has no read permissions for anyone (master key only).
696
+ # @return [Boolean] true if no one has read access
697
+ def no_read?
698
+ permissions.values.none? { |v| v.read }
699
+ end
700
+
701
+ # Checks if the object has no write permissions for anyone (master key only).
702
+ # @return [Boolean] true if no one has write access
703
+ def no_write?
704
+ permissions.values.none? { |v| v.write }
705
+ end
706
+
707
+ # Checks if the object is read-only (has read permissions but no write permissions).
708
+ # @return [Boolean] true if object has read access but no write access
709
+ def read_only?
710
+ permissions.values.any? { |v| v.read } && permissions.values.none? { |v| v.write }
711
+ end
712
+
713
+ # Checks if the object is write-only (has write permissions but no read permissions).
714
+ # @return [Boolean] true if object has write access but no read access
715
+ def write_only?
716
+ permissions.values.any? { |v| v.write } && permissions.values.none? { |v| v.read }
717
+ end
718
+
719
+ # Returns an array of all user IDs and role names that have both read and write access.
720
+ # @return [Array<String>] list of user IDs and role names with full access
721
+ def owners
722
+ permissions.select { |k, v| v.read && v.write }.keys
723
+ end
724
+
725
+ # Checks if a specific user or role (or any in an array) has both read and write access to this object.
726
+ # When passed an array of strings, returns true if ANY of the users/roles have both read and write access (OR logic).
727
+ # When passed a Parse::User object or pointer, automatically fetches and checks the user's roles as well.
728
+ # @param user_or_role [String, Parse::User, Parse::Pointer, Array<String>] the user ID, role name, user object, user pointer, or array of user IDs/role names
729
+ # @return [Boolean] true if the user/role (or any in the array) has both read and write access
730
+ # @example
731
+ # acl.owner?("user123") # Check single user ID
732
+ # acl.owner?("Admin") # Check single role name
733
+ # acl.owner?(user_object) # Check user + their roles
734
+ # acl.owner?(user_pointer) # Check user pointer + their roles
735
+ # acl.owner?(["user123", "Admin"]) # Check array (OR logic)
736
+ def owner?(user_or_role)
737
+ # Handle arrays - check if ANY item in the array is an owner (OR logic)
738
+ if user_or_role.is_a?(Array)
739
+ # For arrays, just check each string value directly (no User object expansion)
740
+ return user_or_role.any? do |item|
741
+ key = normalize_permission_key(item)
742
+ next false unless key
743
+ perm = permissions[key]
744
+ perm&.read == true && perm&.write == true
745
+ end
746
+ end
747
+
748
+ # Handle Parse::Pointer to User - expand to include user ID and roles
749
+ if user_or_role.is_a?(Parse::Pointer) || (user_or_role.respond_to?(:parse_class) && user_or_role.respond_to?(:id))
750
+ # Check if it's a pointer to a User
751
+ if user_or_role.respond_to?(:parse_class) && (user_or_role.parse_class == "User" || user_or_role.parse_class == "_User")
752
+ permissions_to_check = []
753
+
754
+ # Add the user ID from the pointer
755
+ user_id = user_or_role.respond_to?(:id) ? user_or_role.id : nil
756
+ permissions_to_check << user_id if user_id.present?
757
+
758
+ # Query roles directly using the user pointer (no need to fetch the full user)
759
+ begin
760
+ if user_id.present? && defined?(Parse::Role)
761
+ user_roles = Parse::Role.all(users: user_or_role)
762
+ user_roles.each do |role|
763
+ permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
764
+ end
765
+ end
766
+ rescue
767
+ # If role fetching fails, continue with just the user ID
768
+ end
769
+
770
+ # Check if any of the user's permissions (user ID or roles) are owners
771
+ return owner?(permissions_to_check) if permissions_to_check.any?
772
+ return false
773
+ end
774
+ end
775
+
776
+ # If it's a User object, expand it to include the user ID and all their roles
777
+ if user_or_role.is_a?(Parse::User) || (user_or_role.respond_to?(:is_a?) && user_or_role.is_a?(Parse::User))
778
+ permissions_to_check = []
779
+
780
+ # Add the user ID
781
+ permissions_to_check << user_or_role.id if user_or_role.respond_to?(:id) && user_or_role.id.present?
782
+
783
+ # Fetch and add all the user's roles
784
+ begin
785
+ if user_or_role.respond_to?(:id) && user_or_role.id.present? && defined?(Parse::Role)
786
+ user_roles = Parse::Role.all(users: user_or_role)
787
+ user_roles.each do |role|
788
+ permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
789
+ end
790
+ end
791
+ rescue
792
+ # If role fetching fails, continue with just the user ID
793
+ end
794
+
795
+ # Check if any of the user's permissions (user ID or roles) are owners
796
+ # Use array checking logic (OR)
797
+ return owner?(permissions_to_check) if permissions_to_check.any?
798
+ return false
799
+ end
800
+
801
+ # Single string value - check directly
802
+ key = normalize_permission_key(user_or_role)
803
+ return false unless key
804
+ perm = permissions[key]
805
+ perm&.read == true && perm&.write == true
806
+ end
807
+
808
+ # Checks if the ACL has no permissions (master key only access).
809
+ # @return [Boolean] true if no permissions exist
810
+ def empty?
811
+ permissions.empty? || permissions.values.none? { |v| v.present? }
812
+ end
813
+
814
+ alias_method :master_key_only?, :empty?
815
+ alias_method :master_only?, :empty?
816
+
817
+ private
818
+
819
+ # Normalizes a user or role input to the appropriate permission key format.
820
+ # @param user_or_role [String, Parse::User, Parse::Role] the input to normalize
821
+ # @return [String, nil] the normalized key or nil if invalid
822
+ def normalize_permission_key(user_or_role)
823
+ case user_or_role
824
+ when String
825
+ # Handle role names, user IDs, or public
826
+ if user_or_role == "*" || user_or_role.to_sym == :public
827
+ PUBLIC
828
+ elsif user_or_role.start_with?("role:")
829
+ user_or_role
830
+ else
831
+ # Could be a role name without prefix or a user ID
832
+ # Check if it exists as-is first, then try as role
833
+ if permissions.key?(user_or_role)
834
+ user_or_role
835
+ elsif permissions.key?("role:#{user_or_role}")
836
+ "role:#{user_or_role}"
837
+ else
838
+ user_or_role # Return as-is, might be a user ID
839
+ end
840
+ end
841
+ when Parse::User
842
+ user_or_role.id if user_or_role.respond_to?(:id)
843
+ when Parse::Role
844
+ "role:#{user_or_role.name}" if user_or_role.respond_to?(:name)
845
+ when Parse::Pointer
846
+ user_or_role.id if user_or_role.respond_to?(:id)
847
+ when Symbol
848
+ user_or_role == :public ? PUBLIC : user_or_role.to_s
849
+ else
850
+ nil
851
+ end
852
+ end
853
+
854
+ public
855
+
856
+ # The Permission class tracks the read and write permissions for a specific
857
+ # ACL entry. The value of an Parse-ACL hash only contains two keys: "read" and "write".
858
+ #
859
+ # # Example of the ACL format
860
+ # { "*": { "read": true },
861
+ # "3KmCvT7Zsb": { "read": true, "write": true },
862
+ # "role:Admins": { "write": true }
863
+ # }
864
+ # This would be managed as:
865
+ # { "*": ACL::Permission.new(true),
866
+ # "3KmCvT7Zsb": ACL::Permission.new(true, true)
867
+ # "role:Amdins": ACL::Permission.new(false,true)
868
+ # }
869
+ #
870
+ class Permission
871
+ include ::ActiveModel::Model
872
+ include ::ActiveModel::Serializers::JSON
873
+
874
+ # The *read* permission state.
875
+ # @return [Boolean] whether this permission is allowed.
876
+ attr_reader :read
877
+
878
+ # The *write* permission state.
879
+ # @return [Boolean] whether this permission is allowed.
880
+ attr_reader :write
881
+
882
+ # Create a new permission with the given read and write privileges.
883
+ # @overload new(read = nil, write = nil)
884
+ # @param read [Boolean] whether reading is allowed.
885
+ # @param write [Boolean] whether writing is allowed.
886
+ # @example
887
+ # ACL::Permission.new(true, false)
888
+ # @overload new(hash)
889
+ # @param hash [Hash] a key value pair for read/write permissions.
890
+ # @example
891
+ # ACL::Permission.new({read: true, write: false})
892
+ #
893
+ def initialize(r_perm = nil, w_perm = nil)
894
+ if r_perm.is_a?(Hash)
895
+ r_perm = r_perm.symbolize_keys
896
+ @read = r_perm[:read].present?
897
+ @write = r_perm[:write].present?
898
+ else
899
+ @read = r_perm.present?
900
+ @write = w_perm.present?
901
+ end
902
+ end
903
+
904
+ # @return [Boolean] whether two permission instances have the same permissions.
905
+ def ==(per)
906
+ return false unless per.is_a?(self.class)
907
+ @read == per.read && @write == per.write
908
+ end
909
+
910
+ # @return [Hash] A Parse-compatible ACL-hash. Omission or false on a
911
+ # priviledge means don't include it
912
+ def as_json(*args)
913
+ h = {}
914
+ h[:read] = true if @read
915
+ h[:write] = true if @write
916
+ h.empty? ? nil : h.as_json
917
+ end
918
+
919
+ # @return [Hash]
920
+ def attributes
921
+ h = {}
922
+ h.merge!(read: :boolean) if @read
923
+ h.merge!(write: :boolean) if @write
924
+ h
925
+ end
926
+
927
+ # @!visibility private
928
+ def inspect
929
+ as_json.inspect
930
+ end
931
+
932
+ # @return [Boolean] whether there is at least one permission set to true.
933
+ def present?
934
+ @read.present? || @write.present?
935
+ end
936
+
937
+ # Sets the *read* value of the permission. Defaults to true.
938
+ # @note Setting the value in this manner is not dirty tracked.
939
+ # @version 1.7.2
940
+ # @return [void]
941
+ def read!(value = true)
942
+ @read = value
943
+ end
944
+
945
+ # Sets the *write* value of the permission. Defaults to true.
946
+ # @note Setting the value in this manner is not dirty tracked.
947
+ # @version 1.7.2
948
+ # @return [void]
949
+ def write!(value = true)
950
+ @write = value
951
+ end
952
+
953
+ # Sets the *read* value of the permission to false.
954
+ # @version 1.7.2
955
+ # @return [void]
956
+ def no_read!
957
+ @read = false
958
+ end
959
+
960
+ # Sets the *write* value of the permission to false.
961
+ # @version 1.7.2
962
+ # @return [void]
963
+ def no_write!
964
+ @write = false
965
+ end
966
+ end
967
+ end
968
+ end