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%')

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s