diff --git a/images/arrow-down.png b/images/arrow-down.png new file mode 100644 index 0000000..585b0bd Binary files /dev/null and b/images/arrow-down.png differ diff --git a/images/octocat-small.png b/images/octocat-small.png new file mode 100644 index 0000000..66c2539 Binary files /dev/null and b/images/octocat-small.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..ce1fd01 --- /dev/null +++ b/index.html @@ -0,0 +1,200 @@ + + +
+ + +Has Many Associations IN (GROUPS)
+ +This project is maintained by metaskills
+ + +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.
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.2.x version of GroupedScope with any ActiveRecord 3.2 version.
+ +gem 'grouped_scope', '~> 3.2.0'
+
To use GroupedScope on a model it must have a :group_id
column.
class AddGroupId < ActiveRecord::Migration
+ def up
+ add_column :employees, :group_id, :integer
+ end
+ def down
+ remove_column :employees, :group_id
+ end
+end
+
Assume the following model.
+ +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.
+ +@employee_one.group # => [#<Employee id: 1, group_id: nil>]
+
To group resources, just assign the same :group_id
to each record in that group.
@employee_one.update_attribute :group_id, 1
+@employee_two.update_attribute :group_id, 1
+@employee_one.group # => [#<Employee id: 1, group_id: 1>, #<Employee id: 2, group_id: 1>]
+
Calling grouped_scope on the :reports association leaves the existing association intact.
+ +@employee_one.reports # => [#<Report id: 2, employee_id: 1>]
+@employee_two.reports # => [#<Report id: 18, employee_id: 2>, #<Report id: 36, employee_id: 2>]
+
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.
+ +@employee_one.group.reports # => [#<Report id: 2, employee_id: 1>,
+ #<Report id: 18, employee_id: 2>,
+ #<Report id: 36, employee_id: 2>]
+
You can even call scopes or association extensions defined on the objects in the collection +defined on the original association. For instance:
+ +@employee.group.reports.urgent.assigned_to(user)
+
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.
@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.
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 id: 1, group_id: 5, name: 'MetaSkills', email: 'ken@metaskills.net']
+
We always use raw SQL to get the group ids vs. mapping them to an array and using those in scopes.
+This means that large groups can avoid pushing down hundreds of keys in SQL form. So given an employee
+with a group_id
of 43
and calling @employee.group.reports
, you would get something similar to
+the following SQL.
SELECT "reports".*
+FROM "reports"
+WHERE "reports"."employee_id" IN (
+ SELECT "employees"."id"
+ FROM "employees"
+ WHERE "employees"."group_id" = 43
+)
+
You can pass the group scoped object as a predicate to ActiveRecord's relation interface. In past +versions, this would have treated the group object as an array of IDs. The new behavior is to return +a SQL literal to be used with IN statements. So note, the following would generate SQL similar to +the one above.
+ +Employee.where(:group_id => @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.
# 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'
+
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:
Released under the MIT license. +Copyright (c) 2011 Ken Collins
+