Skip to content

Commit 3491113

Browse files
committed
First release of River Ruby bindings
A first push of Ruby bindings for River, providing insert-only client to work jobs that are implemented in Go. Includes two initial drivers in the `drivers/` directory, one for ActiveRecord and one for Sequel, which should cover the vast majority of Ruby applications making use of Postgres. The drivers are kept in the main gem's GitHub repository for convenience, but ship as separate gems so that programs including them can minimize their dependencies. Overall, I'm happy at how close I was able to keep the API to the Go version. A lot of syntax in Go just isn't needed due to the more dynamic and implicit nature of Ruby, but the parts that came through are quite close. e.g. We have a job args concept, along with `InsertOpts` that can be added to both jobs and at insert time, just like Go. Purposely not implemented on this first push (I'll follow up with these later on): * Unique jobs. * Batch insert.
1 parent 7437932 commit 3491113

35 files changed

Lines changed: 1992 additions & 11 deletions

.github/workflows/ci.yml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: CI
2+
3+
env:
4+
# Database to connect to that can create other databases with `CREATE DATABASE`.
5+
ADMIN_DATABASE_URL: postgres://postgres:postgres@localhost:5432
6+
7+
# Just a common place for steps to put binaries they need and which is added
8+
# to GITHUB_PATH/PATH.
9+
BIN_PATH: /home/runner/bin
10+
11+
# A suitable URL for a test database.
12+
TEST_DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/riverqueue_ruby_test?sslmode=disable
13+
14+
on:
15+
- push
16+
17+
jobs:
18+
lint:
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 3
21+
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@v4
25+
26+
- name: Install Ruby + `bundle install`
27+
uses: ruby/setup-ruby@v1
28+
with:
29+
ruby-version: "head"
30+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
31+
32+
- name: Standard Ruby (riverqueue-ruby)
33+
run: bundle exec standardrb
34+
working-directory: .
35+
36+
- name: bundle install (riverqueue-activerecord)
37+
run: bundle install
38+
working-directory: ./drivers/riverqueue-activerecord
39+
40+
- name: Standard Ruby (riverqueue-activerecord)
41+
run: bundle exec standardrb
42+
working-directory: ./drivers/riverqueue-activerecord
43+
44+
- name: bundle install (riverqueue-sequel)
45+
run: bundle install
46+
working-directory: ./drivers/riverqueue-sequel
47+
48+
- name: Standard Ruby (riverqueue-sequel)
49+
run: bundle exec standardrb
50+
working-directory: ./drivers/riverqueue-sequel
51+
52+
spec:
53+
runs-on: ubuntu-latest
54+
timeout-minutes: 3
55+
56+
services:
57+
postgres:
58+
image: postgres
59+
env:
60+
POSTGRES_PASSWORD: postgres
61+
options: >-
62+
--health-cmd pg_isready
63+
--health-interval 2s
64+
--health-timeout 5s
65+
--health-retries 5
66+
ports:
67+
- 5432:5432
68+
69+
steps:
70+
- name: Checkout
71+
uses: actions/checkout@v4
72+
73+
- name: Install Ruby + `bundle install`
74+
uses: ruby/setup-ruby@v1
75+
with:
76+
ruby-version: "head"
77+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
78+
79+
# There is a version of Go on Actions' base image, but it's old and can't
80+
# read modern `go.mod` annotations correctly.
81+
- name: Install Go
82+
uses: actions/setup-go@v4
83+
with:
84+
go-version: "stable"
85+
check-latest: true
86+
87+
- name: Create database
88+
run: psql --echo-errors --quiet -c '\timing off' -c "CREATE DATABASE riverqueue_ruby_test;" ${ADMIN_DATABASE_URL}
89+
90+
- name: Install River CLI
91+
run: go install github.com/riverqueue/river/cmd/river@latest
92+
93+
- name: river migrate-up
94+
run: river migrate-up --database-url "$TEST_DATABASE_URL"
95+
96+
- name: Rspec (riverqueue-ruby)
97+
run: bundle exec rspec
98+
working-directory: .
99+
100+
- name: bundle install (riverqueue-activerecord)
101+
run: bundle install
102+
working-directory: ./drivers/riverqueue-activerecord
103+
104+
- name: Rspec (riverqueue-activerecord)
105+
run: bundle exec rspec
106+
working-directory: ./drivers/riverqueue-activerecord
107+
108+
- name: bundle install (riverqueue-sequel)
109+
run: bundle install
110+
working-directory: ./drivers/riverqueue-sequel
111+
112+
- name: Rspec (riverqueue-sequel)
113+
run: bundle exec rspec
114+
working-directory: ./drivers/riverqueue-sequel

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
*.gem
2+
coverage/

Gemfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
source "https://rubygems.org"
2+
3+
gemspec
4+
5+
group :development, :test do
6+
gem "standard"
7+
end
8+
9+
group :test do
10+
gem "debug"
11+
gem "rspec-core"
12+
gem "rspec-expectations"
13+
gem "simplecov", require: false
14+
end

Gemfile.lock

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
PATH
2+
remote: .
3+
specs:
4+
riverqueue (0.0.1)
5+
6+
GEM
7+
remote: https://rubygems.org/
8+
specs:
9+
ast (2.4.2)
10+
debug (1.9.1)
11+
irb (~> 1.10)
12+
reline (>= 0.3.8)
13+
diff-lcs (1.5.0)
14+
docile (1.4.0)
15+
io-console (0.7.2)
16+
irb (1.11.2)
17+
rdoc
18+
reline (>= 0.4.2)
19+
json (2.7.1)
20+
language_server-protocol (3.17.0.3)
21+
lint_roller (1.1.0)
22+
parallel (1.24.0)
23+
parser (3.3.0.5)
24+
ast (~> 2.4.1)
25+
racc
26+
psych (5.1.2)
27+
stringio
28+
racc (1.7.3)
29+
rainbow (3.1.1)
30+
rdoc (6.6.2)
31+
psych (>= 4.0.0)
32+
regexp_parser (2.9.0)
33+
reline (0.4.3)
34+
io-console (~> 0.5)
35+
rexml (3.2.6)
36+
rspec-core (3.12.2)
37+
rspec-support (~> 3.12.0)
38+
rspec-expectations (3.12.3)
39+
diff-lcs (>= 1.2.0, < 2.0)
40+
rspec-support (~> 3.12.0)
41+
rspec-support (3.12.1)
42+
rubocop (1.61.0)
43+
json (~> 2.3)
44+
language_server-protocol (>= 3.17.0)
45+
parallel (~> 1.10)
46+
parser (>= 3.3.0.2)
47+
rainbow (>= 2.2.2, < 4.0)
48+
regexp_parser (>= 1.8, < 3.0)
49+
rexml (>= 3.2.5, < 4.0)
50+
rubocop-ast (>= 1.30.0, < 2.0)
51+
ruby-progressbar (~> 1.7)
52+
unicode-display_width (>= 2.4.0, < 3.0)
53+
rubocop-ast (1.31.1)
54+
parser (>= 3.3.0.4)
55+
rubocop-performance (1.20.2)
56+
rubocop (>= 1.48.1, < 2.0)
57+
rubocop-ast (>= 1.30.0, < 2.0)
58+
ruby-progressbar (1.13.0)
59+
simplecov (0.22.0)
60+
docile (~> 1.1)
61+
simplecov-html (~> 0.11)
62+
simplecov_json_formatter (~> 0.1)
63+
simplecov-html (0.12.3)
64+
simplecov_json_formatter (0.1.4)
65+
standard (1.34.0)
66+
language_server-protocol (~> 3.17.0.2)
67+
lint_roller (~> 1.0)
68+
rubocop (~> 1.60)
69+
standard-custom (~> 1.0.0)
70+
standard-performance (~> 1.3)
71+
standard-custom (1.0.2)
72+
lint_roller (~> 1.0)
73+
rubocop (~> 1.50)
74+
standard-performance (1.3.1)
75+
lint_roller (~> 1.1)
76+
rubocop-performance (~> 1.20.2)
77+
stringio (3.1.0)
78+
unicode-display_width (2.5.0)
79+
80+
PLATFORMS
81+
arm64-darwin-22
82+
x86_64-linux
83+
84+
DEPENDENCIES
85+
debug
86+
riverqueue!
87+
rspec-core
88+
rspec-expectations
89+
simplecov
90+
standard
91+
92+
BUNDLED WITH
93+
2.4.20

Makefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.PHONY: lint
2+
lint: standardrb
3+
4+
.PHONY: rspec
5+
rspec: spec
6+
7+
.PHONY: spec
8+
spec:
9+
bundle exec rspec
10+
cd drivers/riverqueue-activerecord && bundle exec rspec
11+
cd drivers/riverqueue-sequel && bundle exec rspec
12+
13+
.PHONY: standardrb
14+
standardrb:
15+
bundle exec standardrb --fix
16+
cd drivers/riverqueue-activerecord && bundle exec standardrb --fix
17+
cd drivers/riverqueue-sequel && bundle exec standardrb --fix

