riverqueue-activerecord 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/driver.rb +189 -0
  3. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a895e02bb7ee178301ae6f5151888079c80fae887cc9f50502257673844c58f
4
- data.tar.gz: 48a8d7d0c757a25cb7bddad09f9074bd53cd8daa742ca672bd2667d105a71c59
3
+ metadata.gz: b8e3a60a730e73674a1bfdd5df0d2ca05dd0e7c10667c3527de42b518f441ae1
4
+ data.tar.gz: a336f6a943b451cce3b1504d63570028592b68a9a47d26364ddf472e28e6a864
5
5
  SHA512:
6
- metadata.gz: 2f11bfa470f5649aabcdc5889c5669a0aaa8be6a3d48fda45bf35a5cce725a3c2c3d50a91bcc0e09161d9937fd6c9cfca88c6a68758bdae8005bcffdc930f3e1
7
- data.tar.gz: e20c79fb9f2b12a55d33de5341b6f3086865188337375911df33be605f803bb875a9bc40f8d2435ab45c7f6d68abe70e2221fa99e887a9922ca9d0c090fe0f82
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.6.1
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-21 00:00:00.000000000 Z
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: