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
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!
- Best Practices
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
$ gem install sinja-sequel
Always return Sequel datasets (instead of arrays of objects) from your
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
If you want to use client-generated IDs, enable the
plugin on the model and call
unrestrict_primary_key in the model definition
to allow mass assignment (e.g. with
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
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!
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.)
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.
This will hopefully be fixed in a future version of Sinatra.
Prepending Core has the following effects on your application:
- Defines a
databasehelper that delegates to
- Defines a
transactionhelper that delegates to
- Defines a
validate!helper that raises an error if
resourceis invalid after a
updateaction helper invocation.
- Defines a simple equality-based
filterhelper that passes the filter params to
- Defines a
sorthelper that applies
Sequel.descto the sort terms and passes them to
- Defines a
finalizehelper that simply calls
:pagination Sequel extension is loaded, it also does the following:
page_usingfor page number- and size-based pagination, with an additional record count parameter to avoid repetitive
SELECT COUNTqueries while paging.
- Defines a
pagehelper that calls
Sequel::Dataset#paginateand 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.
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!
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
create do |attr| next_pk Foo.create(attr) end
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
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
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
add_missing, but removes the "present" records from the collection.
Makes writing your
subtract action helpers a breeze!
remove_present, but performs an efficient delta
operation on the collection. Makes writing your
replace action helpers a
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
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
keywords will generate basic action helpers.
has_onetake an optional second argument that specifies the method to use to cast the ID of the resource or resource identifier object(s) (
The generated action helpers will be unrestricted by default.
createaction 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
or removed entirely with
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?
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
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
Bug reports and pull requests are welcome on GitHub at https://github.com/mwpastore/sinja-sequel.
The gem is available as open source under the terms of the MIT License.