• Stars
    star
    10
  • Rank 1,746,056 (Top 36 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 10 years ago
  • Updated about 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

โœจ Works like magic to dry up your controllers

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.

More Repositories

1

vite_ruby

โšก๏ธ Vite.js in Ruby, bringing joy to your JavaScript experience
Ruby
1,124
star
2

iles

๐Ÿ The joyful site generator
TypeScript
1,043
star
3

vite-plugin-image-presets

๐Ÿ–ผ Image Presets for Vite.js apps
TypeScript
243
star
4

vite-plugin-environment

Easily expose environment variables in Vite.js
TypeScript
132
star
5

vite-plugin-full-reload

โ™ป๏ธ Automatically reload the page when files are modified
JavaScript
121
star
6

oj_serializers

โšก๏ธ Faster JSON serialization for Ruby on Rails. Easily migrate away from Active Model Serializers.
Ruby
98
star
7

js_from_routes

๐Ÿ›ฃ๏ธ Generate path helpers and API methods from your Rails routes
Ruby
86
star
8

request_store_rails

๐Ÿ“ฆ Per-request global storage for Rails prepared for multi-threaded apps
Ruby
83
star
9

types_from_serializers

โœ… Generate TypeScript interfaces from your JSON serializers
Ruby
71
star
10

vuex-stores

๐Ÿ—„ Store objects for Vuex, a simple and more fluid API for state-management.
JavaScript
63
star
11

vue-custom-element-example

An example on how to define custom elements using Vue 3
TypeScript
54
star
12

mongoid_includes

๐ŸŒฟ Improves eager loading support for Mongoid
Ruby
46
star
13

jekyll-vite

โšก๏ธ๐Ÿฉธ Use Vite.js in Jekyll as your assets pipeline
Ruby
44
star
14

queryable

โ” Gives your queries a home and avoid tucking scopes inside your models
Ruby
42
star
15

vite-plugin-stimulus-hmr

โšก๏ธ HMR for Stimulus controllers in Vite.js
TypeScript
42
star
16

stimulus-vite-helpers

Helpers to easily load all your Stimulus controllers when using Vite.js
TypeScript
37
star
17

capybara-compose

โœ… Easily write fluent integration tests with Capybara in Ruby
Ruby
31
star
18

better_settings

โš™ Settings for Ruby apps โ€“ fast, immutable, better.
Ruby
20
star
19

vite-plugin-bugsnag

Report builds and upload source maps to Bugsnag
TypeScript
18
star
20

i18n_multitenant

๐ŸŒŽ Provides a convenient way to use tenant-specific translations
Ruby
16
star
21

vite-plugin-manifest-sri

Subresource Integrity for Vite.js manifest files
JavaScript
13
star
22

sublime-toggle-dark-mode

๐ŸŒš๐ŸŒž Toggle between dark and light mode in Sublime Text 4
JavaScript
9
star
23

pakiderm

๐Ÿ˜ Pakiderm will never forget the return value
Ruby
7
star
24

presenter_rails

๐Ÿ”ญ Expose your view models in a convenient way
Ruby
6
star
25

vite-plugin-erb

Use ERB files in Vite.js projects with a Ruby backend
TypeScript
5
star
26

journeyman

Let your factories use your business logic, making them flexible and easier to update.
Ruby
5
star
27

jekyll-vite-minima

โšก๏ธ๐Ÿฉธ Use Vite.js in Jekyll minima theme as your assets pipeline
Ruby
3
star
28

automatic-music-transcription

Automatically exported from code.google.com
C
2
star
29

vite-plugin-xdm

Use XDM in VIte.js
JavaScript
2
star
30

crouton

๐Ÿž Context sensitive notifications for Rails
Ruby
1
star
31

fast-food-mvc

Automatically exported from code.google.com
C#
1
star
32

ElMassimo

1
star
33

vite-vue-router-hmr-repro

Vue
1
star