Skip to content

ElMassimo/resourcerer

Repository files navigation

Resourcerer

Build Status Maintainability Test Coverage Gem Version License

A small library to help you avoid boilerplate for standard CRUD actions, while improving your controllers' readibility.

Installation

Add this line to your application's Gemfile:

gem 'resourcerer'

And then execute:

$ bundle

Or install it yourself as:

$ gem install resourcerer

Usage

In the simplest scenario you'll just use it to define a resource in the controller:

class BandsController < ApplicationController
  resource :band
end

Now 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.

Example

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
end

That's way less code than usual! 😃

Under the Hood

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
end

The resource is lazy, so it won't do anyband until the method is called.

Configuration

It is possible to override each step with options. The acceptable options to the resource macro are:

id

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] }

find

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) }

build

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) }

attrs

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)
end

Using the default model name conventions? permit can do that for you:

resource :band, permit: [:name, :genre]

collection

Defines the scope that's used in find and build steps:

resource :band, collection: ->{ current_user.bands }

model

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_band

assign and assign?

Allows 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) }

Advanced Configuration with resourcerer_config

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_build

Options 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
end

Comparison 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.

Similarities

Both allow you to find or initialize a model and assign attributes, removing the boilerplate from most CRUD actions.

Differences

Resourcerer does not expose an object to the view in any way, nor deal with decoratation. It also provides better support for strong parameters.

Special Thanks

Resourcerer is based on DecentExposure.

About

✨ Works like magic to dry up your controllers

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published