Rails redirect_back_or_default

In a recent project I found myself writing recirect_to :back alot, and then found myself worrying that what if for some reason there is no :back.

Drawing inspiration from this blog, I wrote two helpers in my application_controller.rb.

The store_location stores current URI (or referer URI in case of non-GET request) into session[:return_to] for later usage:

#!ruby
def store_location
  session[:return_to] = if request.get?
    request.request_uri
  else
    request.referer
  end
end

And redirect_back_or_default tries its best to redirect the user to somewhere, in the following order:

  1. previously stored session[:return_to]
  2. Referer URI
  3. Given default URI
  4. or root_url if all else fails

The code itself

#!ruby
def redirect_back_or_default(default = root_url, options)
  redirect_to(session.delete(:return_to) || request.referer || default, options)
end

I’ve found that when rewriting redirect_to :back, notice: 'something' into redirect_back_or_default-call, adding this alias helps:

#!ruby
alias_method :redirect_to_back_or_default, :redirect_back_or_default

But of course, if you are testing your code (and you should be), it’s better to stick to one variant of above and use tests to catch all erroneous incarnations.

Rails 3: Merge scopes

I run into a case where I had User.search method and I wanted the GroupMember model be searchable by the user’s attributes. The most DRY way to accomplish this in Rails 3 is to merge scopes. In the User model:

#!ruby
# user.rb
class User < ActiveRecord::Base
  has_many :memberships, :class_name => "GroupMember", :foreign_key => "user_id"

  def self.search(search)
    if search.present?
      query = []
      params = []
      %w(uid email name).each do |field|
        # The field name must be fully qualified to merge scopes
        query << "#{self.table_name}.#{field} LIKE ?"
        params << "%#{search}%"
      end
      query = query.join(" OR ")
      where(query, *params)
    else
      scoped
    end
  end
end

NB! It’s important to have the User’s field names fully qualified so that they won’t be applied to the GroupMember table. And in the GroupMember model:

#!ruby
# group_member.rb
class GroupMember < ActiveRecord::Base
  belongs_to :user
  belongs_to :group

  def self.search(search)
    if search.present?
      # We search GroupMembers by the user attributes
      scoped.joins(:user).merge(User.search(search))
    else
      scoped
    end
  end
end

Now it’s possible to search for GroupMembers by the User attributes:

#!ruby
group = Group.find 1
group.group_members.search('david')

This results in SQL query:

#!sql
SELECT "group_members".* FROM "group_members" INNER JOIN "users"
ON "users"."id" = "group_members"."user_id" WHERE "group_members"."group_id" = 1
AND (users.uid LIKE '%david%' OR users.email LIKE '%david%'
OR users.name LIKE '%david%')