parse-stack-next 5.6.0 → 5.7.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 +4 -4
- data/CHANGELOG.md +409 -0
- data/README.md +378 -6
- data/docs/caching.md +748 -0
- data/examples/basic_client.rb +3 -3
- data/examples/basic_server.rb +3 -3
- data/examples/live_query_listener.rb +12 -12
- data/examples/rag_chatbot.rb +14 -14
- data/examples/transaction_example.rb +44 -45
- data/examples/webhook_server.rb +3 -3
- data/lib/parse/access.rb +493 -0
- data/lib/parse/acl_scope.rb +125 -52
- data/lib/parse/agent/approval_gate.rb +0 -0
- data/lib/parse/agent/cancellation_token.rb +3 -3
- data/lib/parse/agent/constraint_translator.rb +4 -4
- data/lib/parse/agent/describe.rb +34 -34
- data/lib/parse/agent/errors.rb +9 -9
- data/lib/parse/agent/mcp_client.rb +61 -59
- data/lib/parse/agent/mcp_dispatcher.rb +89 -101
- data/lib/parse/agent/mcp_rack_app.rb +92 -93
- data/lib/parse/agent/mcp_server.rb +1 -1
- data/lib/parse/agent/mcp_subscriptions.rb +22 -22
- data/lib/parse/agent/metadata_audit.rb +1 -2
- data/lib/parse/agent/metadata_dsl.rb +7 -7
- data/lib/parse/agent/metadata_registry.rb +12 -12
- data/lib/parse/agent/prompt_hardening.rb +4 -4
- data/lib/parse/agent/prompts.rb +10 -10
- data/lib/parse/agent/result_formatter.rb +4 -4
- data/lib/parse/agent/tools.rb +493 -400
- data/lib/parse/agent.rb +157 -116
- data/lib/parse/api/hooks.rb +1 -1
- data/lib/parse/api/server.rb +2 -2
- data/lib/parse/api/users.rb +2 -2
- data/lib/parse/atlas_search/index_manager.rb +1 -1
- data/lib/parse/atlas_search/session.rb +40 -218
- data/lib/parse/atlas_search.rb +181 -60
- data/lib/parse/authorization.rb +466 -0
- data/lib/parse/cache/invalidation.rb +167 -0
- data/lib/parse/cache/keyspace.rb +306 -0
- data/lib/parse/cache/moneta_surface.rb +126 -0
- data/lib/parse/cache/pool.rb +48 -5
- data/lib/parse/cache/redis.rb +425 -10
- data/lib/parse/cache/scoped_view.rb +443 -0
- data/lib/parse/cache/sub_cache.rb +264 -0
- data/lib/parse/cache/upstream_roles.rb +230 -0
- data/lib/parse/client/authentication.rb +1 -1
- data/lib/parse/client/body_builder.rb +1 -2
- data/lib/parse/client/caching.rb +84 -5
- data/lib/parse/client.rb +327 -189
- data/lib/parse/clp_scope.rb +225 -28
- data/lib/parse/console.rb +3 -3
- data/lib/parse/embeddings/batch_embedder.rb +3 -3
- data/lib/parse/embeddings/cache.rb +17 -17
- data/lib/parse/embeddings/cohere.rb +21 -22
- data/lib/parse/embeddings/image_fetch.rb +22 -21
- data/lib/parse/embeddings/jina.rb +21 -21
- data/lib/parse/embeddings/local_http.rb +6 -7
- data/lib/parse/embeddings/media_file.rb +5 -4
- data/lib/parse/embeddings/openai.rb +5 -5
- data/lib/parse/embeddings/provider.rb +1 -1
- data/lib/parse/embeddings/qwen.rb +10 -10
- data/lib/parse/embeddings/spend_cap.rb +2 -3
- data/lib/parse/embeddings/streaming_body.rb +11 -11
- data/lib/parse/embeddings/video_source.rb +7 -6
- data/lib/parse/embeddings/voyage.rb +59 -61
- data/lib/parse/embeddings.rb +28 -27
- data/lib/parse/graphql/type_generator.rb +1 -1
- data/lib/parse/graphql.rb +5 -5
- data/lib/parse/live_query/client.rb +1 -1
- data/lib/parse/live_query.rb +1 -1
- data/lib/parse/lock.rb +15 -18
- data/lib/parse/lock_backend.rb +1 -1
- data/lib/parse/lookup_rewriter.rb +6 -6
- data/lib/parse/model/classes/role.rb +227 -28
- data/lib/parse/model/classes/user.rb +60 -1
- data/lib/parse/model/clp.rb +4 -4
- data/lib/parse/model/core/actions.rb +97 -15
- data/lib/parse/model/core/create_lock.rb +0 -2
- data/lib/parse/model/core/describe.rb +57 -57
- data/lib/parse/model/core/embed_managed.rb +21 -23
- data/lib/parse/model/core/fetching.rb +2 -0
- data/lib/parse/model/core/indexing.rb +14 -14
- data/lib/parse/model/core/parse_reference.rb +1 -1
- data/lib/parse/model/core/properties.rb +2 -2
- data/lib/parse/model/core/querying.rb +1 -1
- data/lib/parse/model/core/schema.rb +2 -2
- data/lib/parse/model/core/search_indexing.rb +2 -2
- data/lib/parse/model/core/vector_searchable.rb +4 -5
- data/lib/parse/model/file.rb +21 -15
- data/lib/parse/model/geojson.rb +2 -2
- data/lib/parse/model/geopoint.rb +1 -0
- data/lib/parse/model/object.rb +132 -66
- data/lib/parse/model/pointer.rb +2 -0
- data/lib/parse/model/polygon.rb +3 -6
- data/lib/parse/model/push.rb +2 -2
- data/lib/parse/model/vector.rb +3 -1
- data/lib/parse/mongodb.rb +512 -282
- data/lib/parse/pipeline_security.rb +3 -0
- data/lib/parse/query/constraints.rb +46 -46
- data/lib/parse/query.rb +146 -59
- data/lib/parse/retrieval/agent_tool.rb +34 -20
- data/lib/parse/retrieval/chunk.rb +1 -0
- data/lib/parse/retrieval/reranker/cohere.rb +11 -11
- data/lib/parse/retrieval/reranker.rb +3 -4
- data/lib/parse/retrieval/retriever.rb +4 -6
- data/lib/parse/schema/index_migrator.rb +26 -26
- data/lib/parse/schema/search_index_migrator.rb +19 -19
- data/lib/parse/stack/tasks.rb +6 -6
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +10 -10
- data/lib/parse/vector_search/hybrid.rb +66 -41
- data/lib/parse/vector_search.rb +14 -11
- data/lib/parse/webhooks/payload.rb +1 -0
- data/lib/parse/webhooks/registration.rb +4 -4
- data/lib/parse/webhooks/trigger_audit.rb +50 -49
- data/lib/parse/webhooks.rb +22 -4
- data/parse-stack-next.gemspec +6 -6
- metadata +10 -1
data/examples/basic_client.rb
CHANGED
|
@@ -31,10 +31,10 @@ require "parse-stack-next"
|
|
|
31
31
|
# ---------------------------------------------------------------------------
|
|
32
32
|
Parse.setup(
|
|
33
33
|
server_url: ENV.fetch("PARSE_SERVER_URL", "http://localhost:1337/parse"),
|
|
34
|
-
app_id:
|
|
35
|
-
api_key:
|
|
34
|
+
app_id: ENV.fetch("PARSE_APP_ID"),
|
|
35
|
+
api_key: ENV.fetch("PARSE_REST_KEY"),
|
|
36
36
|
master_key: nil, # explicit: never set this from env in client builds
|
|
37
|
-
logging:
|
|
37
|
+
logging: false,
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
# Belt-and-suspenders: prove the master key really is absent.
|
data/examples/basic_server.rb
CHANGED
|
@@ -22,8 +22,8 @@ require "parse-stack-next"
|
|
|
22
22
|
# ---------------------------------------------------------------------------
|
|
23
23
|
Parse.setup(
|
|
24
24
|
server_url: ENV.fetch("PARSE_SERVER_URL", "http://localhost:1337/parse"),
|
|
25
|
-
app_id:
|
|
26
|
-
api_key:
|
|
25
|
+
app_id: ENV.fetch("PARSE_APP_ID"),
|
|
26
|
+
api_key: ENV.fetch("PARSE_REST_KEY"),
|
|
27
27
|
master_key: ENV.fetch("PARSE_MASTER_KEY"),
|
|
28
28
|
)
|
|
29
29
|
|
|
@@ -98,7 +98,7 @@ popular = Song.query(:plays.gt => 1_500)
|
|
|
98
98
|
.order(:plays.desc)
|
|
99
99
|
.limit(10)
|
|
100
100
|
.results
|
|
101
|
-
puts "Popular songs: #{popular.map(&:title).join(
|
|
101
|
+
puts "Popular songs: #{popular.map(&:title).join(", ")}"
|
|
102
102
|
|
|
103
103
|
puts "Total songs by #{artist.name}: #{Song.count(artist: artist)}"
|
|
104
104
|
|
|
@@ -32,17 +32,17 @@ require "parse/live_query"
|
|
|
32
32
|
# ---------------------------------------------------------------------------
|
|
33
33
|
Parse.setup(
|
|
34
34
|
server_url: ENV.fetch("PARSE_SERVER_URL", "http://localhost:1337/parse"),
|
|
35
|
-
app_id:
|
|
36
|
-
api_key:
|
|
35
|
+
app_id: ENV.fetch("PARSE_APP_ID"),
|
|
36
|
+
api_key: ENV.fetch("PARSE_REST_KEY"),
|
|
37
37
|
master_key: nil, # a plain client — the session token does the scoping
|
|
38
|
-
logging:
|
|
38
|
+
logging: false,
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
Parse.live_query_enabled = true
|
|
42
42
|
Parse::LiveQuery.configure do |config|
|
|
43
|
-
config.url
|
|
43
|
+
config.url = ENV.fetch("PARSE_LIVE_QUERY_URL", "ws://localhost:1337/parse")
|
|
44
44
|
config.application_id = ENV.fetch("PARSE_APP_ID")
|
|
45
|
-
config.client_key
|
|
45
|
+
config.client_key = ENV.fetch("PARSE_REST_KEY")
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
class Post < Parse::Object
|
|
@@ -73,13 +73,13 @@ subscription = Post.subscribe(
|
|
|
73
73
|
session_token: user.session_token,
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
-
subscription.on(:subscribe)
|
|
77
|
-
subscription.on(:create)
|
|
78
|
-
subscription.on(:update)
|
|
79
|
-
subscription.on(:delete)
|
|
80
|
-
subscription.on(:enter)
|
|
81
|
-
subscription.on(:leave)
|
|
82
|
-
subscription.on(:error)
|
|
76
|
+
subscription.on(:subscribe) { puts "[#{stamp}] subscribed — waiting for events…" }
|
|
77
|
+
subscription.on(:create) { |post| puts "[#{stamp}] CREATE #{post.id} #{post.title.inspect}" }
|
|
78
|
+
subscription.on(:update) { |post, _orig| puts "[#{stamp}] UPDATE #{post.id} #{post.title.inspect}" }
|
|
79
|
+
subscription.on(:delete) { |post| puts "[#{stamp}] DELETE #{post.id}" }
|
|
80
|
+
subscription.on(:enter) { |post, _orig| puts "[#{stamp}] ENTER #{post.id} (now matches query)" }
|
|
81
|
+
subscription.on(:leave) { |post, _orig| puts "[#{stamp}] LEAVE #{post.id} (no longer matches)" }
|
|
82
|
+
subscription.on(:error) { |err| warn "[#{stamp}] ERROR #{err}" }
|
|
83
83
|
|
|
84
84
|
# ---------------------------------------------------------------------------
|
|
85
85
|
# 4. Block and print until Ctrl-C
|
data/examples/rag_chatbot.rb
CHANGED
|
@@ -43,8 +43,8 @@ Parse::Embeddings.register(
|
|
|
43
43
|
|
|
44
44
|
Parse.setup(
|
|
45
45
|
server_url: ENV.fetch("PARSE_SERVER_URL", "http://localhost:1337/parse"),
|
|
46
|
-
app_id:
|
|
47
|
-
api_key:
|
|
46
|
+
app_id: ENV.fetch("PARSE_APP_ID"),
|
|
47
|
+
api_key: ENV.fetch("PARSE_REST_KEY"),
|
|
48
48
|
master_key: ENV.fetch("PARSE_MASTER_KEY"),
|
|
49
49
|
)
|
|
50
50
|
|
|
@@ -76,7 +76,7 @@ class KnowledgeArticle < Parse::Object
|
|
|
76
76
|
# Declare the Atlas $vectorSearch index that retrieval needs.
|
|
77
77
|
mongo_search_index "knowledge_embedding",
|
|
78
78
|
{ fields: [{ type: "vector", path: "embedding",
|
|
79
|
-
|
|
79
|
+
numDimensions: 1536, similarity: "cosine" }] },
|
|
80
80
|
type: "vectorSearch"
|
|
81
81
|
end
|
|
82
82
|
|
|
@@ -101,26 +101,26 @@ module ChatAnswerer
|
|
|
101
101
|
# --- OpenAI backend ---
|
|
102
102
|
def openai(question, chunks, model: "gpt-4o-mini")
|
|
103
103
|
post("https://api.openai.com/v1/chat/completions",
|
|
104
|
-
{ "Authorization" => "Bearer #{ENV.fetch(
|
|
104
|
+
{ "Authorization" => "Bearer #{ENV.fetch("OPENAI_API_KEY")}" },
|
|
105
105
|
{ model: model,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
messages: [
|
|
107
|
+
{ role: "system", content: PROMPT },
|
|
108
|
+
{ role: "user",
|
|
109
|
+
content: "Context:\n#{context(chunks)}\n\nQuestion: #{question}" },
|
|
110
|
+
] })
|
|
111
111
|
.dig("choices", 0, "message", "content")
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
# --- Anthropic backend ---
|
|
115
115
|
def anthropic(question, chunks, model: "claude-opus-4-8")
|
|
116
116
|
post("https://api.anthropic.com/v1/messages",
|
|
117
|
-
{ "x-api-key"
|
|
117
|
+
{ "x-api-key" => ENV.fetch("ANTHROPIC_API_KEY"),
|
|
118
118
|
"anthropic-version" => "2023-06-01" },
|
|
119
119
|
{ model: model, max_tokens: 1024, system: PROMPT,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
messages: [
|
|
121
|
+
{ role: "user",
|
|
122
|
+
content: "Context:\n#{context(chunks)}\n\nQuestion: #{question}" },
|
|
123
|
+
] })
|
|
124
124
|
.dig("content", 0, "text")
|
|
125
125
|
end
|
|
126
126
|
|
|
@@ -4,33 +4,33 @@
|
|
|
4
4
|
# This example demonstrates how to use transactions to ensure atomic operations
|
|
5
5
|
# across multiple Parse objects.
|
|
6
6
|
|
|
7
|
-
require
|
|
7
|
+
require "parse/stack"
|
|
8
8
|
|
|
9
9
|
# Configure your Parse application
|
|
10
10
|
Parse.setup(
|
|
11
|
-
app_id: ENV[
|
|
12
|
-
api_key: ENV[
|
|
13
|
-
server_url: ENV[
|
|
11
|
+
app_id: ENV["PARSE_APP_ID"] || "your-app-id",
|
|
12
|
+
api_key: ENV["PARSE_API_KEY"] || "your-api-key",
|
|
13
|
+
server_url: ENV["PARSE_SERVER_URL"] || "http://localhost:1337/parse",
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
# Define example models
|
|
17
17
|
class Workspace < Parse::Object
|
|
18
18
|
property :name
|
|
19
|
-
property :owner, :pointer, class_name:
|
|
19
|
+
property :owner, :pointer, class_name: "User"
|
|
20
20
|
property :member_count, :integer, default: 0
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
class Subscription < Parse::Object
|
|
24
|
-
property :user, :pointer, class_name:
|
|
25
|
-
property :workspace, :pointer, class_name:
|
|
24
|
+
property :user, :pointer, class_name: "User"
|
|
25
|
+
property :workspace, :pointer, class_name: "Workspace"
|
|
26
26
|
property :access_level, :string
|
|
27
27
|
property :grant, :string
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
class Project < Parse::Object
|
|
31
31
|
property :name
|
|
32
|
-
property :workspace, :pointer, class_name:
|
|
33
|
-
property :owner, :pointer, class_name:
|
|
32
|
+
property :workspace, :pointer, class_name: "Workspace"
|
|
33
|
+
property :owner, :pointer, class_name: "User"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Example 1: Basic transaction with explicit batch operations
|
|
@@ -38,45 +38,45 @@ def transfer_project_ownership_basic(project, new_owner)
|
|
|
38
38
|
Parse::Object.transaction do |batch|
|
|
39
39
|
# Get old owner
|
|
40
40
|
old_owner = project.owner
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
# Find or create new owner subscription
|
|
43
43
|
new_owner_membership = Subscription.first(
|
|
44
44
|
project: project,
|
|
45
|
-
user: new_owner
|
|
45
|
+
user: new_owner,
|
|
46
46
|
)
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
if new_owner_membership.nil?
|
|
49
49
|
new_owner_membership = Subscription.new(
|
|
50
50
|
project: project,
|
|
51
51
|
workspace: project.workspace,
|
|
52
52
|
user: new_owner,
|
|
53
|
-
grant:
|
|
54
|
-
access_level:
|
|
53
|
+
grant: "project",
|
|
54
|
+
access_level: "owner",
|
|
55
55
|
)
|
|
56
56
|
batch.add(new_owner_membership)
|
|
57
57
|
else
|
|
58
|
-
new_owner_membership.access_level =
|
|
58
|
+
new_owner_membership.access_level = "owner"
|
|
59
59
|
batch.add(new_owner_membership)
|
|
60
60
|
end
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
# Demote old owner if they have a subscription
|
|
63
63
|
if old_owner.present?
|
|
64
64
|
old_owner_membership = Subscription.first(
|
|
65
65
|
project: project,
|
|
66
|
-
user: old_owner
|
|
66
|
+
user: old_owner,
|
|
67
67
|
)
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
if old_owner_membership.present?
|
|
70
|
-
old_owner_membership.access_level =
|
|
70
|
+
old_owner_membership.access_level = "admin"
|
|
71
71
|
batch.add(old_owner_membership)
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
# Update project owner
|
|
76
76
|
project.owner = new_owner
|
|
77
77
|
batch.add(project)
|
|
78
78
|
end
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
puts "Successfully transferred ownership"
|
|
81
81
|
rescue Parse::Error => e
|
|
82
82
|
puts "Transaction failed: #{e.message}"
|
|
@@ -88,42 +88,42 @@ def transfer_project_ownership_auto(project, new_owner)
|
|
|
88
88
|
results = Parse::Object.transaction do
|
|
89
89
|
old_owner = project.owner
|
|
90
90
|
objects_to_save = []
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
# Find or create new owner subscription
|
|
93
93
|
new_owner_membership = Subscription.first(
|
|
94
94
|
project: project,
|
|
95
|
-
user: new_owner
|
|
95
|
+
user: new_owner,
|
|
96
96
|
) || Subscription.new(
|
|
97
97
|
project: project,
|
|
98
98
|
workspace: project.workspace,
|
|
99
99
|
user: new_owner,
|
|
100
|
-
grant:
|
|
100
|
+
grant: "project",
|
|
101
101
|
)
|
|
102
|
-
|
|
103
|
-
new_owner_membership.access_level =
|
|
102
|
+
|
|
103
|
+
new_owner_membership.access_level = "owner"
|
|
104
104
|
objects_to_save << new_owner_membership
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
# Demote old owner
|
|
107
107
|
if old_owner.present?
|
|
108
108
|
old_owner_membership = Subscription.first(
|
|
109
109
|
project: project,
|
|
110
|
-
user: old_owner
|
|
110
|
+
user: old_owner,
|
|
111
111
|
)
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
if old_owner_membership.present?
|
|
114
|
-
old_owner_membership.access_level =
|
|
114
|
+
old_owner_membership.access_level = "admin"
|
|
115
115
|
objects_to_save << old_owner_membership
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
# Update project
|
|
120
120
|
project.owner = new_owner
|
|
121
121
|
objects_to_save << project
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
# Return array of objects to be saved in transaction
|
|
124
124
|
objects_to_save
|
|
125
125
|
end
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
puts "Transaction completed with #{results.count} operations"
|
|
128
128
|
true
|
|
129
129
|
rescue Parse::Error => e
|
|
@@ -138,32 +138,32 @@ def complex_team_operation(workspace, new_members, new_owner)
|
|
|
138
138
|
unless new_members.include?(new_owner)
|
|
139
139
|
raise Parse::Error, "New owner must be in members list"
|
|
140
140
|
end
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
# Update workspace
|
|
143
143
|
workspace.owner = new_owner
|
|
144
144
|
workspace.member_count = new_members.count
|
|
145
145
|
batch.add(workspace)
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
# Create subscriptions for all new members
|
|
148
148
|
new_members.each do |member|
|
|
149
149
|
subscription = Subscription.new(
|
|
150
150
|
workspace: workspace,
|
|
151
151
|
user: member,
|
|
152
|
-
grant:
|
|
153
|
-
access_level: member == new_owner ?
|
|
152
|
+
grant: "workspace",
|
|
153
|
+
access_level: member == new_owner ? "owner" : "member",
|
|
154
154
|
)
|
|
155
155
|
batch.add(subscription)
|
|
156
156
|
end
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
# Create a project for the workspace
|
|
159
159
|
project = Project.new(
|
|
160
160
|
name: "#{workspace.name} Project",
|
|
161
161
|
workspace: workspace,
|
|
162
|
-
owner: new_owner
|
|
162
|
+
owner: new_owner,
|
|
163
163
|
)
|
|
164
164
|
batch.add(project)
|
|
165
165
|
end
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
puts "Complex operation completed successfully"
|
|
168
168
|
rescue Parse::Error => e
|
|
169
169
|
puts "Complex operation failed: #{e.message}"
|
|
@@ -191,16 +191,16 @@ end
|
|
|
191
191
|
if __FILE__ == $0
|
|
192
192
|
puts "Parse Transaction Examples"
|
|
193
193
|
puts "========================="
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
begin
|
|
196
196
|
# Example usage (requires actual Parse server and data)
|
|
197
197
|
# project = Project.first
|
|
198
198
|
# new_owner = Parse::User.first(username: "new_owner")
|
|
199
|
-
#
|
|
199
|
+
#
|
|
200
200
|
# if project && new_owner
|
|
201
201
|
# transfer_project_ownership_basic(project, new_owner)
|
|
202
202
|
# end
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
puts "\nTransaction support has been added to parse-stack!"
|
|
205
205
|
puts "\nKey features:"
|
|
206
206
|
puts "1. Atomic operations - all succeed or all fail"
|
|
@@ -211,9 +211,8 @@ if __FILE__ == $0
|
|
|
211
211
|
puts " Parse::Object.transaction do |batch|"
|
|
212
212
|
puts " # Add operations to batch"
|
|
213
213
|
puts " end"
|
|
214
|
-
|
|
215
214
|
rescue => e
|
|
216
215
|
puts "Error: #{e.message}"
|
|
217
216
|
puts e.backtrace
|
|
218
217
|
end
|
|
219
|
-
end
|
|
218
|
+
end
|
data/examples/webhook_server.rb
CHANGED
|
@@ -30,8 +30,8 @@ require "parse-stack-next"
|
|
|
30
30
|
# ---------------------------------------------------------------------------
|
|
31
31
|
Parse.setup(
|
|
32
32
|
server_url: ENV.fetch("PARSE_SERVER_URL", "http://localhost:1337/parse"),
|
|
33
|
-
app_id:
|
|
34
|
-
api_key:
|
|
33
|
+
app_id: ENV.fetch("PARSE_APP_ID"),
|
|
34
|
+
api_key: ENV.fetch("PARSE_REST_KEY"),
|
|
35
35
|
master_key: ENV.fetch("PARSE_MASTER_KEY"),
|
|
36
36
|
)
|
|
37
37
|
|
|
@@ -56,7 +56,7 @@ class Post < Parse::Object
|
|
|
56
56
|
# NON-Ruby client they run here only if a beforeSave/afterSave webhook is
|
|
57
57
|
# registered for Post. Registering beforeSave enables BOTH before_save and
|
|
58
58
|
# before_create; afterSave enables both after_save and after_create.
|
|
59
|
-
before_save
|
|
59
|
+
before_save :normalize_slug
|
|
60
60
|
before_create { self.published = false } # created drafts start unpublished
|
|
61
61
|
after_create :enqueue_welcome # see note on afterSave below
|
|
62
62
|
|