riverqueue-activerecord 0.6.1 → 0.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/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-08-
|
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:
|