Sinja::Sequel
Sinja::Sequel configures your Sinja application to work with Sequel
out of the box, and provides additional helpers to greatly simplify the process
of writing the more complex action helpers (specifically replace
, merge
,
and subtract
). An optional extension enhances Sinja's DSL to generate basic
action helpers that can be overridden, customized, or removed.
The core configuration and helpers are in pretty good shape (Sinja uses them in its demo app and test suite), but the extension could use some fleshing out. Testers and community contributions welcome!
- Installation
- Best Practices
- Usage
- Core
- Helpers
next_pk
add_missing
remove_present
add_remove
- Extension
- Pagination
- Development
- Contributing
- License
Installation
Add this line to your application's Gemfile:
gem 'sinja-sequel'
And then execute:
$ bundle
Or install it yourself as:
$ gem install sinja-sequel
Best Practices
Always return Sequel datasets (instead of arrays of objects) from your index
(e.g. Foo.dataset
) and fetch
(e.g. resource.bars_dataset
) action
helpers. The finalize
helper, described below, will ensure they are
"rasterized" before being passed to JSONAPI::Serializers.
You'll want to enable Sequel's :tactical_eager_loading
plugin for the best
performance with JSONAPI::Serializers. I've seen it reduce complex
serializations by a factor of 100 (i.e. quite literally one query instead of
100).
If you want to use client-generated IDs, enable the :update_primary_key
plugin on the model and call unrestrict_primary_key
in the model definition
to allow mass assignment (e.g. with Sequel::Model#set_fields
).
If your model has foreign keys and you want to enforce a non-nullable
constraint at the application level, consider enabling the
:validation_helpers
plugin on the model and using validates_not_null
in
conjuction with the validate!
helper described below:
class Bar < Sequel::Model
plugin :validation_helpers
def validate
super
validates_not_null :foo
end
end
See "Avoiding Null Foreign Keys" in the Sinja documentation for more information.
Finally, enable the :pagination
extension on your connection (before
prepending Core) to enable pagination!
Usage
Progressively opt-in to Sinja::Sequel's features by enabling (1) Core, (2) Helpers and Core (the most common use-case), or (3) Extension, Helpers, and Core. (Pagination is automatically enabled with Core, but may need to be manually enabled under certain circumstances, detailed below.)
Core
Prepend Sinja::Sequel::Core after registering Sinja:
require 'sinja'
require 'sinja/sequel/core'
class MyApp < Sinatra::Base
register Sinja
helpers do
prepend Sinja::Sequel::Core
end
# ..
freeze_jsonapi
end
Note that you must use prepend
(instead of including Sinja::Sequel::Core like
a normal module of Sinatra helpers) in order to ensure that the included
methods take precedence over Sinja's method stubs (e.g. transaction
).
This will hopefully be fixed in a future version of Sinatra.
Prepending Core has the following effects on your application:
- Configures
conflict_
,not_found_
, andvalidation_exceptions
, andvalidation_formatter
. - Defines a
database
helper that delegates toSequel::Model.db
. - Defines a
transaction
helper that delegates todatabase.transaction
. - Defines a
validate!
helper that raises an error ifresource
is invalid after acreate
orupdate
action helper invocation. - Defines a simple equality-based
filter
helper that passes the filter params toSequel::Dataset#where
. - Defines a
sort
helper that appliesSequel.asc
andSequel.desc
to the sort terms and passes them toSequel::Dataset#order
. - Defines a
finalize
helper that simply callsSequel::Dataset#all
.
If the :pagination
Sequel extension is loaded, it also does the following:
- Configures
page_using
for page number- and size-based pagination, with an additional record count parameter to avoid repetitiveSELECT COUNT
queries while paging. - Defines a
page
helper that callsSequel::Dataset#paginate
and computes a hash of page params that Sinja will use to construct the root pagination links and add to the root metadata of the response.
You may override any of the installed helpers by defining your own. Please see the Sinja documentation for more information about Sinja hooks and configurables, and the Sequel documentation for more information about Sequel plugins and features.
Helpers
Include Sinja::Sequel::Helpers after registering Sinja:
require 'sinja'
require 'sinja/sequel/helpers'
class MyApp < Sinatra::Base
register Sinja
helpers Sinja::Sequel::Helpers
# ..
freeze_jsonapi
end
Note that including Helpers will automatically prepend Core!
next_pk
A convenience method to always return the primary key of the resource and the
resource from your create
action helpers. Simply use it instead of next
!
create do |attr|
next_pk Foo.create(attr)
end
add_missing
Take the key of a Sequel *_to_many association and an array of resource
identifier objects and add the "missing" records to the collection. Makes
writing your merge
action helpers a breeze!
has_many :bars do
merge do |rios|
add_missing(:bars, rios)
end
end
It will try to cast the ID of each resource identifier object by sending it the
:to_i
method; pass in a third argument to specify a different method (e.g. if
the primary key of the bars
table is a varchar
, pass in :to_s
instead).
This helper also takes an optional block that can be used to filter subresources during processing. Simply return a truthy or falsey value from the block (or raise an error to abort the entire transaction):
has_many :bars do
merge do |rios|
add_missing(:bars, rios) do |bar|
role?(:admin) || bar.owner == resource.owner
end
end
end
remove_present
Like add_missing
, but removes the "present" records from the collection.
Makes writing your subtract
action helpers a breeze!
add_remove
Like add_missing
and remove_present
, but performs an efficient delta
operation on the collection. Makes writing your replace
action helpers a
breeze!
An optional block passed to this method will be used to filter both adds and
removes. To use different filters for the two operations, pass a hash of
callables (with keys :add
and/or :remove
).
Extension
Register Sinja::Sequel after registering Sinja:
require 'sinja'
require 'sinja/sequel'
class MyApp < Sinatra::Base
register Sinja
register Sinja::Sequel
# ..
freeze_jsonapi
end
Note that registering the extension will automatically include Helpers and prepend Core!
After registering the extension, the resource
, has_many
, and has_one
DSL
keywords will generate basic action helpers.
-
resource
andhas_many
, andhas_one
take an optional second argument that specifies the method to use to cast the ID of the resource or resource identifier object(s) (:to_i
by default). -
The generated action helpers will be unrestricted by default.
-
The generated
create
action helper does not support client-generated IDs.
These action helpers can be subsequently overridden, customized by setting
action helper options (i.e. :roles
) and/or defining before_<action>
hooks,
or removed entirely with remove_<action>
.
Given a database connection and Foo, Bar, and Qux models and serializers, here's an example "classic"-style application using the extension:
require 'sinatra/jsonapi/sequel'
resource :foos do
has_many :bars
has_one :qux
end
resource :bars do
has_one :foo
end
resource :quxes do
has_many :foos
end
freeze_jsonapi
Pretty neat, huh?
Pagination
Sinja::Sequel inspects the first Sequel database to determine whether or not to enable pagination. If (and only if!) you have multiple databases in your application and only some support pagination, you may need to prepend Sinja::Sequel::Pagination after prepending Core, including Helpers, or registering Sinja:
require 'sinja'
require 'sinja/sequel'
require 'sinja/sequel/pagination'
DB = Sequel.connect ENV['DB_URL']
OTHER_DB = Sequel.connect ENV['OTHER_DB_URL']
OTHER_DB.extension :pagination
# Sequel::Model.db now points to DB, which does not support pagination, so
# pagination will not be automatically enabled. We'll point Sinja::Sequel at
# OTHER_DB instead, and manually enable pagination...
class MyApp < Sinatra::Base
register Sinja
register Sinja::Sequel
helpers do
prepend Sinja::Sequel::Pagination
def database
OTHER_DB
end
end
# ..
freeze_jsonapi
end
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run
rake spec
to run the tests. You can also run bin/console
for an interactive
prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To
release a new version, update the version number in version.rb
, and then run
bundle exec rake release
, which will create a git tag for the version, push
git commits and tags, and push the .gem
file to
rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/mwpastore/sinja-sequel.
License
The gem is available as open source under the terms of the MIT License.