Skip to content

Commit

Permalink
Use #ids_sql in all cases. Update docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
metaskills committed Dec 6, 2011
1 parent 50578c4 commit 06ecdf6
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 19 deletions.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ defined on the original association. For instance:

## 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.
Expand All @@ -105,9 +117,40 @@ end
@employee_one.group.email_present # => [#<Employee id: 1, group_id: 5, name: 'MetaSkills', email: '[email protected]']
```

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.

```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.

```ruby
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.

```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
Expand Down
2 changes: 1 addition & 1 deletion lib/grouped_scope/arish/associations/association_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def add_constraints(scope)
# GroupedScope changed this line.
# scope = scope.where(table[key].eq(owner[foreign_key]))
scope = if owner.group.present?
scope.where(table[key].in(owner.group.ids))
scope.where(table[key].in(owner.group.ids_sql))
else
scope.where(table[key].eq(owner[foreign_key]))
end
Expand Down
6 changes: 3 additions & 3 deletions lib/grouped_scope/arish/relation/predicate_builer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ module PredicateBuilder
module ClassMethods

def build_from_hash_with_grouped_scope(engine, attributes, default_table)
attributes.select{ |k,v| GroupedScope::SelfGroupping === v }.each do |kv|
k, v = kv
attributes[k] = v.ids
attributes.select{ |column, value| GroupedScope::SelfGroupping === value }.each do |column_value|
column, value = column_value
attributes[column] = value.arel_table[column.to_s].in(value.ids_sql)
end
build_from_hash_without_grouped_scope(engine, attributes, default_table)
end
Expand Down
1 change: 0 additions & 1 deletion lib/grouped_scope/self_grouping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def ids_sql
Arel.sql(grouped_scoped_ids.to_sql)
end

# TODO: Note this.
def quoted_ids
ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
end
Expand Down
16 changes: 8 additions & 8 deletions test/grouped_scope/has_many_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class GroupedScope::HasManyTest < GroupedScope::TestCase

it 'scopes group association to owner when group present' do
@employee.update_attribute :group_id, 43
assert_sql(/"employee_id" IN \(#{@employee.id}\)/) do
assert_sql(/"employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = 43\)/) do
@employee.group.reports(true)
end
end
Expand All @@ -41,7 +41,7 @@ class GroupedScope::HasManyTest < GroupedScope::TestCase
end

it 'scope count sql to group' do
assert_sql(/SELECT COUNT\(\*\)/,/"employee_id" IN \(#{@e1.id}, #{@e2.id}\)/) do
assert_sql(/SELECT COUNT\(\*\)/,/"employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = #{@e1.group_id}\)/) do
@e1.group.reports(true).count
end
end
Expand All @@ -68,8 +68,8 @@ class GroupedScope::HasManyTest < GroupedScope::TestCase
assert_same_elements @urgent_reports, @e2.group.reports(true).urgent
end

it 'use assoc extension SQL along with group reflection' do
assert_sql(select_from_reports, where_for_groups, where_for_urgent_title) do
it 'use association extension SQL along with group reflection' do
assert_sql(select_from_reports, where_for_groups(@e2.group_id), where_for_urgent_title) do
@e2.group.reports.urgent
end
end
Expand All @@ -96,7 +96,7 @@ class GroupedScope::HasManyTest < GroupedScope::TestCase
end

it 'use named scope SQL along with group reflection' do
assert_sql(select_from_reports, where_for_groups, where_for_urgent_body, where_for_urgent_title) do
assert_sql(select_from_reports, where_for_groups(@e2.group_id), where_for_urgent_body, where_for_urgent_title) do
@e2.group.reports.with_urgent_title.with_urgent_body.inspect
end
end
Expand Down Expand Up @@ -125,7 +125,7 @@ class GroupedScope::HasManyTest < GroupedScope::TestCase

it 'scopes group association to owners group when present' do
@employee.update_attribute :group_id, 43
assert_sql(/"legacy_reports"."email" IN \('#{@employee.id}'\)/) do
assert_sql(/"legacy_reports"."email" IN \(SELECT "legacy_employees"\."email" FROM "legacy_employees" WHERE "legacy_employees"\."group_id" = 43\)/) do
@employee.group.reports(true)
end
end
Expand All @@ -139,8 +139,8 @@ def select_from_reports
/SELECT "reports"\.\* FROM "reports"/
end

def where_for_groups
/WHERE "reports"."employee_id" IN \(2, 3\)/
def where_for_groups(id)
/WHERE "reports"."employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = #{id}\)/
end

def where_for_urgent_body
Expand Down
5 changes: 3 additions & 2 deletions test/grouped_scope/has_many_through_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ class GroupedScope::HasManyThroughTest < GroupedScope::TestCase
describe 'For grouped association' do

it 'scope to group' do
assert_sql(/"employee_id" IN \(#{@e1.id}, #{@e2.id}\)/) do

assert_sql(/"employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = #{@e1.group_id}\)/) do
@e2.group.departments(true)
end
end

it 'scope count to group' do
assert_sql(/"employee_id" IN \(#{@e1.id}, #{@e2.id}\)/) do
assert_sql(/"employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = #{@e1.group_id}\)/) do
@e1.group.departments(true).count
end
end
Expand Down
10 changes: 6 additions & 4 deletions test/grouped_scope/self_grouping_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ class GroupedScope::SelfGrouppingTest < GroupedScope::TestCase
lambda{ GroupedScope::SelfGroupping.new(FooBar.new) }.must_raise(GroupedScope::NoGroupIdError)
end

it 'return correct attribute_condition for GroupedScope::SelfGroupping object' do
assert_sql(/"?group_id"? IN \(#{@employee.id}\)/) do
Employee.find :all, :conditions => {:group_id => @employee.group}
end
it 'return correct predicate for GroupedScope::SelfGroupping object' do
@employee.update_attribute :group_id, 82
expected_sql = /"group_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = 82/
assert_sql(expected_sql) { Employee.where(:group_id => @employee.group).all }
assert_sql(expected_sql) { Employee.all(:conditions => {:group_id => @employee.group}) }
assert_equal [@employee], Employee.where(:group_id => @employee.group).all
end

it 'allows you to ask if the group is present' do
Expand Down

0 comments on commit 06ecdf6

Please sign in to comment.