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,158 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../pointer"
|
|
5
|
+
require_relative "collection_proxy"
|
|
6
|
+
require_relative "pointer_collection_proxy"
|
|
7
|
+
require_relative "relation_collection_proxy"
|
|
8
|
+
|
|
9
|
+
module Parse
|
|
10
|
+
module Associations
|
|
11
|
+
# The `has_one` creates a one-to-one association with another Parse class.
|
|
12
|
+
# This association says that the other class in the association contains a
|
|
13
|
+
# foreign pointer column which references instances of this class. If your
|
|
14
|
+
# model contains a column that is a Parse pointer to another class, you should
|
|
15
|
+
# use `belongs_to` for that association instead.
|
|
16
|
+
#
|
|
17
|
+
# Defining a `has_one` property generates a helper query method to fetch a
|
|
18
|
+
# particular record from a foreign class. This is useful for setting up the
|
|
19
|
+
# inverse relationship accessors of a `belongs_to`. In the case of the
|
|
20
|
+
# `has_one` relationship, the `:field` option represents the name of the
|
|
21
|
+
# column of the foreign class where the Parse pointer is stored. By default,
|
|
22
|
+
# the lower-first camel case version of the Parse class name is used.
|
|
23
|
+
#
|
|
24
|
+
# In the example below, a `Band` has a local column named `manager` which has
|
|
25
|
+
# a pointer to a `Parse::User` (_:user_) record. This setups up the accessor for `Band`
|
|
26
|
+
# objects to access the band's manager.
|
|
27
|
+
#
|
|
28
|
+
# Since we know there is a column named `manager` in the `Band` class that
|
|
29
|
+
# points to a single `Parse::User`, you can setup the inverse association
|
|
30
|
+
# read accessor in the `Parse::User` class. Note, that to change the
|
|
31
|
+
# association, you need to modify the `manager` property on the band instance
|
|
32
|
+
# since it contains the `belongs_to` property.
|
|
33
|
+
#
|
|
34
|
+
# # every band has a manager
|
|
35
|
+
# class Band < Parse::Object
|
|
36
|
+
# belongs_to :manager, as: :user
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# band = Band.first id: '12345'
|
|
40
|
+
# # the user represented by this manager
|
|
41
|
+
# user = band.manger
|
|
42
|
+
#
|
|
43
|
+
# # every user manages a band
|
|
44
|
+
# class Parse::User
|
|
45
|
+
# # inverse relationship to `Band.belongs_to :manager`
|
|
46
|
+
# has_one :band, field: :manager
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# user = Parse::User.first
|
|
50
|
+
#
|
|
51
|
+
# user.band # similar to performing: Band.first(:manager => user)
|
|
52
|
+
#
|
|
53
|
+
#
|
|
54
|
+
# You may optionally use `has_one` with scopes, in order to fine tune the
|
|
55
|
+
# query result. Using the example above, you can customize the query with
|
|
56
|
+
# a scope that only fetches the association if the band is approved. If
|
|
57
|
+
# the association cannot be fetched, `nil` is returned.
|
|
58
|
+
#
|
|
59
|
+
# # adding to previous example
|
|
60
|
+
# class Band < Parse::Object
|
|
61
|
+
# property :approved, :boolean
|
|
62
|
+
# property :approved_date, :date
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# # every user manages a band
|
|
66
|
+
# class Parse::User
|
|
67
|
+
# has_one :recently_approved, ->{ where(order: :approved_date.desc) }, field: :manager, as: :band
|
|
68
|
+
# has_one :band_by_status, ->(status) { where(approved: status) }, field: :manager, as: :band
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# # gets the band most recently approved
|
|
72
|
+
# user.recently_approved
|
|
73
|
+
# # equivalent: Band.first(manager: user, order: :approved_date.desc)
|
|
74
|
+
#
|
|
75
|
+
# # fetch the managed band that is not approved
|
|
76
|
+
# user.band_by_status(false)
|
|
77
|
+
# # equivalent: Band.first(manager: user, approved: false)
|
|
78
|
+
#
|
|
79
|
+
# @see Parse::Associations::BelongsTo
|
|
80
|
+
# @see Parse::Associations::HasMany
|
|
81
|
+
module HasOne
|
|
82
|
+
|
|
83
|
+
# @!method self.has_one(key, scope = nil, opts = {})
|
|
84
|
+
# Creates a one-to-one association with another Parse model.
|
|
85
|
+
# @param [Symbol] key The singularized version of the foreign class and the name of the
|
|
86
|
+
# *foreign* column in the foreign Parse table where the pointer is stored.
|
|
87
|
+
# @param [Proc] scope A proc that can customize the query by applying
|
|
88
|
+
# additional constraints when fetching the associated record. Works similarly as
|
|
89
|
+
# ActiveModel associations described in section {http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Customizing the Query}
|
|
90
|
+
# @option opts [Symbol] :field override the name of the remote column
|
|
91
|
+
# where the pointer is stored. By default this is inferred as
|
|
92
|
+
# the columnized of the key parameter.
|
|
93
|
+
# @option opts [Symbol] :as override the inferred Parse::Object subclass.
|
|
94
|
+
# By default this is inferred as the singularized camel case version of
|
|
95
|
+
# the key parameter. This option allows you to override the Parse model used
|
|
96
|
+
# to perform the query for the association, while allowing you to have a
|
|
97
|
+
# different accessor name.
|
|
98
|
+
# @option opts [Boolean] scope_only Setting this option to `true`,
|
|
99
|
+
# makes the association fetch based only on the scope provided and does
|
|
100
|
+
# not use the local instance object as a foreign pointer in the query.
|
|
101
|
+
# This allows for cases where another property of the local class, is
|
|
102
|
+
# needed to match the resulting records in the association.
|
|
103
|
+
# @see String#columnize
|
|
104
|
+
# @see Associations::HasMany.has_many
|
|
105
|
+
# @return [Parse::Object] a Parse::Object subclass when using the accessor
|
|
106
|
+
# when fetching the association.
|
|
107
|
+
|
|
108
|
+
# @!visibility private
|
|
109
|
+
def self.included(base)
|
|
110
|
+
base.extend(ClassMethods)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @!visibility private
|
|
114
|
+
module ClassMethods
|
|
115
|
+
|
|
116
|
+
# has one are not property but instance scope methods
|
|
117
|
+
def has_one(key, scope = nil, **opts)
|
|
118
|
+
opts.reverse_merge!({ as: key, field: parse_class.columnize, scope_only: false })
|
|
119
|
+
klassName = opts[:as].to_parse_class
|
|
120
|
+
foreign_field = opts[:field].to_sym
|
|
121
|
+
_ivar = :"@_has_one_#{key}" # reserved for future caching
|
|
122
|
+
|
|
123
|
+
if self.method_defined?(key)
|
|
124
|
+
warn "Creating has_one :#{key} association. Will overwrite existing method #{self}##{key}."
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
define_method(key) do |*args, &block|
|
|
128
|
+
return nil if @id.nil?
|
|
129
|
+
query = Parse::Query.new(klassName, limit: 1)
|
|
130
|
+
query.where(foreign_field => self) unless opts[:scope_only] == true
|
|
131
|
+
|
|
132
|
+
if scope.is_a?(Proc)
|
|
133
|
+
# any method not part of Query, gets delegated to the instance object
|
|
134
|
+
instance = self
|
|
135
|
+
query.define_singleton_method(:method_missing) { |m, *args, &block| instance.send(m, *args, &block) }
|
|
136
|
+
query.define_singleton_method(:i) { instance }
|
|
137
|
+
|
|
138
|
+
if scope.arity.zero?
|
|
139
|
+
query.instance_exec(&scope)
|
|
140
|
+
query.conditions(*args) if args.present?
|
|
141
|
+
else
|
|
142
|
+
query.instance_exec(*args, &scope)
|
|
143
|
+
end
|
|
144
|
+
instance = nil # help clean up ruby gc
|
|
145
|
+
elsif args.present?
|
|
146
|
+
query.conditions(*args)
|
|
147
|
+
end
|
|
148
|
+
# query.define_singleton_method(:method_missing) do |m, *args, &block|
|
|
149
|
+
# self.first.send(m, *args, &block)
|
|
150
|
+
# end
|
|
151
|
+
return query.first if block.nil?
|
|
152
|
+
block.call(query.first)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "active_model"
|
|
5
|
+
require "active_support"
|
|
6
|
+
require "active_support/inflector"
|
|
7
|
+
require "active_support/core_ext/object"
|
|
8
|
+
require_relative "collection_proxy"
|
|
9
|
+
|
|
10
|
+
module Parse
|
|
11
|
+
# A PointerCollectionProxy is a collection proxy that only allows Parse Pointers (Objects)
|
|
12
|
+
# to be part of the collection. This is done by typecasting the collection to a particular
|
|
13
|
+
# Parse class. Ex. An Artist may have several Song objects. Therefore an Artist could have a
|
|
14
|
+
# column :songs, that is an array (collection) of Song (Parse::Object subclass) objects.
|
|
15
|
+
class PointerCollectionProxy < CollectionProxy
|
|
16
|
+
|
|
17
|
+
# @!attribute [rw] collection
|
|
18
|
+
# The internal backing store of the collection.
|
|
19
|
+
# @note If you modify this directly, it is highly recommended that you
|
|
20
|
+
# call {CollectionProxy#notify_will_change!} to notify the dirty tracking system.
|
|
21
|
+
# @return [Array<Parse::Object>]
|
|
22
|
+
# @see CollectionProxy#collection
|
|
23
|
+
def collection=(c)
|
|
24
|
+
notify_will_change!
|
|
25
|
+
@collection = c
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Add Parse::Objects to the collection.
|
|
29
|
+
# @overload add(parse_object)
|
|
30
|
+
# Add a Parse::Object or Parse::Pointer to this collection.
|
|
31
|
+
# @param parse_object [Parse::Object,Parse::Pointer] the object to add
|
|
32
|
+
# @overload add(parse_objects)
|
|
33
|
+
# Add an array of Parse::Objects or Parse::Pointers to this collection.
|
|
34
|
+
# @param parse_objects [Array<Parse::Object,Parse::Pointer>] the array to append.
|
|
35
|
+
# @return [Array<Parse::Object>] the collection
|
|
36
|
+
def add(*items)
|
|
37
|
+
notify_will_change! if items.count > 0
|
|
38
|
+
items.flatten.parse_objects.each do |item|
|
|
39
|
+
collection.push(item) if item.is_a?(Parse::Pointer)
|
|
40
|
+
end
|
|
41
|
+
@collection
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Removes Parse::Objects from the collection.
|
|
45
|
+
# @overload remove(parse_object)
|
|
46
|
+
# Remove a Parse::Object or Parse::Pointer to this collection.
|
|
47
|
+
# @param parse_object [Parse::Object,Parse::Pointer] the object to remove
|
|
48
|
+
# @overload remove(parse_objects)
|
|
49
|
+
# Remove an array of Parse::Objects or Parse::Pointers from this collection.
|
|
50
|
+
# @param parse_objects [Array<Parse::Object,Parse::Pointer>] the array of objects to remove.
|
|
51
|
+
# @return [Array<Parse::Object>] the collection
|
|
52
|
+
def remove(*items)
|
|
53
|
+
notify_will_change! if items.count > 0
|
|
54
|
+
items.flatten.parse_objects.each do |item|
|
|
55
|
+
collection.delete item
|
|
56
|
+
end
|
|
57
|
+
@collection
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Atomically add a set of Parse::Objects to this collection.
|
|
61
|
+
# This is done by making the API request directly with Parse server; the
|
|
62
|
+
# local object is not updated with changes.
|
|
63
|
+
# @see CollectionProxy#add!
|
|
64
|
+
# @see #add_unique!
|
|
65
|
+
def add!(*items)
|
|
66
|
+
super(items.flatten.parse_pointers)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Atomically add a set of Parse::Objects to this collection for those not already
|
|
70
|
+
# in the collection.
|
|
71
|
+
# This is done by making the API request directly with Parse server; the
|
|
72
|
+
# local object is not updated with changes.
|
|
73
|
+
# @see CollectionProxy#add_unique!
|
|
74
|
+
# @see #add!
|
|
75
|
+
def add_unique!(*items)
|
|
76
|
+
super(items.flatten.parse_pointers)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Atomically remove a set of Parse::Objects to this collection.
|
|
80
|
+
# This is done by making the API request directly with Parse server; the
|
|
81
|
+
# local object is not updated with changes.
|
|
82
|
+
# @see CollectionProxy#remove!
|
|
83
|
+
def remove!(*items)
|
|
84
|
+
super(items.flatten.parse_pointers)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Force fetch the set of pointer objects in this collection.
|
|
88
|
+
# @see Array.fetch_objects!
|
|
89
|
+
def fetch!
|
|
90
|
+
collection.fetch_objects!
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Fetch the set of pointer objects in this collection.
|
|
94
|
+
# @see Array.fetch_objects
|
|
95
|
+
def fetch
|
|
96
|
+
collection.fetch_objects
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Encode the collection as JSON.
|
|
100
|
+
# By default, returns Parse::Pointers for backward compatibility when saving.
|
|
101
|
+
# Set `pointers_only: false` to get full hydrated objects for API responses.
|
|
102
|
+
# @param opts [Hash] options for serialization
|
|
103
|
+
# @option opts [Boolean] :pointers_only (true) When true (default), converts all
|
|
104
|
+
# Parse objects to pointer format. Set to false to serialize full objects.
|
|
105
|
+
# @option opts [Boolean] :only_fetched (true) When true (default when pointers_only
|
|
106
|
+
# is false), only serialize fields that were actually fetched. This prevents
|
|
107
|
+
# autofetch from being triggered during serialization of partially hydrated objects.
|
|
108
|
+
# @example Default - pointers for storage
|
|
109
|
+
# capture.assets.as_json
|
|
110
|
+
# # => [{"__type"=>"Pointer", "className"=>"Asset", "objectId"=>"abc"}, ...]
|
|
111
|
+
# @example Full objects for API responses (only fetched fields, no autofetch)
|
|
112
|
+
# capture.assets.as_json(pointers_only: false)
|
|
113
|
+
# # => [{"objectId"=>"abc", "file"=>{...}, "caption"=>"...", ...}, ...]
|
|
114
|
+
def as_json(opts = nil)
|
|
115
|
+
opts ||= {}
|
|
116
|
+
|
|
117
|
+
# Normalize string keys to symbols to avoid conflicts with defaults
|
|
118
|
+
opts = opts.transform_keys { |k| k.is_a?(String) ? k.to_sym : k }
|
|
119
|
+
|
|
120
|
+
# Check if pointers_only was explicitly set, otherwise default to true
|
|
121
|
+
pointers_only = opts.fetch(:pointers_only, true)
|
|
122
|
+
|
|
123
|
+
# Default to pointers_only: true for backward compatibility
|
|
124
|
+
# When pointers_only is false, default only_fetched to true to prevent
|
|
125
|
+
# autofetch during serialization of partially hydrated objects
|
|
126
|
+
defaults = { pointers_only: true }
|
|
127
|
+
unless pointers_only
|
|
128
|
+
defaults[:only_fetched] = true unless opts.key?(:only_fetched)
|
|
129
|
+
end
|
|
130
|
+
opts = defaults.merge(opts)
|
|
131
|
+
super(opts)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "active_support"
|
|
5
|
+
require "active_support/inflector"
|
|
6
|
+
require "active_support/core_ext/object"
|
|
7
|
+
require_relative "pointer_collection_proxy"
|
|
8
|
+
|
|
9
|
+
module Parse
|
|
10
|
+
# The RelationCollectionProxy is similar to a PointerCollectionProxy except that
|
|
11
|
+
# there is no actual "array" object in Parse. Parse treats relation through an
|
|
12
|
+
# intermediary table (a.k.a. join table). Whenever a developer wants the
|
|
13
|
+
# contents of a collection, the foreign table needs to be queried instead.
|
|
14
|
+
# In this scenario, the parse_class: initializer argument should be passed in order to
|
|
15
|
+
# know which remote table needs to be queried in order to fetch the items of the collection.
|
|
16
|
+
#
|
|
17
|
+
# Unlike managing an array of Pointers, relations in Parse are done throug atomic operations,
|
|
18
|
+
# which have a specific API. The design of this proxy is to maintain two sets of lists,
|
|
19
|
+
# items to be added to the relation and a separate list of items to be removed from the
|
|
20
|
+
# relation.
|
|
21
|
+
#
|
|
22
|
+
# Because this relationship is based on queryable Parse table, we are also able to
|
|
23
|
+
# not just get all the items in a collection, but also provide additional constraints to
|
|
24
|
+
# get matching items within the relation collection.
|
|
25
|
+
#
|
|
26
|
+
# When creating a Relation proxy, all the delegate methods defined in the superclasses
|
|
27
|
+
# need to be implemented, in addition to a few others with the key parameter:
|
|
28
|
+
# _relation_query and _commit_relation_updates . :'key'_relation_query should return a
|
|
29
|
+
# Parse::Query object that is properly tied to the foreign table class related to this object column.
|
|
30
|
+
# Example, if an Artist has many Song objects, then the query to be returned by this method
|
|
31
|
+
# should be a Parse::Query for the class 'Song'.
|
|
32
|
+
# Because relation changes are separate from object changes, you can call save on a
|
|
33
|
+
# relation collection to save the current add and remove operations. Because the delegate needs
|
|
34
|
+
# to be informed of the changes being committed, it will be notified
|
|
35
|
+
# through :'key'_commit_relation_updates message. The delegate is also in charge of
|
|
36
|
+
# clearing out the change information for the collection if saved successfully.
|
|
37
|
+
# @see PointerCollectionProxy
|
|
38
|
+
class RelationCollectionProxy < PointerCollectionProxy
|
|
39
|
+
define_attribute_methods :additions, :removals
|
|
40
|
+
# @!attribute [r] removals
|
|
41
|
+
# The objects that have been newly removed to this collection
|
|
42
|
+
# @return [Array<Parse::Object>]
|
|
43
|
+
# @!attribute [r] additions
|
|
44
|
+
# The objects that have been newly added to this collection
|
|
45
|
+
# @return [Array<Parse::Object>]
|
|
46
|
+
attr_reader :additions, :removals
|
|
47
|
+
|
|
48
|
+
def initialize(collection = nil, delegate: nil, key: nil, parse_class: nil)
|
|
49
|
+
super
|
|
50
|
+
@additions = []
|
|
51
|
+
@removals = []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# You can get items within the collection relation filtered by a specific set
|
|
55
|
+
# of query constraints.
|
|
56
|
+
def all(constraints = {}, &block)
|
|
57
|
+
q = query({ limit: :max }.merge(constraints))
|
|
58
|
+
if block_given?
|
|
59
|
+
# if we have a query, then use the Proc with it (more efficient)
|
|
60
|
+
return q.present? ? q.results(&block) : collection.each(&block)
|
|
61
|
+
end
|
|
62
|
+
# if no block given, get all the results
|
|
63
|
+
q.present? ? q.results : collection
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Ask the delegate to return a query for this collection type
|
|
67
|
+
def query(constraints = {})
|
|
68
|
+
q = forward :"#{@key}_relation_query"
|
|
69
|
+
|
|
70
|
+
# Apply constraints if provided (excluding limit which is handled differently)
|
|
71
|
+
query_constraints = constraints.except(:limit)
|
|
72
|
+
if query_constraints.present?
|
|
73
|
+
q = q.where(query_constraints)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Apply limit if specified
|
|
77
|
+
if constraints[:limit].present?
|
|
78
|
+
q = q.limit(constraints[:limit])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
q
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Return a query with limit applied - allows chaining like relation.limit(5).all
|
|
85
|
+
def limit(count)
|
|
86
|
+
query(limit: count)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Add Parse::Objects to the relation.
|
|
90
|
+
# @overload add(parse_object)
|
|
91
|
+
# Add a Parse::Object or Parse::Pointer to this relation.
|
|
92
|
+
# @param parse_object [Parse::Object,Parse::Pointer] the object to add
|
|
93
|
+
# @overload add(parse_objects)
|
|
94
|
+
# Add an array of Parse::Objects or Parse::Pointers to this relation.
|
|
95
|
+
# @param parse_objects [Array<Parse::Object,Parse::Pointer>] the array to append.
|
|
96
|
+
# @return [Array<Parse::Object>] the collection
|
|
97
|
+
def add(*items)
|
|
98
|
+
items = items.flatten.parse_objects
|
|
99
|
+
return @collection if items.empty?
|
|
100
|
+
|
|
101
|
+
notify_will_change!
|
|
102
|
+
additions_will_change!
|
|
103
|
+
removals_will_change!
|
|
104
|
+
# take all the items
|
|
105
|
+
items.each do |item|
|
|
106
|
+
@additions.push item
|
|
107
|
+
@collection.push item
|
|
108
|
+
#cleanup
|
|
109
|
+
@removals.delete item
|
|
110
|
+
end
|
|
111
|
+
@collection
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Removes Parse::Objects from the relation.
|
|
115
|
+
# @overload remove(parse_object)
|
|
116
|
+
# Remove a Parse::Object or Parse::Pointer to this relation.
|
|
117
|
+
# @param parse_object [Parse::Object,Parse::Pointer] the object to remove
|
|
118
|
+
# @overload remove(parse_objects)
|
|
119
|
+
# Remove an array of Parse::Objects or Parse::Pointers from this relation.
|
|
120
|
+
# @param parse_objects [Array<Parse::Object,Parse::Pointer>] the array of objects to remove.
|
|
121
|
+
# @return [Array<Parse::Object>] the collection
|
|
122
|
+
def remove(*items)
|
|
123
|
+
items = items.flatten.parse_objects
|
|
124
|
+
return @collection if items.empty?
|
|
125
|
+
notify_will_change!
|
|
126
|
+
additions_will_change!
|
|
127
|
+
removals_will_change!
|
|
128
|
+
items.each do |item|
|
|
129
|
+
@removals.push item
|
|
130
|
+
@collection.delete item
|
|
131
|
+
# remove it from any add operations
|
|
132
|
+
@additions.delete item
|
|
133
|
+
end
|
|
134
|
+
@collection
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Atomically add a set of Parse::Objects to this relation.
|
|
138
|
+
# This is done by making the API request directly with Parse server; the
|
|
139
|
+
# local object is not updated with changes.
|
|
140
|
+
def add!(*items)
|
|
141
|
+
return false unless @delegate.respond_to?(:op_add_relation!)
|
|
142
|
+
items = items.flatten.parse_pointers
|
|
143
|
+
@delegate.send :op_add_relation!, @key, items
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Atomically add a set of Parse::Objects to this relation.
|
|
147
|
+
# This is done by making the API request directly with Parse server; the
|
|
148
|
+
# local object is not updated with changes.
|
|
149
|
+
def add_unique!(*items)
|
|
150
|
+
return false unless @delegate.respond_to?(:op_add_relation!)
|
|
151
|
+
items = items.flatten.parse_pointers
|
|
152
|
+
@delegate.send :op_add_relation!, @key, items
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Atomically remove a set of Parse::Objects to this relation.
|
|
156
|
+
# This is done by making the API request directly with Parse server; the
|
|
157
|
+
# local object is not updated with changes.
|
|
158
|
+
def remove!(*items)
|
|
159
|
+
return false unless @delegate.respond_to?(:op_remove_relation!)
|
|
160
|
+
items = items.flatten.parse_pointers
|
|
161
|
+
@delegate.send :op_remove_relation!, @key, items
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Save the changes to the relation
|
|
165
|
+
def save
|
|
166
|
+
unless @removals.empty? && @additions.empty?
|
|
167
|
+
forward :"#{@key}_commit_relation_updates"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @see #add
|
|
172
|
+
def <<(*list)
|
|
173
|
+
list.each { |d| add(d) }
|
|
174
|
+
@collection
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "active_support"
|
|
5
|
+
require "active_support/core_ext/object"
|
|
6
|
+
require_relative "model"
|
|
7
|
+
require "base64"
|
|
8
|
+
|
|
9
|
+
module Parse
|
|
10
|
+
|
|
11
|
+
# Support for the Bytes type in Parse
|
|
12
|
+
class Bytes < Model
|
|
13
|
+
# The default attributes in a Parse Bytes hash.
|
|
14
|
+
ATTRIBUTES = { __type: :string, base64: :string }.freeze
|
|
15
|
+
# @return [String] the base64 string representing the content
|
|
16
|
+
attr_accessor :base64
|
|
17
|
+
# @return [TYPE_BYTES]
|
|
18
|
+
def self.parse_class; TYPE_BYTES; end
|
|
19
|
+
# @return [TYPE_BYTES]
|
|
20
|
+
def parse_class; self.class.parse_class; end
|
|
21
|
+
|
|
22
|
+
alias_method :__type, :parse_class
|
|
23
|
+
|
|
24
|
+
# initialize with a base64 string or a Bytes object
|
|
25
|
+
# @param bytes [String] The content as base64 string.
|
|
26
|
+
def initialize(bytes = "")
|
|
27
|
+
@base64 = (bytes.is_a?(Bytes) ? bytes.base64 : bytes).dup
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @!attribute attributes
|
|
31
|
+
# Supports for mass assignment of values and encoding to JSON.
|
|
32
|
+
# @return [ATTRIBUTES]
|
|
33
|
+
def attributes
|
|
34
|
+
ATTRIBUTES
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Base64 encode and set the instance contents
|
|
38
|
+
# @param str the string to encode
|
|
39
|
+
def encode(str)
|
|
40
|
+
@base64 = Base64.encode64(str)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the content as decoded base64 bytes
|
|
44
|
+
def decoded
|
|
45
|
+
Base64.decode64(@base64 || "")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def attributes=(a)
|
|
49
|
+
if a.is_a?(String)
|
|
50
|
+
@bytes = a
|
|
51
|
+
elsif a.is_a?(Hash)
|
|
52
|
+
@bytes = a["base64"] || @bytes
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Two Parse::Bytes objects are equal if they have the same base64 signature
|
|
57
|
+
def ==(u)
|
|
58
|
+
return false unless u.is_a?(self.class)
|
|
59
|
+
@base64 == u.base64
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|