Skip to content

Commit

Permalink
First release of River Ruby bindings
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
brandur committed Mar 6, 2024
1 parent 7437932 commit e25d151
Show file tree
Hide file tree
Showing 35 changed files with 2,005 additions and 11 deletions.
114 changes: 114 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: CI

env:
# Database to connect to that can create other databases with `CREATE DATABASE`.
ADMIN_DATABASE_URL: postgres://postgres:postgres@localhost:5432

# Just a common place for steps to put binaries they need and which is added
# to GITHUB_PATH/PATH.
BIN_PATH: /home/runner/bin

# A suitable URL for a test database.
TEST_DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/riverqueue_ruby_test?sslmode=disable

on:
- push

jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 3

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Ruby + `bundle install`
uses: ruby/setup-ruby@v1
with:
ruby-version: "head"
bundler-cache: true # runs 'bundle install' and caches installed gems automatically

- name: Standard Ruby (riverqueue-ruby)
run: bundle exec standardrb
working-directory: .

- name: bundle install (riverqueue-activerecord)
run: bundle install
working-directory: ./drivers/riverqueue-activerecord

- name: Standard Ruby (riverqueue-activerecord)
run: bundle exec standardrb
working-directory: ./drivers/riverqueue-activerecord

- name: bundle install (riverqueue-sequel)
run: bundle install
working-directory: ./drivers/riverqueue-sequel

- name: Standard Ruby (riverqueue-sequel)
run: bundle exec standardrb
working-directory: ./drivers/riverqueue-sequel

spec:
runs-on: ubuntu-latest
timeout-minutes: 3

services:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 2s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Ruby + `bundle install`
uses: ruby/setup-ruby@v1
with:
ruby-version: "head"
bundler-cache: true # runs 'bundle install' and caches installed gems automatically

# There is a version of Go on Actions' base image, but it's old and can't
# read modern `go.mod` annotations correctly.
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "stable"
check-latest: true

- name: Create database
run: psql --echo-errors --quiet -c '\timing off' -c "CREATE DATABASE riverqueue_ruby_test;" ${ADMIN_DATABASE_URL}

- name: Install River CLI
run: go install github.com/riverqueue/river/cmd/river@latest

- name: river migrate-up
run: river migrate-up --database-url "$TEST_DATABASE_URL"

- name: Rspec (riverqueue-ruby)
run: bundle exec rspec
working-directory: .

- name: bundle install (riverqueue-activerecord)
run: bundle install
working-directory: ./drivers/riverqueue-activerecord

- name: Rspec (riverqueue-activerecord)
run: bundle exec rspec
working-directory: ./drivers/riverqueue-activerecord

- name: bundle install (riverqueue-sequel)
run: bundle install
working-directory: ./drivers/riverqueue-sequel

- name: Rspec (riverqueue-sequel)
run: bundle exec rspec
working-directory: ./drivers/riverqueue-sequel
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.gem
coverage/
14 changes: 14 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
source "https://rubygems.org"

gemspec

group :development, :test do
gem "standard"
end

group :test do
gem "debug"
gem "rspec-core"
gem "rspec-expectations"
gem "simplecov", require: false
end
93 changes: 93 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
PATH
remote: .
specs:
riverqueue (0.0.1)

GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
debug (1.9.1)
irb (~> 1.10)
reline (>= 0.3.8)
diff-lcs (1.5.0)
docile (1.4.0)
io-console (0.7.2)
irb (1.11.2)
rdoc
reline (>= 0.4.2)
json (2.7.1)
language_server-protocol (3.17.0.3)
lint_roller (1.1.0)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
racc
psych (5.1.2)
stringio
racc (1.7.3)
rainbow (3.1.1)
rdoc (6.6.2)
psych (>= 4.0.0)
regexp_parser (2.9.0)
reline (0.4.3)
io-console (~> 0.5)
rexml (3.2.6)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-support (3.12.1)
rubocop (1.61.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.1)
parser (>= 3.3.0.4)
rubocop-performance (1.20.2)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (1.13.0)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
standard (1.34.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.60)
standard-custom (~> 1.0.0)
standard-performance (~> 1.3)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.3.1)
lint_roller (~> 1.1)
rubocop-performance (~> 1.20.2)
stringio (3.1.0)
unicode-display_width (2.5.0)

