Rails is very friendly whenever you need to create forms to input data to your web app’s database. Things get a little different when you must have forms and you don’t want to save anything in the database. For that, you have to resort to other ways, maybe creating tableless models.
However, there are some simple cases that even creating a new class seems an overkill, such as forms for searching or filtering data in your app. In these cases, you just want a form that user can pick options and hit a button to see the results. When returning to the user, it is expected to have that form filled with the options the user had chosen before, but there is no simple, clean way to do that with plain old “form_tag”. Here is where our little tip comes in.
OpenStruct is a cool lib that comes with the Ruby Standard Library. “It is like a hash with a different way to access the data” says the documentation:
>> user = OpenStruct.new({:name => 'John', :last_name => 'Doe'}) => #<OpenStruct name="John", last_name="Doe"> >> user.name => "John" >> user.last_name => "Doe" >> user.bla => nil
We can use it to fool our old friend “form_for” helper to think we’re dealing with normal AR objects, so we can create a method that wraps “form_for”, simple as this:
require 'ostruct' module SearchFormHelper def search_form_for(object_name, options={}, &block) options[:html] = {:method => :get}.update(options[:html] || {}) object = OpenStruct.new(params[object_name]) form_for(object_name, object, options, &block) end end
Inside the view, you will do the same way you do with AR models:
<% search_form_for :search do |f| %> <p> <%= f.label :start_at %> <%= f.date_select :start_at %> </p> <p> <%= f.label :end_at %> <%= f.date_select :end_at %> </p> <p> <%= f.submit 'Search' %> </p> <% end %>
That’s pretty much it! If you’re filtering data, by a category for example, try checking the has_scope plugin, works like a charm in combination with this tip, but it is a matter for other post.
And you, reader, do you have any little tricks like this? If you don’t mind, share with us!
Tags: filters, form, rails, search
Posted in English | View Comments
Well, we are in business for almost one year and we are happy to say that quite a lot of applications were already delivered. In each one of them, we were definitely learning and moving toward the Plataforma way of doing things.
So I want to talk the way we treat Rails controllers at Plataforma. With love, of course, but we are going to go deeper than that, specifically in three steps:
1) Using InheritedResources to dry up controllers
2) Splitting your controllers by scope with nested controllers
3) Creating acts_as_* and filters to contain common logic and configuration among controllers
Step 1: Using InheritedResources to dry up controllers
InheritedResources is a tool that allows drying up your controllers, by removing the common logic and taking care of stuff like relationships.
For instance, imagine that you have an Article model which belongs to user. If you want to show all the articles for an specific user, your routes definition and controller are just the following:
# config/routes.rb map.resources :users do |u| u.resources :articles end # app/controllers/articles_controller.rb class ArticlesController < InheritedResources::Base belongs_to :users end
It supports nested and/or polymorphic relationships, I18n and other stuff like named scopes.
Almost all of our controllers inherit from InheritedResources::Base and the first step is as simple as that.
Step 2: Splitting your controllers by scope with nested controllers
Sometime ago, Matt Jankowski from Thoughtbot wrote an excellent blog post on how they deal with scoped controllers, like in the scenario above where we want to show all the articles which belongs to an specific user.
Matt tell us that instead of having an ArticlesController, we should have a Users::ArticlesController, so ArticlesController is available if you want to show all the articles in the application.
Since we read this post, we started to use this setup on our applications as well. However, InheritedResources does not play nice with it by default and let’s check why. Our ArticlesController should be renamed to Users::ArticlesController and be rewritten as:
class Users::ArticlesController < InheritedResources::Base belongs_to :users end
The problem is, according to the namespace Users, InheritedResources appends to all named routes the prefix “users”. But since Articles belongs to :user, it also appends “user”. So the resource_url method will actually call “users_user_article_url”, which is not defined. The fix is simple:
class Users::ArticlesController < InheritedResources::Base defaults :route_prefix => nil belongs_to :users end
Matt’s post contains a lot of tips about using nested controllers and it’s a must-read.
Step 3: Creating acts_as_* and filters to contain common logic and configuration among controllers
As your application grow, you will start to notice that your controllers will have a lot of configuration values and methods for specific scopes. Let’s consider that in our application an user can only see his articles and we are going to retrieve the user from session. So instead of having a route “/users/:user_id/articles/:id” like above, we should just have “/articles/:id” and our controller will be similar to:
class Users::ArticlesController < InheritedResources::Base before_filter :find_user_from_session layout "users" defaults :route_prefix => nil protected # Get the articles scoped to the current user def begin_of_association_chain @current_user end # Simple method to retrieve an user from session def find_user_from_session @current_user ||= User.find(session[:user_id]) end end
With time, we will notice that several controllers use this same pattern, and we need to refactor it. Our first thought would be move part of the logic to ApplicationController, but our ApplicationController will only grow and grow as you add roles (like Admin) to your application.
A better approach would be to move this specific logic to a controller named Users::ApplicationController inside “app/controllers/users/application_controller.rb”:
class Users::ApplicationController < ApplicationController before_filter :find_user_from_session layout "users" protected def begin_of_association_chain @current_user end def find_user_from_session @current_user ||= User.find(session[:user_id]) end end
Now we can inherit from it and get InheritedResources methods by calling inherit_resources in our controller:
class Users::ArticlesController < Users::ApplicationController inherit_resources # the same as inheriting from InheritedResources::Base defaults :route_prefix => nil end
But we are not very happy with this pattern, because we still need to call defaults and set some configuration values (which can be quite a few depending on your application).
What we do instead is create some macros inside a module called Filters. So we create a file at “app/controllers/users/filters.rb” with the following:
module Users::Filters def acts_as_user(options={}) before_filter :find_user_from_session layout "users" defaults options.reverse_merge(:route_prefix => nil) include ControllerMethods end module ControllerMethods protected def begin_of_association_chain @current_user end def find_user_from_session @current_user ||= User.find(session[:user_id]) end end end
And extend this module in our ApplicationController:
class ApplicationController < ActionController::Base extend Users::Filters end
And we can use it simply as:
class Users::ArticlesController < InheritedResources::Base acts_as_user end
The main advantage here is that we have more control of our controller configuration through a single interface. If we need to configure a singleton controller or ensure that most controllers have pagination, we can simply do:
module Users::Filters def acts_as_user(options={}) before_filter :find_user_from_session layout "users" has_scope :paginate, :only => :index unless options.delete(:paginate) == false defaults options.reverse_merge(:route_prefix => nil) include ControllerMethods end
And invoke it as:
acts_as_user :paginate => false acts_as_user :singleton => true acts_as_user :paginate => false, :singleton => true
Wrapping up
As you add more roles to your application, you create more filter. Remember that the important here is to keep your code DRY and is NOT reduce the lines of code. So just place inside those acts_as_* helpers configuration which is common to almost all (if not all) controllers. There is some convention over configuration here, so be sure that the whole team working in the project agrees with them as well. In other words: use it with caution, because we certainly do.
This pattern holds almost all controllers in the last applications we built and we are quite happy with it. Be sure to grab our blog feed (see our sidebar), because we will continue writing about the Plataforma way in next posts!
Tags: filters, inherited_resources, the plataforma way
Posted in English | View Comments

All
English only
Em português apenas