Packages for Inherited Resources Gem

When I work with Ruby on Rails, I like to write the less I can for the server side part and one gem is perfect for this inherited_resources. This gem permits to create RESTful controllers without anything to write in your controller.

app/controllers/products_controller.rb
1
2
class ProductsController < InheritedResources::Base
end
config/routes.rb
1
2
3
MyApp::Application.routes.draw do
  resources :products
end

Only with this, now you can Create, Read, Update and Delete your users. It’s cool but sometimes you need to have a search or apply a scope, a pagination… For this I decided to add some module to do that.

  • Scope: Apply a scope
  • Search: Search using meta_search
  • Order: Reorder your collection
  • Pagination: Paginate your results using kaminari
  • BuildWithCurrentUser: Create/Update your resource with the current user
scope.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module InheritedResourcesModules
  # Scope
  # Sometime it can be usefull to scope any request using the scopes defined in the model
  # ex:
  # a class Article with the scope published
  # scope :published -> { Article.arel_table[:published_at].lteq Time.now }  # published_at < Time.now
  #
  # including this module in the controller permits to automatically call this scope when the parameter scope=published is given
  # /articles?scope=published  will return all articles published
  module Scope

    # overwrite method call at the end of the process
    # in this method we will just call the scope with the name given in parameter for the ActiveRecord::Relation
    # if no scope is given simply return the ActiveRecord::Relation from super method
    def end_of_association_chain
      if params[:scope].present?
        super.send params[:scope]
      else
        super
      end
    end
  end
end
search.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module InheritedResourcesModules
  # Search
  # This module will use meta_search gem to search in inherited_resources controllers
  # ex:
  # want to search for articles where the author_id is 3
  # /articles/?q[author_id_eq]=3
  # more details with documentation of meta_search
  module Search

    # overwrite method call at the end of the process
    # in this method we will get the parameters needed for the search and apply this search filter to the chain
    def end_of_association_chain
      search = super.search params[:q]
      search.relation
    end
  end
end
pagination.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module InheritedResourcesModules
  # Pagination
  # When you work with a big collection, it can be very convinient to paginate the results
  # including this module, your collection will be paginate using the page given by the parameter page
  module Pagination

    # overwrite method call at the end of the process
    # in this method we will just paginate the result with the params page
    #
    # WARNING:
    # this module should be included at the end because it doesn't return an ActiveRecord::Relation object
    def end_of_association_chain
      chain = super
      chain = chain.page params[:page] if params[:page].present?
      chain
    end
  end
end
order.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module InheritedResourcesModules
  # Order
  # Add the possibility to reorder one api call
  module Order

    # overwrite method call at the end of the process
    # in this method we will check the order paramater require to reorder the collection and extract the table name
    # the column and the order wanted
    # ex:
    # To reorder Article collection by published_at ASC simply call with
    #   /articles.json?order=published_at_asc
    #
    # if wanted to reorder using another table attribute like the name of the author
    #   /articles.json?order=author.name_asc
    def end_of_association_chain
      chain = super
      if params[:order] && params[:order] =~ /^([\w\_\.]+)_(desc|asc)$/
        column = $1
        order  = $2
        table  = params[:controller].singularize.classify.constantize.table_name
        table_column = (column =~ /\./) ? column : "#{table}.#{column}"

        chain = chain.reorder("#{table_column} #{order}")
      end
      chain
    end
  end
end
build_with_current_user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module InheritedResourcesModules
  # BuildWithCurrentUser
  # force the user of the object to be the current user when create resource
  module BuildWithCurrentUser

    # overwrite create method to build the resource with the current user and force the creation of
    # the collection variable @articles for ArticlesController. Need to check why it's necessary to
    # user @articles instead of @article to work with rabl gem
    def create
      set_user
      create!
    end

    def update
      set_user
      update!
    end

    private

    def set_user
      object = build_resource
      object.user_id = current_user.id if user_signed_in?
      set_collection_ivar object
    end
  end
end

Once you put those files in your lib directory and load them at startup your are able to simply add it in your controller. For example, I want to be able to create a product and set the current user, to apply some search, some filter/scope, reorder and paginate my results for my products.

app/controllers/products_controller.rb
1
2
3
4
5
6
7
class ProductsController < InheritedResources::Base
  include InheritedResourcesModules::Order
  include InheritedResourcesModules::Scope
  include InheritedResourcesModules::Search
  include InheritedResourcesModules::BuildWithCurrentUser
  include InheritedResourcesModules::Pagination
end

Done ! you have a 7 lines controller with search, all CRUD actions, paginations… and with this it’s not so hard to maintain or test your controller ;)

Now I just have to create a gem or even a pull request on inherited_resources gem because copy paste between each project is not so good.

NB: In practice you have also to add the function permitted_params in your controller to control which parameters you want accessible with strong_parameters now included by default in Rails 4.

Comments

Copyright © 2014 - Anthony Estebe -