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.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.env.sample +112 -0
- data/.env.test +10 -0
- data/.github/workflows/ruby.yml +36 -0
- data/.gitignore +49 -0
- data/.ruby-version +1 -0
- data/.solargraph.yml +22 -0
- data/CHANGELOG.md +5816 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +175 -0
- data/LICENSE.txt +23 -0
- data/Makefile +63 -0
- data/README.md +5655 -0
- data/Rakefile +573 -0
- data/bin/console +38 -0
- data/bin/parse-console +136 -0
- data/bin/server +17 -0
- data/bin/setup +7 -0
- data/config/parse-config.json +12 -0
- data/docs/TEST_SERVER.md +271 -0
- data/docs/_config.yml +1 -0
- data/docs/mcp_guide.md +3484 -0
- data/docs/mongodb_direct_guide.md +1348 -0
- data/docs/mongodb_index_optimization_guide.md +631 -0
- data/examples/transaction_example.rb +219 -0
- data/lib/parse/acl_scope.rb +728 -0
- data/lib/parse/agent/cancellation_token.rb +80 -0
- data/lib/parse/agent/constraint_translator.rb +480 -0
- data/lib/parse/agent/describe.rb +420 -0
- data/lib/parse/agent/errors.rb +133 -0
- data/lib/parse/agent/mcp_client.rb +557 -0
- data/lib/parse/agent/mcp_dispatcher.rb +1023 -0
- data/lib/parse/agent/mcp_rack_app.rb +1143 -0
- data/lib/parse/agent/mcp_server.rb +376 -0
- data/lib/parse/agent/metadata_audit.rb +259 -0
- data/lib/parse/agent/metadata_dsl.rb +733 -0
- data/lib/parse/agent/metadata_registry.rb +794 -0
- data/lib/parse/agent/pipeline_validator.rb +82 -0
- data/lib/parse/agent/prompts.rb +351 -0
- data/lib/parse/agent/rate_limiter.rb +158 -0
- data/lib/parse/agent/relation_graph.rb +162 -0
- data/lib/parse/agent/result_formatter.rb +453 -0
- data/lib/parse/agent/tools.rb +5489 -0
- data/lib/parse/agent.rb +3249 -0
- data/lib/parse/api/aggregate.rb +79 -0
- data/lib/parse/api/all.rb +26 -0
- data/lib/parse/api/analytics.rb +18 -0
- data/lib/parse/api/batch.rb +33 -0
- data/lib/parse/api/cloud_functions.rb +58 -0
- data/lib/parse/api/config.rb +125 -0
- data/lib/parse/api/files.rb +29 -0
- data/lib/parse/api/hooks.rb +117 -0
- data/lib/parse/api/objects.rb +146 -0
- data/lib/parse/api/path_segment.rb +75 -0
- data/lib/parse/api/push.rb +20 -0
- data/lib/parse/api/schema.rb +49 -0
- data/lib/parse/api/server.rb +50 -0
- data/lib/parse/api/sessions.rb +24 -0
- data/lib/parse/api/users.rb +250 -0
- data/lib/parse/atlas_search/index_manager.rb +353 -0
- data/lib/parse/atlas_search/result.rb +204 -0
- data/lib/parse/atlas_search/search_builder.rb +604 -0
- data/lib/parse/atlas_search/session.rb +253 -0
- data/lib/parse/atlas_search.rb +995 -0
- data/lib/parse/client/authentication.rb +97 -0
- data/lib/parse/client/batch.rb +234 -0
- data/lib/parse/client/body_builder.rb +240 -0
- data/lib/parse/client/caching.rb +203 -0
- data/lib/parse/client/logging.rb +293 -0
- data/lib/parse/client/profiling.rb +181 -0
- data/lib/parse/client/protocol.rb +91 -0
- data/lib/parse/client/request.rb +233 -0
- data/lib/parse/client/response.rb +208 -0
- data/lib/parse/client.rb +1104 -0
- data/lib/parse/clp_scope.rb +361 -0
- data/lib/parse/live_query/circuit_breaker.rb +256 -0
- data/lib/parse/live_query/client.rb +1001 -0
- data/lib/parse/live_query/configuration.rb +224 -0
- data/lib/parse/live_query/event.rb +115 -0
- data/lib/parse/live_query/event_queue.rb +272 -0
- data/lib/parse/live_query/health_monitor.rb +214 -0
- data/lib/parse/live_query/logging.rb +149 -0
- data/lib/parse/live_query/subscription.rb +294 -0
- data/lib/parse/live_query.rb +163 -0
- data/lib/parse/lookup_rewriter.rb +445 -0
- data/lib/parse/model/acl.rb +968 -0
- data/lib/parse/model/associations/belongs_to.rb +275 -0
- data/lib/parse/model/associations/collection_proxy.rb +435 -0
- data/lib/parse/model/associations/has_many.rb +597 -0
- data/lib/parse/model/associations/has_one.rb +158 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +134 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +177 -0
- data/lib/parse/model/bytes.rb +62 -0
- data/lib/parse/model/classes/audience.rb +262 -0
- data/lib/parse/model/classes/installation.rb +363 -0
- data/lib/parse/model/classes/job_schedule.rb +153 -0
- data/lib/parse/model/classes/job_status.rb +264 -0
- data/lib/parse/model/classes/product.rb +75 -0
- data/lib/parse/model/classes/push_status.rb +263 -0
- data/lib/parse/model/classes/role.rb +751 -0
- data/lib/parse/model/classes/session.rb +201 -0
- data/lib/parse/model/classes/user.rb +943 -0
- data/lib/parse/model/clp.rb +544 -0
- data/lib/parse/model/core/actions.rb +1268 -0
- data/lib/parse/model/core/builder.rb +139 -0
- data/lib/parse/model/core/create_lock.rb +386 -0
- data/lib/parse/model/core/describe.rb +382 -0
- data/lib/parse/model/core/enhanced_change_tracking.rb +159 -0
- data/lib/parse/model/core/errors.rb +38 -0
- data/lib/parse/model/core/fetching.rb +566 -0
- data/lib/parse/model/core/field_guards.rb +220 -0
- data/lib/parse/model/core/indexing.rb +382 -0
- data/lib/parse/model/core/parse_reference.rb +407 -0
- data/lib/parse/model/core/properties.rb +809 -0
- data/lib/parse/model/core/querying.rb +491 -0
- data/lib/parse/model/core/schema.rb +202 -0
- data/lib/parse/model/core/search_indexing.rb +174 -0
- data/lib/parse/model/date.rb +88 -0
- data/lib/parse/model/email.rb +213 -0
- data/lib/parse/model/file.rb +527 -0
- data/lib/parse/model/geojson.rb +271 -0
- data/lib/parse/model/geopoint.rb +261 -0
- data/lib/parse/model/model.rb +260 -0
- data/lib/parse/model/object.rb +2068 -0
- data/lib/parse/model/phone.rb +520 -0
- data/lib/parse/model/pointer.rb +443 -0
- data/lib/parse/model/polygon.rb +406 -0
- data/lib/parse/model/push.rb +975 -0
- data/lib/parse/model/shortnames.rb +8 -0
- data/lib/parse/model/time_zone.rb +141 -0
- data/lib/parse/model/validations/uniqueness_validator.rb +97 -0
- data/lib/parse/model/validations.rb +96 -0
- data/lib/parse/mongodb.rb +2300 -0
- data/lib/parse/pipeline_security.rb +554 -0
- data/lib/parse/query/constraint.rb +198 -0
- data/lib/parse/query/constraints.rb +3279 -0
- data/lib/parse/query/cursor.rb +434 -0
- data/lib/parse/query/n_plus_one_detector.rb +445 -0
- data/lib/parse/query/operation.rb +104 -0
- data/lib/parse/query/ordering.rb +66 -0
- data/lib/parse/query.rb +7028 -0
- data/lib/parse/schema/index_migrator.rb +291 -0
- data/lib/parse/schema/search_index_migrator.rb +289 -0
- data/lib/parse/schema.rb +494 -0
- data/lib/parse/stack/generators/rails.rb +40 -0
- data/lib/parse/stack/generators/templates/model.erb +51 -0
- data/lib/parse/stack/generators/templates/model_installation.rb +4 -0
- data/lib/parse/stack/generators/templates/model_role.rb +4 -0
- data/lib/parse/stack/generators/templates/model_session.rb +4 -0
- data/lib/parse/stack/generators/templates/model_user.rb +11 -0
- data/lib/parse/stack/generators/templates/parse.rb +12 -0
- data/lib/parse/stack/generators/templates/webhooks.rb +10 -0
- data/lib/parse/stack/railtie.rb +18 -0
- data/lib/parse/stack/tasks.rb +563 -0
- data/lib/parse/stack/version.rb +11 -0
- data/lib/parse/stack.rb +455 -0
- data/lib/parse/two_factor_auth/user_extension.rb +449 -0
- data/lib/parse/two_factor_auth.rb +310 -0
- data/lib/parse/webhooks/payload.rb +360 -0
- data/lib/parse/webhooks/registration.rb +199 -0
- data/lib/parse/webhooks/replay_protection.rb +189 -0
- data/lib/parse/webhooks.rb +510 -0
- data/lib/parse-stack-next.rb +5 -0
- data/lib/parse-stack.rb +5 -0
- data/parse-stack-next.gemspec +82 -0
- data/parse-stack.png +0 -0
- data/scripts/debug-ips.js +35 -0
- data/scripts/docker/Dockerfile.parse +13 -0
- data/scripts/docker/atlas-init.js +284 -0
- data/scripts/docker/docker-compose.atlas.yml +76 -0
- data/scripts/docker/docker-compose.test.yml +106 -0
- data/scripts/docker/mongo-init.js +21 -0
- data/scripts/eval_mcp_with_lm_studio.rb +274 -0
- data/scripts/start-parse.sh +90 -0
- data/scripts/start_mcp_server.rb +78 -0
- data/scripts/test_server_connection.rb +82 -0
- metadata +377 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../../query"
|
|
5
|
+
|
|
6
|
+
module Parse
|
|
7
|
+
module Core
|
|
8
|
+
# Defines the querying methods applied to a Parse::Object.
|
|
9
|
+
module Querying
|
|
10
|
+
|
|
11
|
+
# This feature is a small subset of the
|
|
12
|
+
# {http://guides.rubyonrails.org/active_record_querying.html#scopes
|
|
13
|
+
# ActiveRecord named scopes} feature. Scoping allows you to specify
|
|
14
|
+
# commonly-used queries which can be referenced as class method calls and
|
|
15
|
+
# are chainable with other scopes. You can use every {Parse::Query}
|
|
16
|
+
# method previously covered such as `where`, `includes` and `limit`.
|
|
17
|
+
#
|
|
18
|
+
# class Article < Parse::Object
|
|
19
|
+
# property :published, :boolean
|
|
20
|
+
# scope :published, -> { query(published: true) }
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# This is the same as defining your own class method for the query.
|
|
24
|
+
#
|
|
25
|
+
# class Article < Parse::Object
|
|
26
|
+
# def self.published
|
|
27
|
+
# query(published: true)
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# You can also chain scopes and pass parameters. In addition, boolean and
|
|
32
|
+
# enumerated properties have automatically generated scopes for you to use.
|
|
33
|
+
#
|
|
34
|
+
# class Article < Parse::Object
|
|
35
|
+
# scope :published, -> { query(published: true) }
|
|
36
|
+
#
|
|
37
|
+
# property :comment_count, :integer
|
|
38
|
+
# property :category
|
|
39
|
+
# property :approved, :boolean
|
|
40
|
+
#
|
|
41
|
+
# scope :published_and_commented, -> { published.where :comment_count.gt => 0 }
|
|
42
|
+
# scope :popular_topics, ->(name) { published_and_commented.where category: name }
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# # simple scope
|
|
46
|
+
# Article.published # => where published is true
|
|
47
|
+
#
|
|
48
|
+
# # chained scope
|
|
49
|
+
# Article.published_and_commented # published is true and comment_count > 0
|
|
50
|
+
#
|
|
51
|
+
# # scope with parameters
|
|
52
|
+
# Article.popular_topic("music") # => popular music articles
|
|
53
|
+
# # equivalent: where(published: true, :comment_count.gt => 0, category: name)
|
|
54
|
+
#
|
|
55
|
+
# # automatically generated scope
|
|
56
|
+
# Article.approved(category: "tour") # => where approved: true, category: 'tour'
|
|
57
|
+
#
|
|
58
|
+
# If you would like to turn off automatic scope generation for property types,
|
|
59
|
+
# set the option `:scope` to false when declaring the property.
|
|
60
|
+
# @param name [Symbol] the name of the scope.
|
|
61
|
+
# @param body [Proc] the proc related to the scope.
|
|
62
|
+
# @raise ArgumentError if body parameter does not respond to `call`
|
|
63
|
+
# @return [Symbol] the name of the singleton method created.
|
|
64
|
+
def scope(name, body)
|
|
65
|
+
unless body.respond_to?(:call)
|
|
66
|
+
raise ArgumentError, "The scope body needs to be callable."
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
name = name.to_sym
|
|
70
|
+
if respond_to?(name, true)
|
|
71
|
+
puts "Creating scope :#{name}. Will overwrite existing method #{self}.#{name}."
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
define_singleton_method(name) do |*args, &block|
|
|
75
|
+
if body.arity.zero?
|
|
76
|
+
res = body.call
|
|
77
|
+
res.conditions(*args) if args.present?
|
|
78
|
+
else
|
|
79
|
+
res = body.call(*args)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
_q = res || query
|
|
83
|
+
|
|
84
|
+
if _q.is_a?(Parse::Query)
|
|
85
|
+
klass = self
|
|
86
|
+
_q.define_singleton_method(:method_missing) do |m, *args, &chained_block|
|
|
87
|
+
if klass.respond_to?(m, true)
|
|
88
|
+
# must be a scope
|
|
89
|
+
klass_scope = klass.send(m, *args)
|
|
90
|
+
if klass_scope.is_a?(Parse::Query)
|
|
91
|
+
# merge constraints
|
|
92
|
+
add_constraints(klass_scope.constraints)
|
|
93
|
+
# if a block was passed, execute the query, otherwise return the query
|
|
94
|
+
return chained_block.present? ? results(&chained_block) : self
|
|
95
|
+
end # if
|
|
96
|
+
klass = nil # help clean up ruby gc
|
|
97
|
+
return klass_scope
|
|
98
|
+
end
|
|
99
|
+
klass = nil # help clean up ruby gc
|
|
100
|
+
return results.send(m, *args, &chained_block)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
Parse::Query.apply_auto_introspection!(_q)
|
|
105
|
+
|
|
106
|
+
return _q if block.nil?
|
|
107
|
+
_q.results(&block)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Creates a new {Parse::Query} with the given constraints for this class.
|
|
112
|
+
# @example
|
|
113
|
+
# # assume Post < Parse::Object
|
|
114
|
+
# query = Post.query(:updated_at.before => DateTime.now)
|
|
115
|
+
# @return [Parse::Query] a new query with the given constraints for this
|
|
116
|
+
# Parse::Object subclass.
|
|
117
|
+
def query(constraints = {})
|
|
118
|
+
Parse::Query.new self.parse_class, constraints
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
alias_method :where, :query
|
|
122
|
+
|
|
123
|
+
# @param conditions (see Parse::Query#where)
|
|
124
|
+
# @return (see Parse::Query#where)
|
|
125
|
+
# @see Parse::Query#where
|
|
126
|
+
def literal_where(conditions = {})
|
|
127
|
+
query.where(conditions)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# This methods allow you to efficiently iterate over all the records in the collection
|
|
131
|
+
# (lower memory cost) at a minor cost of performance. This method utilizes
|
|
132
|
+
# the `created_at` field of Parse records to order and iterate over *all* matching records,
|
|
133
|
+
# therefore you should not use this method if you want to perform a query
|
|
134
|
+
# with constraints against the `created_at` field or need specific type of ordering.
|
|
135
|
+
# If you need to use `:created_at` in your constraints, consider using {Parse::Core::Querying#all} or
|
|
136
|
+
# {Parse::Core::Actions::ClassMethods#save_all}
|
|
137
|
+
# @param constraints [Hash] a set of query constraints.
|
|
138
|
+
# @yield a block which will iterate through each matching record.
|
|
139
|
+
# @example
|
|
140
|
+
#
|
|
141
|
+
# post = Post.first
|
|
142
|
+
# # iterate over all comments matching conditions
|
|
143
|
+
# Comment.each(post: post) do |comment|
|
|
144
|
+
# # ...
|
|
145
|
+
# end
|
|
146
|
+
# @return [Parse::Object] the last Parse::Object record processed.
|
|
147
|
+
# @note You cannot use *:created_at* as a constraint.
|
|
148
|
+
# @raise ArgumentError if :created_at is detected in the constraints argument.
|
|
149
|
+
# @see Parse::Core::Querying.all
|
|
150
|
+
# @see Parse::Core::Actions.save_all
|
|
151
|
+
def each(constraints = {}, &block)
|
|
152
|
+
# verify we don't hvae created at as a constraint, otherwise this will not work
|
|
153
|
+
invalid_constraints = constraints.keys.any? do |k|
|
|
154
|
+
(k == :created_at || k == :createdAt) ||
|
|
155
|
+
(k.is_a?(Parse::Operation) && (k.operand == :created_at || k.operand == :createdAt))
|
|
156
|
+
end
|
|
157
|
+
if invalid_constraints
|
|
158
|
+
raise ArgumentError, "[#{self.class}.each] Special method each()" \
|
|
159
|
+
"cannot be used with a :created_at constraint."
|
|
160
|
+
end
|
|
161
|
+
batch_size = 250
|
|
162
|
+
start_cursor = first(order: :created_at.asc, keys: :created_at)
|
|
163
|
+
constraints.merge! cache: false, limit: batch_size, order: :created_at.asc
|
|
164
|
+
_all_query = query(constraints) # used for reference in loop below
|
|
165
|
+
cursor = start_cursor
|
|
166
|
+
# the exclusion set is a set of ids not to include the next query.
|
|
167
|
+
exclusion_set = []
|
|
168
|
+
loop do
|
|
169
|
+
_q = query(constraints.dup)
|
|
170
|
+
_q.where(:created_at.on_or_after => cursor.created_at)
|
|
171
|
+
# set of ids not to include in the next query. non-performant, but accurate.
|
|
172
|
+
_q.where(:id.nin => exclusion_set) unless exclusion_set.empty?
|
|
173
|
+
results = _q.results # get results
|
|
174
|
+
|
|
175
|
+
break cursor if results.empty? # break if no results
|
|
176
|
+
results.each(&block)
|
|
177
|
+
next_cursor = results.last
|
|
178
|
+
# break if we got less than the maximum requested
|
|
179
|
+
break next_cursor if results.count < batch_size
|
|
180
|
+
# break if the next object is the same as the current object.
|
|
181
|
+
break next_cursor if cursor.id == next_cursor.id
|
|
182
|
+
# The exclusion set is used in the case where multiple records have the exact
|
|
183
|
+
# same created_at date (down to the microsecond). This prevents getting the same
|
|
184
|
+
# record in the next query request.
|
|
185
|
+
exclusion_set = results.select { |r| r.created_at == next_cursor.created_at }.map(&:id)
|
|
186
|
+
results = nil
|
|
187
|
+
cursor = next_cursor
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Fetch all matching objects in this collection matching the constraints.
|
|
192
|
+
# This will be the most common way when querying Parse objects for a subclass.
|
|
193
|
+
# When no block is passed, all objects are returned. Using a block is more memory
|
|
194
|
+
# efficient as matching objects are fetched in batches and discarded after the iteration
|
|
195
|
+
# is completed.
|
|
196
|
+
# @param constraints [Hash] a set of {Parse::Query} constraints.
|
|
197
|
+
# @yield a block to iterate with each matching object.
|
|
198
|
+
# @example
|
|
199
|
+
#
|
|
200
|
+
# songs = Song.all( ... expressions ...) # => array of Parse::Objects
|
|
201
|
+
# # memory efficient for large amounts of records.
|
|
202
|
+
# Song.all( ... expressions ...) do |song|
|
|
203
|
+
# # ... do something with song..
|
|
204
|
+
# end
|
|
205
|
+
#
|
|
206
|
+
# @note This method will continually query for records by automatically
|
|
207
|
+
# incrementing the *:skip* parameter until no more results are returned
|
|
208
|
+
# by the server.
|
|
209
|
+
# @return [Array<Parse::Object>] an array of matching objects. If a block is passed,
|
|
210
|
+
# an empty array is returned.
|
|
211
|
+
def all(constraints = { limit: :max }, &block)
|
|
212
|
+
constraints = constraints.reverse_merge({ limit: :max })
|
|
213
|
+
prepared_query = query(constraints)
|
|
214
|
+
return prepared_query.results(&block) if block_given?
|
|
215
|
+
prepared_query.results
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Returns the first item matching the constraint.
|
|
219
|
+
# @overload first(count = 1)
|
|
220
|
+
# @param count [Interger] The number of items to return.
|
|
221
|
+
# @example
|
|
222
|
+
# Object.first(2) # => an array of the first 2 objects in the collection.
|
|
223
|
+
# @return [Parse::Object] if count == 1
|
|
224
|
+
# @return [Array<Parse::Object>] if count > 1
|
|
225
|
+
# @overload first(constraints = {})
|
|
226
|
+
# @param constraints [Hash] a set of {Parse::Query} constraints.
|
|
227
|
+
# @example
|
|
228
|
+
# Object.first( :name => "Anthony" )
|
|
229
|
+
# @return [Parse::Object] the first matching object.
|
|
230
|
+
def first(constraints = {})
|
|
231
|
+
fetch_count = 1
|
|
232
|
+
if constraints.is_a?(Numeric)
|
|
233
|
+
fetch_count = constraints.to_i
|
|
234
|
+
constraints = {}
|
|
235
|
+
end
|
|
236
|
+
constraints.merge!({ limit: fetch_count })
|
|
237
|
+
res = query(constraints).results
|
|
238
|
+
return res.first if fetch_count == 1
|
|
239
|
+
return res.first fetch_count
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Returns the most recently created object (ordered by created_at descending).
|
|
243
|
+
# @overload latest(count = 1)
|
|
244
|
+
# @param count [Integer] The number of items to return.
|
|
245
|
+
# @example
|
|
246
|
+
# Object.latest(3) # => an array of the 3 most recently created objects.
|
|
247
|
+
# @return [Parse::Object] if count == 1
|
|
248
|
+
# @return [Array<Parse::Object>] if count > 1
|
|
249
|
+
# @overload latest(constraints = {})
|
|
250
|
+
# @param constraints [Hash] a set of {Parse::Query} constraints.
|
|
251
|
+
# Supports a :limit key to override the default limit of 1.
|
|
252
|
+
# @example
|
|
253
|
+
# Object.latest(category: "news") # => most recent object in news category
|
|
254
|
+
# Object.latest(:user.eq => user, limit: 5) # => 5 most recent for user
|
|
255
|
+
# @return [Parse::Object] the most recently created object matching constraints.
|
|
256
|
+
def latest(constraints = {})
|
|
257
|
+
fetch_count = 1
|
|
258
|
+
if constraints.is_a?(Numeric)
|
|
259
|
+
fetch_count = constraints.to_i
|
|
260
|
+
constraints = {}
|
|
261
|
+
else
|
|
262
|
+
# Allow limit to be specified in constraints hash
|
|
263
|
+
fetch_count = constraints.delete(:limit) || 1
|
|
264
|
+
end
|
|
265
|
+
constraints.merge!({ limit: fetch_count, order: :created_at.desc })
|
|
266
|
+
res = query(constraints).results
|
|
267
|
+
return res.first if fetch_count == 1
|
|
268
|
+
return res.first fetch_count
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Returns the most recently updated object (ordered by updated_at descending).
|
|
272
|
+
# @overload last_updated(count = 1)
|
|
273
|
+
# @param count [Integer] The number of items to return.
|
|
274
|
+
# @example
|
|
275
|
+
# Object.last_updated(5) # => an array of the 5 most recently updated objects.
|
|
276
|
+
# @return [Parse::Object] if count == 1
|
|
277
|
+
# @return [Array<Parse::Object>] if count > 1
|
|
278
|
+
# @overload last_updated(constraints = {})
|
|
279
|
+
# @param constraints [Hash] a set of {Parse::Query} constraints.
|
|
280
|
+
# Supports a :limit key to override the default limit of 1.
|
|
281
|
+
# @example
|
|
282
|
+
# Object.last_updated(status: "active") # => most recently updated active object
|
|
283
|
+
# Object.last_updated(:user.eq => user, limit: 3) # => 3 most recently updated for user
|
|
284
|
+
# @return [Parse::Object] the most recently updated object matching constraints.
|
|
285
|
+
def last_updated(constraints = {})
|
|
286
|
+
fetch_count = 1
|
|
287
|
+
if constraints.is_a?(Numeric)
|
|
288
|
+
fetch_count = constraints.to_i
|
|
289
|
+
constraints = {}
|
|
290
|
+
else
|
|
291
|
+
# Allow limit to be specified in constraints hash
|
|
292
|
+
fetch_count = constraints.delete(:limit) || 1
|
|
293
|
+
end
|
|
294
|
+
constraints.merge!({ limit: fetch_count, order: :updated_at.desc })
|
|
295
|
+
res = query(constraints).results
|
|
296
|
+
return res.first if fetch_count == 1
|
|
297
|
+
return res.first fetch_count
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Creates a count request which is more performant when counting objects.
|
|
301
|
+
# @example
|
|
302
|
+
# # number of songs with a like count greater than 20.
|
|
303
|
+
# count = Song.count( :like_count.gt => 20 )
|
|
304
|
+
# @param constraints (see #all)
|
|
305
|
+
# @return [Interger] the number of records matching the query.
|
|
306
|
+
# @see Parse::Query#count
|
|
307
|
+
def count(constraints = {})
|
|
308
|
+
query(constraints).count
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Counts the number of distinct values for a specified field.
|
|
312
|
+
# Uses MongoDB aggregation pipeline to efficiently count unique values.
|
|
313
|
+
# @example
|
|
314
|
+
# # get count of unique genres for songs with play_count > 100
|
|
315
|
+
# distinct_genres_count = Song.count_distinct(:genre, :play_count.gt => 100)
|
|
316
|
+
# # get total number of unique users
|
|
317
|
+
# unique_users = User.count_distinct(:objectId)
|
|
318
|
+
# @param field [Symbol|String] The name of the field to count distinct values for.
|
|
319
|
+
# @param constraints (see #all)
|
|
320
|
+
# @return [Integer] the number of distinct values
|
|
321
|
+
# @see Parse::Query#count_distinct
|
|
322
|
+
def count_distinct(field, constraints = {})
|
|
323
|
+
query(constraints).count_distinct(field)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Finds the distinct values for a specified field across a single
|
|
327
|
+
# collection or view and returns the results in an array.
|
|
328
|
+
# @example
|
|
329
|
+
# # get a list of unique city names for users who are older than 21.
|
|
330
|
+
# cities = User.distinct(:city, :age.gt => 21 )
|
|
331
|
+
# @param field The name of the field to use for unique aggregation.
|
|
332
|
+
# @param constraints (see #all)
|
|
333
|
+
# @return [Array] a list of distinct values
|
|
334
|
+
# @see Parse::Query#distinct
|
|
335
|
+
def distinct(field, constraints = {})
|
|
336
|
+
query(constraints).distinct(field)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Find objects matching the constraint ordered by the descending created_at date.
|
|
340
|
+
# @param constraints (see #all)
|
|
341
|
+
# @return [Array<Parse::Object>]
|
|
342
|
+
def newest(constraints = {})
|
|
343
|
+
constraints.merge!(order: :created_at.desc)
|
|
344
|
+
_q = query(constraints)
|
|
345
|
+
_q.define_singleton_method(:method_missing) { |m, *args, &block| self.results.send(m, *args, &block) }
|
|
346
|
+
_q
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Find objects matching the constraint ordered by the ascending created_at date.
|
|
350
|
+
# @param constraints (see #all)
|
|
351
|
+
# @return [Array<Parse::Object>]
|
|
352
|
+
def oldest(constraints = {})
|
|
353
|
+
constraints.merge!(order: :created_at.asc)
|
|
354
|
+
_q = query(constraints)
|
|
355
|
+
_q.define_singleton_method(:method_missing) { |m, *args, &block| self.results.send(m, *args, &block) }
|
|
356
|
+
_q
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Create a cursor-based paginator for efficiently traversing large datasets.
|
|
360
|
+
# This is more efficient than skip/offset pagination for large result sets.
|
|
361
|
+
#
|
|
362
|
+
# @example Basic usage
|
|
363
|
+
# cursor = Song.cursor(limit: 100, order: :created_at.desc)
|
|
364
|
+
# cursor.each_page do |page|
|
|
365
|
+
# process(page)
|
|
366
|
+
# end
|
|
367
|
+
#
|
|
368
|
+
# @example With constraints
|
|
369
|
+
# cursor = Song.cursor(artist: "Artist Name", limit: 50)
|
|
370
|
+
# cursor.each { |song| puts song.title }
|
|
371
|
+
#
|
|
372
|
+
# @param constraints [Hash] query constraints to apply
|
|
373
|
+
# @param limit [Integer] number of items per page (default: 100)
|
|
374
|
+
# @param order [Symbol, Parse::Order] the ordering for pagination
|
|
375
|
+
# @return [Parse::Cursor] a cursor for paginating results
|
|
376
|
+
# @see Parse::Cursor
|
|
377
|
+
def cursor(constraints = {}, limit: 100, order: nil)
|
|
378
|
+
query(constraints).cursor(limit: limit, order: order)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Subscribe to real-time updates for objects in this collection.
|
|
382
|
+
# Uses Parse LiveQuery WebSocket connection to receive push notifications
|
|
383
|
+
# when objects are created, updated, deleted, or enter/leave the query results.
|
|
384
|
+
#
|
|
385
|
+
# @example Basic subscription (all objects)
|
|
386
|
+
# subscription = Song.subscribe
|
|
387
|
+
# subscription.on(:create) { |song| puts "New song: #{song.title}" }
|
|
388
|
+
# subscription.on(:update) { |song, original| puts "Updated!" }
|
|
389
|
+
# subscription.on(:delete) { |song| puts "Deleted!" }
|
|
390
|
+
#
|
|
391
|
+
# @example Subscribe with query constraints
|
|
392
|
+
# subscription = Song.subscribe(where: { artist: "Beatles" })
|
|
393
|
+
# subscription.on_create { |song| puts "New Beatles song!" }
|
|
394
|
+
#
|
|
395
|
+
# @example With field filtering
|
|
396
|
+
# subscription = User.subscribe(where: { status: "online" }, fields: ["name", "avatar"])
|
|
397
|
+
# subscription.on_update { |user| puts "User changed: #{user.name}" }
|
|
398
|
+
#
|
|
399
|
+
# @example With session token for ACL-aware subscriptions
|
|
400
|
+
# subscription = PrivateData.subscribe(session_token: current_user.session_token)
|
|
401
|
+
#
|
|
402
|
+
# @param where [Hash] query constraints for the subscription
|
|
403
|
+
# @param fields [Array<String>] specific fields to watch for changes (nil = all fields)
|
|
404
|
+
# @param session_token [String] session token for ACL-aware subscriptions
|
|
405
|
+
# @param client [Parse::LiveQuery::Client] custom LiveQuery client (optional)
|
|
406
|
+
# @return [Parse::LiveQuery::Subscription] the subscription object
|
|
407
|
+
# @see Parse::LiveQuery::Subscription
|
|
408
|
+
# @see Parse::Query#subscribe
|
|
409
|
+
def subscribe(where: {}, fields: nil, session_token: nil, client: nil)
|
|
410
|
+
query(where).subscribe(fields: fields, session_token: session_token, client: client)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Find objects for a given objectId in this collection. The result is a list
|
|
414
|
+
# (or single item) of the objects that were successfully found.
|
|
415
|
+
# By default, bypasses the cache to ensure fresh data from the server.
|
|
416
|
+
# @example
|
|
417
|
+
# Object.find "<objectId>"
|
|
418
|
+
# Object.find "<objectId>", "<objectId>"....
|
|
419
|
+
# Object.find ["<objectId>", "<objectId>"]
|
|
420
|
+
# Object.find "<objectId>", cache: true # opt-in to cache
|
|
421
|
+
# @param parse_ids [String] the objectId to find.
|
|
422
|
+
# @param type [Symbol] the fetching methodology to use if more than one id was passed.
|
|
423
|
+
# - *:parallel* : Utilizes parrallel HTTP requests to fetch all objects requested.
|
|
424
|
+
# - *:batch* : This uses a batch fetch request using a contained_in clause.
|
|
425
|
+
# @param compact [Boolean] whether to remove nil items from the returned array for objects
|
|
426
|
+
# that were not found.
|
|
427
|
+
# @param cache [Boolean, Symbol] caching mode. Defaults to :write_only when Parse.cache_write_on_fetch is true.
|
|
428
|
+
# - :write_only (default) - skip cache read, but update cache with fresh data
|
|
429
|
+
# - true - read from and write to cache
|
|
430
|
+
# - false - completely bypass cache (no read or write)
|
|
431
|
+
# @return [Parse::Object] if only one id was provided as a parameter.
|
|
432
|
+
# @return [Array<Parse::Object>] if more than one id was provided as a parameter.
|
|
433
|
+
def find(*parse_ids, type: :parallel, compact: true, cache: nil)
|
|
434
|
+
# flatten the list of Object ids.
|
|
435
|
+
parse_ids.flatten!
|
|
436
|
+
parse_ids.compact!
|
|
437
|
+
# determines if the result back to the call site is an array or a single result
|
|
438
|
+
as_array = parse_ids.count > 1
|
|
439
|
+
results = []
|
|
440
|
+
|
|
441
|
+
# Default to write-only cache mode - find always gets fresh data
|
|
442
|
+
# but updates cache for future cached reads. Controlled by feature flag.
|
|
443
|
+
if cache.nil?
|
|
444
|
+
cache = Parse.cache_write_on_fetch ? :write_only : false
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Extract cache option for client requests
|
|
448
|
+
client_opts = { cache: cache }
|
|
449
|
+
|
|
450
|
+
if type == :batch
|
|
451
|
+
# use a .in query with the given id as a list
|
|
452
|
+
query = self.class.query(:id.in => parse_ids)
|
|
453
|
+
query.cache = cache
|
|
454
|
+
results = query.results
|
|
455
|
+
else
|
|
456
|
+
# use Parallel to make multiple threaded requests for finding these objects.
|
|
457
|
+
# The benefit of using this as default is that each request goes to a specific URL
|
|
458
|
+
# which is better than Query request (table scan). This in turn allows for caching of
|
|
459
|
+
# individual objects.
|
|
460
|
+
results = parse_ids.threaded_map do |parse_id|
|
|
461
|
+
next nil unless parse_id.present?
|
|
462
|
+
response = client.fetch_object(parse_class, parse_id, **client_opts)
|
|
463
|
+
next nil if response.error?
|
|
464
|
+
Parse::Object.build response.result, parse_class
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
# removes any nil items in the array
|
|
468
|
+
results.compact! if compact
|
|
469
|
+
|
|
470
|
+
as_array ? results : results.first
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
alias_method :get, :find
|
|
474
|
+
|
|
475
|
+
# Find objects with caching enabled. This is a convenience method that calls
|
|
476
|
+
# find with cache: true.
|
|
477
|
+
# @example
|
|
478
|
+
# Object.find_cached "<objectId>"
|
|
479
|
+
# Object.find_cached "<objectId>", "<objectId>"....
|
|
480
|
+
# @param parse_ids [String] the objectId(s) to find.
|
|
481
|
+
# @param type [Symbol] the fetching methodology (:parallel or :batch).
|
|
482
|
+
# @param compact [Boolean] whether to remove nil items from the returned array.
|
|
483
|
+
# @return [Parse::Object] if only one id was provided as a parameter.
|
|
484
|
+
# @return [Array<Parse::Object>] if more than one id was provided as a parameter.
|
|
485
|
+
# @see #find
|
|
486
|
+
def find_cached(*parse_ids, type: :parallel, compact: true)
|
|
487
|
+
find(*parse_ids, type: type, compact: compact, cache: true)
|
|
488
|
+
end
|
|
489
|
+
end # Querying
|
|
490
|
+
end
|
|
491
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "properties"
|
|
5
|
+
|
|
6
|
+
module Parse
|
|
7
|
+
module Core
|
|
8
|
+
# Defines the Schema methods applied to a Parse::Object.
|
|
9
|
+
module Schema
|
|
10
|
+
|
|
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 :polygon, :geo_polygon
|
|
29
|
+
result[:type] = Parse::Model::TYPE_POLYGON
|
|
30
|
+
when :pointer
|
|
31
|
+
result = { type: Parse::Model::TYPE_POINTER, targetClass: references[k] }
|
|
32
|
+
when :acl
|
|
33
|
+
result[:type] = Parse::Model::ACL
|
|
34
|
+
when :timezone, :time_zone
|
|
35
|
+
result[:type] = "String" # no TimeZone native in Parse
|
|
36
|
+
else
|
|
37
|
+
result[:type] = v.to_s.camelize
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
sch[:fields][k] = result
|
|
41
|
+
end
|
|
42
|
+
#then add all the relational column attributes
|
|
43
|
+
relations.each do |k, v|
|
|
44
|
+
sch[:fields][k] = { type: Parse::Model::TYPE_RELATION, targetClass: relations[k] }
|
|
45
|
+
end
|
|
46
|
+
sch
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Update the remote schema for this Parse collection.
|
|
50
|
+
# @param schema_updates [Hash] the changes to be made to the schema.
|
|
51
|
+
# @return [Parse::Response]
|
|
52
|
+
def update_schema(schema_updates = nil)
|
|
53
|
+
schema_updates ||= schema
|
|
54
|
+
client.update_schema parse_class, schema_updates
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Create a new collection for this model with the schema defined by the local
|
|
58
|
+
# model.
|
|
59
|
+
# @return [Parse::Response]
|
|
60
|
+
# @see Schema.schema
|
|
61
|
+
def create_schema
|
|
62
|
+
client.create_schema parse_class, schema
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Fetche the current schema for this collection from Parse server.
|
|
66
|
+
# @return [Parse::Response]
|
|
67
|
+
def fetch_schema
|
|
68
|
+
client.schema parse_class
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# System classes that cannot be created or modified via the schema API.
|
|
72
|
+
# These are managed automatically by Parse Server.
|
|
73
|
+
SCHEMA_READONLY_CLASSES = [
|
|
74
|
+
Parse::Model::CLASS_PUSH_STATUS,
|
|
75
|
+
Parse::Model::CLASS_SCHEMA
|
|
76
|
+
].freeze
|
|
77
|
+
|
|
78
|
+
# Default CLP that grants public access to all operations.
|
|
79
|
+
# Used to reset CLPs before applying new ones.
|
|
80
|
+
DEFAULT_PUBLIC_CLP = {
|
|
81
|
+
"find" => { "*" => true },
|
|
82
|
+
"get" => { "*" => true },
|
|
83
|
+
"count" => { "*" => true },
|
|
84
|
+
"create" => { "*" => true },
|
|
85
|
+
"update" => { "*" => true },
|
|
86
|
+
"delete" => { "*" => true },
|
|
87
|
+
"addField" => { "*" => true }
|
|
88
|
+
}.freeze
|
|
89
|
+
|
|
90
|
+
# Reset the CLP on the server to public defaults.
|
|
91
|
+
# This clears any existing restrictive permissions.
|
|
92
|
+
#
|
|
93
|
+
# @param client [Parse::Client] optional client to use
|
|
94
|
+
# @return [Parse::Response] the response from the server
|
|
95
|
+
#
|
|
96
|
+
# @example Reset CLPs to public
|
|
97
|
+
# Song.reset_clp!
|
|
98
|
+
def reset_clp!(client: nil)
|
|
99
|
+
client ||= self.client
|
|
100
|
+
|
|
101
|
+
unless client.master_key.present?
|
|
102
|
+
warn "[Parse] CLP reset for #{parse_class} requires the master key!"
|
|
103
|
+
return nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
client.update_schema(parse_class, { "classLevelPermissions" => DEFAULT_PUBLIC_CLP })
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# A class method for non-destructive auto upgrading a remote schema based
|
|
110
|
+
# on the properties and relations you have defined in your local model. If
|
|
111
|
+
# the collection doesn't exist, we create the schema. If the collection already
|
|
112
|
+
# exists, the current schema is fetched, and only add the additional fields
|
|
113
|
+
# that are missing.
|
|
114
|
+
#
|
|
115
|
+
# Also updates Class-Level Permissions (CLPs) if defined on the model using
|
|
116
|
+
# the `set_clp` and `protect_fields` DSL methods.
|
|
117
|
+
#
|
|
118
|
+
# @note This feature requires use of the master_key. No columns or fields are removed, this is a safe non-destructive upgrade.
|
|
119
|
+
# @param include_clp [Boolean] whether to also update CLPs (default: true)
|
|
120
|
+
# @return [Parse::Response] if the remote schema was modified.
|
|
121
|
+
# @return [Boolean] if no changes were made to the schema, it returns true.
|
|
122
|
+
def auto_upgrade!(include_clp: true)
|
|
123
|
+
# Skip read-only system classes that Parse Server manages automatically
|
|
124
|
+
if SCHEMA_READONLY_CLASSES.include?(parse_class)
|
|
125
|
+
warn "[Parse] Skipping #{parse_class} - managed automatically by Parse Server"
|
|
126
|
+
return true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
unless client.master_key.present?
|
|
130
|
+
warn "[Parse] Schema changes for #{parse_class} is only available with the master key!"
|
|
131
|
+
return false
|
|
132
|
+
end
|
|
133
|
+
# fetch the current schema (requires master key)
|
|
134
|
+
response = fetch_schema
|
|
135
|
+
|
|
136
|
+
# if it's a core class that doesn't exist, then create the collection without any fields,
|
|
137
|
+
# since parse-server will automatically create the collection with the set of core fields.
|
|
138
|
+
# then fetch the schema again, to add the missing fields.
|
|
139
|
+
if response.error? && self.to_s.start_with?("Parse::") #is it a core class?
|
|
140
|
+
client.create_schema parse_class, {}
|
|
141
|
+
response = fetch_schema
|
|
142
|
+
# if it still wasn't able to be created, raise an error.
|
|
143
|
+
if response.error?
|
|
144
|
+
warn "[Parse] Schema error: unable to create class #{parse_class}"
|
|
145
|
+
return response
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
if response.success?
|
|
150
|
+
#let's figure out the diff fields
|
|
151
|
+
remote_fields = response.result["fields"]
|
|
152
|
+
current_schema = schema
|
|
153
|
+
current_schema[:fields] = current_schema[:fields].reduce({}) do |h, (k, v)|
|
|
154
|
+
#if the field does not exist in Parse, then add it to the update list
|
|
155
|
+
h[k] = v if remote_fields[k.to_s].nil?
|
|
156
|
+
h
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Handle CLP updates if configured and requested
|
|
160
|
+
if include_clp && respond_to?(:class_permissions) && class_permissions.present?
|
|
161
|
+
# First, reset CLPs to public defaults to clear any old restrictive permissions.
|
|
162
|
+
# Parse Server merges CLPs rather than replacing them, so old keys can persist
|
|
163
|
+
# and cause "Permission denied" errors if not explicitly cleared.
|
|
164
|
+
reset_clp!
|
|
165
|
+
|
|
166
|
+
# Now apply the new CLP configuration
|
|
167
|
+
current_schema[:classLevelPermissions] = class_permissions.as_json(include_defaults: true)
|
|
168
|
+
elsif include_clp && _default_class_level_permissions_for_upgrade
|
|
169
|
+
# Opt-in global default: applies only on the initial create path
|
|
170
|
+
# below (existing classes do NOT get rewritten here — we only
|
|
171
|
+
# reach this branch when current_schema[:fields] is non-empty,
|
|
172
|
+
# i.e. there are NEW columns to add; we still avoid mutating
|
|
173
|
+
# existing CLPs to prevent surprising lockouts).
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
return true if current_schema[:fields].empty? && !current_schema[:classLevelPermissions]
|
|
177
|
+
return update_schema(current_schema)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Create new schema (class doesn't exist)
|
|
181
|
+
initial_schema = schema
|
|
182
|
+
# Include CLPs in initial schema creation if configured. Order of
|
|
183
|
+
# precedence:
|
|
184
|
+
# 1. Per-model `class_permissions` (DSL)
|
|
185
|
+
# 2. Global `Parse::Schema.default_class_level_permissions` opt-in
|
|
186
|
+
# 3. None — Parse Server's wide-open defaults apply
|
|
187
|
+
if include_clp && respond_to?(:class_permissions) && class_permissions.present?
|
|
188
|
+
initial_schema[:classLevelPermissions] = class_permissions.as_json(include_defaults: true)
|
|
189
|
+
elsif include_clp && (default_clps = _default_class_level_permissions_for_upgrade)
|
|
190
|
+
initial_schema[:classLevelPermissions] = default_clps
|
|
191
|
+
end
|
|
192
|
+
client.create_schema parse_class, initial_schema
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# @api private
|
|
196
|
+
def _default_class_level_permissions_for_upgrade
|
|
197
|
+
return nil unless defined?(Parse::Schema)
|
|
198
|
+
Parse::Schema.default_class_level_permissions
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|