A small library to help you avoid boilerplate for standard CRUD actions, while improving your controllers' readibility.
Add this line to your application's Gemfile:
gem 'resourcerer'And then execute:
$ bundle
Or install it yourself as:
$ gem install resourcerer
In the simplest scenario you'll just use it to define a resource in the controller:
class BandsController < ApplicationController
resource :band
endNow every time you call band in your controller or view, it will look for an
ID and try to perform Band.find(id). If an ID parameter isn't found, it will
call Band.new(band_params). The result will be memoized in a
@resourcerer_band instance variable.
Here's what a standard Rails CRUD controller using Resourcerer might look like:
class BandsController < ApplicationController
resource :band do
permit [:name, :genre]
end
def create
if band.save
redirect_to band_path(band)
else
render :new
end
end
def update
if band.save
redirect_to band_path(band)
else
render :edit
end
end
def destroy
band.destroy
redirect_to bands_path
end
endThat's way less code than usual! 😃
The default resolving workflow is pretty powerful and customizable. It could be expressed with the following pseudocode:
def fetch(scope, id)
instance = id ? find(id, scope) : build(attrs, scope)
instance.tap { instance.assign_attributes(attrs) if assign? }
end
def id
params[:band_id] || params[:id]
end
def find(id, scope)
scope.find(id)
end
def build(params, scope)
scope.new(params) # Band.new(params)
end
def scope
model # Band
end
def model
:band.classify.constantize # Band
end
def assign?
action_name == 'update'
end
def attrs
if respond_to?(:band_params, true) && !request.get?
band_params
else
{}
end
endThe resource is lazy, so it won't do anyband until the method is called.
It is possible to override each step with options. The acceptable options to the
resource macro are:
In order to fetch a resource Resourcerer relies on the presence of an ID:
# Default Behavior
resource :band, id: ->{ params[:band_id] || params[:id] }You can override any option's default behavior by passing in a Proc:
resource :band, id: ->{ 42 }Passing lambdas might not always be fun, so most options provide shortcuts that might help make life easier:
resource :band, id: :custom_band_id
# same as
resource :band, id: ->{ params[:custom_band_id] }
resource :band, id: [:try_this_id, :or_maybe_that_id]
# same as
resource :band, id: ->{ params[:try_this_id] || params[:or_maybe_that_id] }If an ID was provided, Resourcerer will try to find the model:
# Default Behavior
resource :band, find: -> (id, scope) { scope.find(id) }Where scope is a model scope, like Band or User.active or
Post.published. There's even a convenient shortcut for cases where the ID is
actually something else:
resource :band, find_by: :slug
# same as
resource :band, find: ->(slug, scope){ scope.find_by!(slug: slug) }When an ID is not present, Resourcerer tries to build an object for you:
# Default Behavior
resource :band, build: ->(attrs, scope){ scope.new(band_params) }This option is responsible for calulating params before passing them to the
build step. The default behavior was modeled with Strong Parameters in mind and
is somewhat smart: it calls the band_params controller method if it's
available and the request method is not GET. In all other cases it produces
an empty hash.
You can easily specify which controller method you want it to call instead of
band_params, or just provide your own logic:
resource :band, attrs: :custom_band_params
resource :other_band, attrs: ->{ { foo: "bar" } }
private
def custom_band_params
params.require(:band).permit(:name, :genre)
endUsing the default model name conventions? permit can do that for you:
resource :band, permit: [:name, :genre]Defines the scope that's used in find and build steps:
resource :band, collection: ->{ current_user.bands }Allows you to specify the model class to use:
resource :band, model: ->{ AnotherBand }
resource :band, model: AnotherBand
resource :band, model: "AnotherBand"
resource :band, model: :another_bandAllows you to specify whether the attributes should be assigned:
resource :band, assign?: false
resource :band, assign?: [:edit, :update]
resource :band, assign?: ->{ current_user.admin? }and also how to assign them:
resource :band, assign: ->(band, attrs) { band.set_info(attrs) }You can define configuration presets with the resourcerer_config method to reuse
them later in different resource definitions.
resourcerer_config :cool_find, find: ->{ very_cool_find_code }
resourcerer_config :cool_build, build: ->{ very_cool_build_code }
resource :band, using: [:cool_find, :cool_build]
resource :another_band, using: :cool_buildOptions that are passed to resource will take precedence over the presets.
Decorators or Presenters (like draper)
If you use decorators, you'll be able to avoid even more boilerplate if you throw presenter_rails in the mix:
class BandController < ApplicationController
resource(:band, permit: :name)
present(:band) { band.decorate }
def create
if band.save
redirect_to(band)
else
render :new
end
end
def update
if band.save
redirect_to(band)
else
render :edit
end
end
endComparison with Decent Exposure.
Resourcerer is heavily inspired on Decent Exposure, but it attempts to be simpler and more flexible by not focusing on exposing variables to the view context.
Both allow you to find or initialize a model and assign attributes, removing the boilerplate from most CRUD actions.
Resourcerer does not expose an object to the view in any way, nor deal with decoratation. It also provides better support for strong parameters.
Resourcerer is based on DecentExposure.