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_pkadd_missingremove_presentadd_remove
- Extension
- Pagination
- Development
- Contributing
- License
Installation
Add this line to your application's Gemfile:
gem 'sinja-sequel'And then execute:
$ bundleOr install it yourself as:
$ gem install sinja-sequelBest 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
endSee "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
endNote 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
databasehelper that delegates toSequel::Model.db. - Defines a
transactionhelper that delegates todatabase.transaction. - Defines a
validate!helper that raises an error ifresourceis invalid after acreateorupdateaction helper invocation. - Defines a simple equality-based
filterhelper that passes the filter params toSequel::Dataset#where. - Defines a
sorthelper that appliesSequel.ascandSequel.descto the sort terms and passes them toSequel::Dataset#order. - Defines a
finalizehelper that simply callsSequel::Dataset#all.
If the :pagination Sequel extension is loaded, it also does the following:
- Configures
page_usingfor page number- and size-based pagination, with an additional record count parameter to avoid repetitiveSELECT COUNTqueries while paging. - Defines a
pagehelper that callsSequel::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.
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
endNote 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)
endadd_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
endIt 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
endremove_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
endNote 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.
-
resourceandhas_many, andhas_onetake an optional second argument that specifies the method to use to cast the ID of the resource or resource identifier object(s) (:to_iby default). -
The generated action helpers will be unrestricted by default.
-
The generated
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 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_jsonapiPretty 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
endDevelopment
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.