# GroupedScope: Has Many Associations IN (GROUPS) Jack Has Many Things GroupedScope provides an easy way to group objects and to allow those groups to share association collections via existing `has_many` relationships. You may enjoy my original article titled [*Jack has_many :things*](http://metaskills.net/2008/09/28/jack-has_many-things/). ## Installation Install the gem with bundler. We follow a semantic versioning format that tracks ActiveRecord's minor version. So this means to use the latest 3.1.x version of GroupedScope with any ActiveRecord 3.1 version. ```ruby gem 'grouped_scope', '~> 3.1.0' ``` ## Setup To use GroupedScope on a model it must have a `:group_id` column. ```ruby class AddGroupId < ActiveRecord::Migration def up add_column :employees, :group_id, :integer end def down remove_column :employees, :group_id end end ``` ## General Usage Assume the following model. ```ruby class Employee < ActiveRecord::Base has_many :reports grouped_scope :reports end ``` By calling grouped_scope on any association you create a new group accessor for each instance. The object returned will act just like an array and at least include the current object that called it. ```ruby @employee_one.group # => [#] ``` To group resources, just assign the same `:group_id` to each record in that group. ```ruby @employee_one.update_attribute :group_id, 1 @employee_two.update_attribute :group_id, 1 @employee_one.group # => [#, #] ``` Calling grouped_scope on the :reports association leaves the existing association intact. ```ruby @employee_one.reports # => [#] @employee_two.reports # => [#, #] ``` Now the good part, all associations passed to the grouped_scope method can be called on the group proxy. The collection will return resources shared by the group. ```ruby @employee_one.group.reports # => [#, #, #] ``` You can even call scopes or association extensions defined on the objects in the collection defined on the original association. For instance: ```ruby @employee.group.reports.urgent.assigned_to(user) ``` ## Advanced Usage The group scoped object can respond to either `blank?` or `present?` which checks the group's target `group_id` presence or not. We use this internally so that grouped scopes only use grouping SQL when absolutely needed. ```ruby @employee_one = Employee.create :group_id => nil @employee_two = Employee.create :group_id => 38 @employee_one.group.blank? # => true @employee_two.group.present? # => true ``` The object returned by the `#group` method is an ActiveRecord relation on the targets class, in this case `Employee`. Given this, you can further scope the grouped proxy if needed. Below, we use the `:email_present` scope to refine the group down. ```ruby class Employee < ActiveRecord::Base has_many :reports grouped_scope :reports scope :email_present, where("email IS NOT NULL") end @employee_one = Employee.create :group_id => 5, :name => 'Ken' @employee_two = Employee.create :group_id => 5, :name => 'MetaSkills', :email => 'ken@metaskills.net' # Only one employee is returned now. @employee_one.group.email_present # => [# @employee.group).all ``` If you need more control and you are working with the group at a lower level, you can always use the `#ids` or `#ids_sql` methods on the group. ```ruby # Returns primary key array. @employee.group.ids # => [33, 58, 240] # Returns a Arel::Nodes::SqlLiteral object. @employee.group.ids_sql # => 'SELECT "employees"."id" FROM "employees" WHERE "employees"."group_id" = 33' ``` ## Todo List * Raise errors for :finder_sql/:counter_sql. * Add a user definable group_id schema. * Remove SelfGrouping#with_relation, has not yet proved useful. ## Testing Simple! Just clone the repo, then run `bundle install` and `bundle exec rake`. The tests will begin to run. We also use Travis CI to run our tests too. Current build status is: [![Build Status](https://secure.travis-ci.org/metaskills/grouped_scope.png)](http://travis-ci.org/metaskills/grouped_scope) ## License Released under the MIT license. Copyright (c) 2011 Ken Collins