PLATFORMS
arm64-darwin-22
x86_64-linux

DEPENDENCIES
debug
riverqueue!
rspec-core
rspec-expectations
simplecov
standard

BUNDLED WITH
2.4.20
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.PHONY: lint
lint: standardrb

.PHONY: rspec
rspec: spec

.PHONY: spec
spec:
bundle exec rspec
cd drivers/riverqueue-activerecord && bundle exec rspec
cd drivers/riverqueue-sequel && bundle exec rspec

.PHONY: standardrb
standardrb:
bundle exec standardrb --fix
cd drivers/riverqueue-activerecord && bundle exec standardrb --fix
cd drivers/riverqueue-sequel && bundle exec standardrb --fix
104 changes: 104 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# River client for Ruby [![Build Status](https://github.com/riverqueue/riverqueue-ruby/workflows/CI/badge.svg)](https://github.com/riverqueue/riverqueue-ruby/actions)

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.

## Basic usage

`Gemfile` should contain the core gem and a driver like [`rubyqueue-sequel`](https://github.com/riverqueue/riverqueue-ruby/drivers/riverqueue-sequel) (see [drivers](#drivers)):

``` ruby
gem "riverqueue"
gem "riverqueue-sequel"
```

Initialize a client with:

```ruby
DB = Sequel.connect("postgres://...")
client = River::Client.new(River::Driver::Sequel.new(DB))
```

Define a job and insert it:

```ruby
class SortArgs
attr_accessor :strings

def initialize(strings:)
self.strings = strings
end

def kind = "sort"

def to_json = JSON.dump({strings: strings})
end

job = client.insert(SimpleArgs.new(strings: ["whale", "tiger", "bear"]))
```

Job args should:

* Respond to `#kind` with a unique string that identifies them in the database, and which a Go worker will recognize.
* Response to `#to_json` with a JSON serialization that'll be parseable as an object in Go.

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.

### Insertion options

Inserts take an `insert_opts` parameter to customize features of the inserted job:

```ruby
job = client.insert(
SimpleArgs.new(strings: ["whale", "tiger", "bear"]),
insert_opts: River::InsertOpts.new(
max_attempts: 17,
priority: 3,
queue: "my_queue",
tags: ["custom"]
)
)
```

### Inserting with a Ruby hash

`JobArgsHash` can be used to insert with a kind and JSON hash so that it's not necessary to define a class:

```ruby
job = client.insert(River::JobArgsHash.new("hash_kind", {
job_num: 1
}))
```

## Drivers

### ActiveRecord

``` ruby
gem "riverqueue"
gem "riverqueue-activerecord"
```

Initialize driver and client:

```ruby
ActiveRecord::Base.establish_connection("postgres://...")
client = River::Client.new(River::Driver::ActiveRecord.new)
```

### Sequel

``` ruby
gem "riverqueue"
gem "riverqueue-sequel"
```

Initialize driver and client:

```ruby
DB = Sequel.connect("postgres://...")
client = River::Client.new(River::Driver::Sequel.new(DB))
```

## Development

See [development](./development.md).
48 changes: 48 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# riverqueue-ruby development

## Install dependencies

```shell
$ bundle install
```
## Run tests

Create a test database and migrate with River's CLI:

```shell
$ go install github.com/riverqueue/river/cmd/river
$ createdb riverqueue_ruby_test
$ river migrate-up --database-url "postgres://localhost/riverqueue_ruby_test"
```

Run all specs:

```shell
$ bundle exec rspec spec
```

## Run lint

```shell
$ standardrb --fix
```

## Code coverage

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:

```shell
$ bundle exec rspec spec
$ open coverage/index.html
```

## Publish a new gem

```shell
git checkout master && git pull --rebase
VERSION=v0.0.x
gem build riverqueue.gemspec
gem push riverqueue-$VERSION.gem
git tag $VERSION
git push --tags
```
Loading

0 comments on commit e25d151

Please sign in to comment.