docs/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# River client for Ruby [![Build Status](https://github.com/riverqueue/riverqueue-ruby/workflows/CI/badge.svg)](https://github.com/riverqueue/riverqueue-ruby/actions)
2+
3+
An insert-only Ruby client for [River](https://github.com/riverqueue/river) packaged in the [`riverqueue` gem](https://rubygems.org/gems/riverqueue). Allows jobs to be inserted in Ruby and run by a Go worker, but doesn't support working jobs in Ruby.
4+
5+
## Basic usage
6+
7+
`Gemfile` should contain the core gem and a driver like [`rubyqueue-sequel`](https://github.com/riverqueue/riverqueue-ruby/drivers/riverqueue-sequel) (see [drivers](#drivers)):
8+
9+
``` ruby
10+
gem "riverqueue"
11+
gem "riverqueue-sequel"
12+
```
13+
14+
Initialize a client with:
15+
16+
```ruby
17+
DB = Sequel.connect("postgres://...")
18+
client = River::Client.new(River::Driver::Sequel.new(DB))
19+
```
20+
21+
Define a job and insert it:
22+
23+
```ruby
24+
class SortArgs
25+
attr_accessor :strings
26+
27+
def initialize(strings:)
28+
self.strings = strings
29+
end
30+
31+
def kind = "sort"
32+
33+
def to_json = JSON.dump({strings: strings})
34+
end
35+
36+
job = client.insert(SimpleArgs.new(strings: ["whale", "tiger", "bear"]))
37+
```
38+
39+
Job args should:
40+
41+
* Respond to `#kind` with a unique string that identifies them in the database, and which a Go worker will recognize.
42+
* Response to `#to_json` with a JSON serialization that'll be parseable as an object in Go.
43+
44+
They may also respond to `#insert_opts` with an instance of `InsertOpts` to define insertion options that'll be used for all jobs of the kind.
45+
46+
### Insertion options
47+
48+
Inserts take an `insert_opts` parameter to customize features of the inserted job:
49+
50+
```ruby
51+
job = client.insert(
52+
SimpleArgs.new(strings: ["whale", "tiger", "bear"]),
53+
insert_opts: River::InsertOpts.new(
54+
max_attempts: 17,
55+
priority: 3,
56+
queue: "my_queue",
57+
tags: ["custom"]
58+
)
59+
)
60+
```
61+
62+
### Inserting with a Ruby hash
63+
64+
`JobArgsHash` can be used to insert with a kind and JSON hash so that it's not necessary to define a class:
65+
66+
```ruby
67+
job = client.insert(River::JobArgsHash.new("hash_kind", {
68+
job_num: 1
69+
}))
70+
```
71+
72+
## Drivers
73+
74+
### ActiveRecord
75+
76+
``` ruby
77+
gem "riverqueue"
78+
gem "riverqueue-activerecord"
79+
```
80+
81+
Initialize driver and client:
82+
83+
```ruby
84+
ActiveRecord::Base.establish_connection("postgres://...")
85+
client = River::Client.new(River::Driver::ActiveRecord.new(DB))
86+
```
87+
88+
### Sequel
89+
90+
``` ruby
91+
gem "riverqueue"
92+
gem "riverqueue-sequel"
93+
```
94+
95+
Initialize driver and client:
96+
97+
```ruby
98+
DB = Sequel.connect("postgres://...")
99+
client = River::Client.new(River::Driver::Sequel.new(DB))
100+
```
101+
102+
## Development
103+
104+
See [development](./development.md).

docs/development.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# riverqueue-ruby development
2+
3+
## Install dependencies
4+
5+
```shell
6+
$ bundle install
7+
```
8+
## Run tests
9+
10+
Create a test database and migrate with River's CLI:
11+
12+
```shell
13+
$ go install github.com/riverqueue/river/cmd/river
14+
$ createdb riverqueue_ruby_test
15+
$ river migrate-up --database-url "postgres://localhost/riverqueue_ruby_test"
16+
```
17+
18+
Run all specs:
19+
20+
```shell
21+
$ bundle exec rspec spec
22+
```
23+
24+
## Run lint
25+
26+
```shell
27+
$ standardrb --fix
28+
```
29+
30+
## Code coverage
31+
32+
Running the entire test suite will produce a coverage report, and will fail if line and branch coverage is below 100%. Run the suite and open `coverage/index.html` to find lines or branches that weren't covered:
33+
34+
```shell
35+
$ bundle exec rspec spec
36+
$ open coverage/index.html
37+
```
38+
39+
## Publish a new gem
40+
41+
```shell
42+
git checkout master && git pull --rebase
43+
VERSION=v0.0.x
44+
gem build riverqueue.gemspec
45+
gem push riverqueue-$VERSION.gem
46+
git tag $VERSION
47+
git push --tags
48+
```

0 commit comments

Comments
 (0)