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.
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: