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,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