riverqueue-activerecord 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/driver.rb +189 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8e3a60a730e73674a1bfdd5df0d2ca05dd0e7c10667c3527de42b518f441ae1
|
4
|
+
data.tar.gz: a336f6a943b451cce3b1504d63570028592b68a9a47d26364ddf472e28e6a864
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fc854c995ed4d340c1c8ddcbfcbc19e5246247d66879c6bca7ba1f99cabc4a4b7260bf359d1b716dd9fdd1d882dc4cdd762a2665c34c921fff4abcce39b49f8
|
7
|
+
data.tar.gz: 2487f551511dda6a7d649298d762340a9244ed2ec82712c12b365c90f2c6ddc2f600a615a1a21bf4c3b3360422d596e43b3d0d56737e3901d8cbf352bc90955d
|
data/lib/driver.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
module River::Driver
|
2
|
+
# Provides a ActiveRecord driver for River.
|
3
|
+
#
|
4
|
+
# Used in conjunction with a River client like:
|
5
|
+
#
|
6
|
+
# DB = ActiveRecord.connect("postgres://...")
|
7
|
+
# client = River::Client.new(River::Driver::ActiveRecord.new(DB))
|
8
|
+
#
|
9
|
+
class ActiveRecord
|
10
|
+
def initialize
|
11
|
+
# It's Ruby, so we can only define a model after ActiveRecord's established a
|
12
|
+
# connection because it's all dynamic.
|
13
|
+
if !River::Driver::ActiveRecord.const_defined?(:RiverJob)
|
14
|
+
River::Driver::ActiveRecord.const_set(:RiverJob, Class.new(::ActiveRecord::Base) do
|
15
|
+
self.table_name = "river_job"
|
16
|
+
|
17
|
+
# Unfortunately, Rails errors if you have a column called `errors` and
|
18
|
+
# provides no way to remap names (beyond ignoring a column, which we
|
19
|
+
# really don't want). This patch is in place so we can hydrate this
|
20
|
+
# model at all without ActiveRecord self-immolating.
|
21
|
+
def self.dangerous_attribute_method?(method_name)
|
22
|
+
return false if method_name == "errors"
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# See comment above, but since we force allowed `errors` as an
|
27
|
+
# attribute name, ActiveRecord would otherwise fail to save a row as
|
28
|
+
# it checked for its own `errors` hash and finding no values.
|
29
|
+
def errors = {}
|
30
|
+
end)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def advisory_lock(key)
|
35
|
+
::ActiveRecord::Base.connection.execute("SELECT pg_advisory_xact_lock(#{key})")
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def advisory_lock_try(key)
|
40
|
+
::ActiveRecord::Base.connection.execute("SELECT pg_try_advisory_xact_lock(123)").first["pg_try_advisory_xact_lock"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def job_get_by_id(id)
|
44
|
+
data_set = RiverJob.where(id: id)
|
45
|
+
data_set.first ? to_job_row_from_model(data_set.first) : nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def job_get_by_kind_and_unique_properties(get_params)
|
49
|
+
data_set = RiverJob.where(kind: get_params.kind)
|
50
|
+
data_set = data_set.where("tstzrange(?, ?, '[)') @> created_at", get_params.created_at[0], get_params.created_at[1]) if get_params.created_at
|
51
|
+
data_set = data_set.where(args: get_params.encoded_args) if get_params.encoded_args
|
52
|
+
data_set = data_set.where(queue: get_params.queue) if get_params.queue
|
53
|
+
data_set = data_set.where(state: get_params.state) if get_params.state
|
54
|
+
data_set.first ? to_job_row_from_model(data_set.first) : nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def job_insert(insert_params)
|
58
|
+
to_job_row_from_model(RiverJob.create(insert_params_to_hash(insert_params)))
|
59
|
+
end
|
60
|
+
|
61
|
+
def job_insert_unique(insert_params, unique_key)
|
62
|
+
res = RiverJob.upsert(
|
63
|
+
insert_params_to_hash(insert_params).merge(unique_key: unique_key),
|
64
|
+
on_duplicate: Arel.sql("kind = EXCLUDED.kind"),
|
65
|
+
returning: Arel.sql("*, (xmax != 0) AS unique_skipped_as_duplicate"),
|
66
|
+
|
67
|
+
# It'd be nice to specify this as `(kind, unique_key) WHERE unique_key
|
68
|
+
# IS NOT NULL` like we do elsewhere, but in its pure ingenuity, fucking
|
69
|
+
# ActiveRecord tries to look up a unique index instead of letting
|
70
|
+
# Postgres handle that, and of course it doesn't support a `WHERE`
|
71
|
+
# clause. The workaround is to target the index name instead of columns.
|
72
|
+
unique_by: "river_job_kind_unique_key_idx"
|
73
|
+
)
|
74
|
+
|
75
|
+
[to_job_row_from_raw(res), res.send(:hash_rows)[0]["unique_skipped_as_duplicate"]]
|
76
|
+
end
|
77
|
+
|
78
|
+
def job_insert_many(insert_params_many)
|
79
|
+
RiverJob.insert_all(insert_params_many.map { |p| insert_params_to_hash(p) })
|
80
|
+
insert_params_many.count
|
81
|
+
end
|
82
|
+
|
83
|
+
def job_list
|
84
|
+
data_set = RiverJob.order(:id)
|
85
|
+
data_set.all.map { |job| to_job_row_from_model(job) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def rollback_exception
|
89
|
+
::ActiveRecord::Rollback
|
90
|
+
end
|
91
|
+
|
92
|
+
def transaction(&)
|
93
|
+
::ActiveRecord::Base.transaction(requires_new: true, &)
|
94
|
+
end
|
95
|
+
|
96
|
+
private def insert_params_to_hash(insert_params)
|
97
|
+
# the call to `#compact` is important so that we remove nils and table
|
98
|
+
# default values get picked up instead
|
99
|
+
{
|
100
|
+
args: insert_params.encoded_args,
|
101
|
+
kind: insert_params.kind,
|
102
|
+
max_attempts: insert_params.max_attempts,
|
103
|
+
priority: insert_params.priority,
|
104
|
+
queue: insert_params.queue,
|
105
|
+
state: insert_params.state,
|
106
|
+
scheduled_at: insert_params.scheduled_at,
|
107
|
+
tags: insert_params.tags
|
108
|
+
}.compact
|
109
|
+
end
|
110
|
+
|
111
|
+
private def to_job_row_from_model(river_job)
|
112
|
+
# needs to be accessed through values because `errors` is shadowed by both
|
113
|
+
# ActiveRecord and the patch above
|
114
|
+
errors = river_job.attributes["errors"]
|
115
|
+
|
116
|
+
River::JobRow.new(
|
117
|
+
id: river_job.id,
|
118
|
+
args: JSON.parse(river_job.args),
|
119
|
+
attempt: river_job.attempt,
|
120
|
+
attempted_at: river_job.attempted_at&.getutc,
|
121
|
+
attempted_by: river_job.attempted_by,
|
122
|
+
created_at: river_job.created_at.getutc,
|
123
|
+
errors: errors&.map { |e|
|
124
|
+
deserialized_error = JSON.parse(e, symbolize_names: true)
|
125
|
+
|
126
|
+
River::AttemptError.new(
|
127
|
+
at: Time.parse(deserialized_error[:at]),
|
128
|
+
attempt: deserialized_error[:attempt],
|
129
|
+
error: deserialized_error[:error],
|
130
|
+
trace: deserialized_error[:trace]
|
131
|
+
)
|
132
|
+
},
|
133
|
+
finalized_at: river_job.finalized_at&.getutc,
|
134
|
+
kind: river_job.kind,
|
135
|
+
max_attempts: river_job.max_attempts,
|
136
|
+
metadata: river_job.metadata,
|
137
|
+
priority: river_job.priority,
|
138
|
+
queue: river_job.queue,
|
139
|
+
scheduled_at: river_job.scheduled_at.getutc,
|
140
|
+
state: river_job.state,
|
141
|
+
tags: river_job.tags,
|
142
|
+
unique_key: river_job.unique_key
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
# This is really awful, but some of ActiveRecord's methods (e.g. `.create`)
|
147
|
+
# return a model, and others (e.g. `.upsert`) return raw values, and
|
148
|
+
# therefore this second version from unmarshaling a job row exists. I
|
149
|
+
# searched long and hard for a way to have the former type of method return
|
150
|
+
# raw or the latter type of method return a model, but was unable to find
|
151
|
+
# anything.
|
152
|
+
private def to_job_row_from_raw(res)
|
153
|
+
river_job = {}
|
154
|
+
|
155
|
+
res.rows[0].each_with_index do |val, i|
|
156
|
+
river_job[res.columns[i]] = res.column_types[i].deserialize(val)
|
157
|
+
end
|
158
|
+
|
159
|
+
River::JobRow.new(
|
160
|
+
id: river_job["id"],
|
161
|
+
args: JSON.parse(river_job["args"]),
|
162
|
+
attempt: river_job["attempt"],
|
163
|
+
attempted_at: river_job["attempted_at"]&.getutc,
|
164
|
+
attempted_by: river_job["attempted_by"],
|
165
|
+
created_at: river_job["created_at"].getutc,
|
166
|
+
errors: river_job["errors"]&.map { |e|
|
167
|
+
deserialized_error = JSON.parse(e)
|
168
|
+
|
169
|
+
River::AttemptError.new(
|
170
|
+
at: Time.parse(deserialized_error["at"]),
|
171
|
+
attempt: deserialized_error["attempt"],
|
172
|
+
error: deserialized_error["error"],
|
173
|
+
trace: deserialized_error["trace"]
|
174
|
+
)
|
175
|
+
},
|
176
|
+
finalized_at: river_job["finalized_at"]&.getutc,
|
177
|
+
kind: river_job["kind"],
|
178
|
+
max_attempts: river_job["max_attempts"],
|
179
|
+
metadata: river_job["metadata"],
|
180
|
+
priority: river_job["priority"],
|
181
|
+
queue: river_job["queue"],
|
182
|
+
scheduled_at: river_job["scheduled_at"].getutc,
|
183
|
+
state: river_job["state"],
|
184
|
+
tags: river_job["tags"],
|
185
|
+
unique_key: river_job["unique_key"]
|
186
|
+
)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: riverqueue-activerecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Blake Gentry
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-08-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -78,6 +78,7 @@ executables: []
|
|
78
78
|
extensions: []
|
79
79
|
extra_rdoc_files: []
|
80
80
|
files:
|
81
|
+
- lib/driver.rb
|
81
82
|
- lib/riverqueue-activerecord.rb
|
82
83
|
homepage: https://riverqueue.com
|
83
84
|
licenses:
|