grouped_scope 0.6.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +5 -0
- data/CHANGELOG +13 -0
- data/Gemfile +4 -6
- data/README.md +173 -0
- data/lib/grouped_scope.rb +7 -7
- data/lib/grouped_scope/arish/associations/association_scope.rb +90 -0
- data/lib/grouped_scope/arish/associations/builder/grouped_association.rb +50 -0
- data/lib/grouped_scope/arish/associations/builder/grouped_collection_association.rb +32 -0
- data/lib/grouped_scope/arish/associations/collection_association.rb +25 -0
- data/lib/grouped_scope/arish/base.rb +24 -0
- data/lib/grouped_scope/arish/reflection.rb +18 -0
- data/lib/grouped_scope/arish/relation/predicate_builer.rb +27 -0
- data/lib/grouped_scope/errors.rb +2 -3
- data/lib/grouped_scope/self_grouping.rb +59 -23
- data/lib/grouped_scope/version.rb +2 -4
- data/test/cases/has_many_test.rb +155 -0
- data/test/cases/has_many_through_test.rb +51 -0
- data/test/cases/reflection_test.rb +62 -0
- data/test/cases/self_grouping_test.rb +201 -0
- data/test/helper.rb +48 -35
- metadata +27 -30
- data/README.rdoc +0 -98
- data/lib/grouped_scope/association_reflection.rb +0 -54
- data/lib/grouped_scope/class_methods.rb +0 -32
- data/lib/grouped_scope/core_ext.rb +0 -29
- data/lib/grouped_scope/grouping.rb +0 -9
- data/lib/grouped_scope/has_many_association.rb +0 -28
- data/lib/grouped_scope/has_many_through_association.rb +0 -28
- data/lib/grouped_scope/instance_methods.rb +0 -10
- data/test/grouped_scope/association_reflection_test.rb +0 -73
- data/test/grouped_scope/class_methods_test.rb +0 -51
- data/test/grouped_scope/has_many_association_test.rb +0 -156
- data/test/grouped_scope/has_many_through_association_test.rb +0 -51
- data/test/grouped_scope/self_grouping_test.rb +0 -146
@@ -0,0 +1,18 @@
|
|
1
|
+
module GroupedScope
|
2
|
+
module Arish
|
3
|
+
module Reflection
|
4
|
+
module AssociationReflection
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
attr_accessor :grouped_scope
|
10
|
+
alias :grouped_scope? :grouped_scope
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
ActiveRecord::Reflection::AssociationReflection.send :include, GroupedScope::Arish::Reflection::AssociationReflection
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module GroupedScope
|
2
|
+
module Arish
|
3
|
+
module PredicateBuilder
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
singleton_class.alias_method_chain :build_from_hash, :grouped_scope
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def build_from_hash_with_grouped_scope(engine, attributes, default_table)
|
14
|
+
attributes.select{ |column, value| GroupedScope::SelfGroupping === value }.each do |column_value|
|
15
|
+
column, value = column_value
|
16
|
+
attributes[column] = value.arel_table[column.to_s].in(value.ids_sql)
|
17
|
+
end
|
18
|
+
build_from_hash_without_grouped_scope(engine, attributes, default_table)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ActiveRecord::PredicateBuilder.send :include, GroupedScope::Arish::PredicateBuilder
|
data/lib/grouped_scope/errors.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module GroupedScope
|
2
2
|
class SelfGroupping
|
3
3
|
|
4
|
-
attr_reader :proxy_owner
|
4
|
+
attr_reader :proxy_owner, :reflection
|
5
5
|
|
6
|
-
delegate :
|
6
|
+
delegate :quote_value, :columns_hash, :to => :proxy_class
|
7
7
|
|
8
8
|
[].methods.each do |m|
|
9
|
-
unless m =~
|
10
|
-
delegate m, :to => :
|
9
|
+
unless m =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/
|
10
|
+
delegate m, :to => :grouped_proxy
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -17,45 +17,71 @@ module GroupedScope
|
|
17
17
|
@proxy_owner = proxy_owner
|
18
18
|
end
|
19
19
|
|
20
|
+
def blank?
|
21
|
+
proxy_owner.group_id.blank?
|
22
|
+
end
|
23
|
+
|
24
|
+
def present?
|
25
|
+
!blank?
|
26
|
+
end
|
27
|
+
|
20
28
|
def ids
|
21
|
-
|
29
|
+
grouped_scoped_ids.map(&primary_key.to_sym)
|
30
|
+
end
|
31
|
+
|
32
|
+
def ids_sql
|
33
|
+
Arel.sql(grouped_scoped_ids.to_sql)
|
22
34
|
end
|
23
35
|
|
24
36
|
def quoted_ids
|
25
37
|
ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
|
26
38
|
end
|
27
39
|
|
40
|
+
def with_reflection(reflection)
|
41
|
+
@reflection = reflection
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
@reflection = nil
|
45
|
+
end
|
46
|
+
|
28
47
|
def respond_to?(method, include_private=false)
|
29
|
-
super ||
|
48
|
+
super || proxy_class.grouped_reflections[method].present? || grouped_proxy.respond_to?(method, include_private)
|
30
49
|
end
|
31
50
|
|
32
51
|
|
33
52
|
protected
|
34
53
|
|
35
|
-
def
|
36
|
-
|
54
|
+
def primary_key
|
55
|
+
reflection ? reflection.association_primary_key : proxy_class.primary_key
|
37
56
|
end
|
38
57
|
|
39
|
-
def
|
40
|
-
|
58
|
+
def arel_group_id
|
59
|
+
arel_table['group_id']
|
41
60
|
end
|
42
61
|
|
43
|
-
def
|
44
|
-
|
62
|
+
def arel_primary_key
|
63
|
+
arel_table[primary_key]
|
64
|
+
end
|
65
|
+
|
66
|
+
def arel_table
|
67
|
+
reflection ? Arel::Table.new(reflection.table_name) : proxy_class.arel_table
|
45
68
|
end
|
46
69
|
|
47
|
-
def
|
48
|
-
|
70
|
+
def grouped_proxy
|
71
|
+
@grouped_proxy ||= grouped_scoped
|
72
|
+
end
|
73
|
+
|
74
|
+
def all_grouped?
|
75
|
+
proxy_owner.all_grouped? rescue false
|
49
76
|
end
|
50
77
|
|
51
|
-
def
|
52
|
-
return
|
53
|
-
|
54
|
-
{ :conditions => conditions }
|
78
|
+
def grouped_scoped
|
79
|
+
return proxy_class.scoped if all_grouped?
|
80
|
+
proxy_class.where present? ? arel_group_id.eq(proxy_owner.group_id) : arel_primary_key.eq(proxy_owner.id)
|
55
81
|
end
|
56
82
|
|
57
|
-
def
|
58
|
-
|
83
|
+
def grouped_scoped_ids
|
84
|
+
grouped_scoped.select(arel_primary_key)
|
59
85
|
end
|
60
86
|
|
61
87
|
def proxy_class
|
@@ -65,9 +91,19 @@ module GroupedScope
|
|
65
91
|
|
66
92
|
private
|
67
93
|
|
68
|
-
def method_missing(method, *args
|
69
|
-
if proxy_class.
|
70
|
-
|
94
|
+
def method_missing(method, *args)
|
95
|
+
if proxy_class.grouped_reflections[method]
|
96
|
+
if block_given?
|
97
|
+
proxy_owner.send(:"grouped_scope_#{method}", *args) { |*block_args| yield(*block_args) }
|
98
|
+
else
|
99
|
+
proxy_owner.send(:"grouped_scope_#{method}", *args)
|
100
|
+
end
|
101
|
+
elsif grouped_proxy.respond_to?(method)
|
102
|
+
if block_given?
|
103
|
+
grouped_proxy.send(method, *args) { |*block_args| yield(*block_args) }
|
104
|
+
else
|
105
|
+
grouped_proxy.send(method, *args)
|
106
|
+
end
|
71
107
|
else
|
72
108
|
super
|
73
109
|
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class GroupedScope::HasManyTest < GroupedScope::TestCase
|
4
|
+
|
5
|
+
describe 'For an Employee' do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@employee = FactoryGirl.create(:employee)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'scopes existing association to owner' do
|
12
|
+
assert_sql(/"employee_id" = #{@employee.id}/) do
|
13
|
+
@employee.reports(true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'scopes group association to owner when no group present' do
|
18
|
+
assert_sql(/"employee_id" = #{@employee.id}/) do
|
19
|
+
@employee.group.reports(true)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'scopes group association to owner when group present' do
|
24
|
+
@employee.update_attribute :group_id, 43
|
25
|
+
assert_sql(/"employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = 43\)/) do
|
26
|
+
@employee.group.reports(true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'for counting sql' do
|
31
|
+
|
32
|
+
before do
|
33
|
+
@e1 = FactoryGirl.create(:employee_with_reports, :group_id => 1)
|
34
|
+
@e2 = FactoryGirl.create(:employee_with_reports, :group_id => 1)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'scope count sql to owner' do
|
38
|
+
assert_sql(/SELECT COUNT\(\*\)/,/"employee_id" = #{@e1.id}/) do
|
39
|
+
@e1.reports(true).count
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'scope count sql to group' do
|
44
|
+
assert_sql(/SELECT COUNT\(\*\)/,/"employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = #{@e1.group_id}\)/) do
|
45
|
+
@e1.group.reports(true).count
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'have a group count equal to sum of seperate owner counts' do
|
50
|
+
assert_equal @e1.reports(true).count + @e2.reports(true).count, @e2.group.reports(true).count
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'training association extensions' do
|
56
|
+
|
57
|
+
before do
|
58
|
+
@e1 = FactoryGirl.create(:employee_with_urgent_reports, :group_id => 1)
|
59
|
+
@e2 = FactoryGirl.create(:employee, :group_id => 1)
|
60
|
+
@urgent_reports = @e1.reports.select(&:urgent_title?)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'find urgent report via normal ungrouped association' do
|
64
|
+
assert_same_elements @urgent_reports, @e1.reports(true).urgent
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'find urgent report via grouped reflection' do
|
68
|
+
assert_same_elements @urgent_reports, @e2.group.reports(true).urgent
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'use association extension SQL along with group reflection' do
|
72
|
+
assert_sql(select_from_reports, where_for_groups(@e2.group_id), where_for_urgent_title) do
|
73
|
+
@e2.group.reports.urgent
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'training named scopes' do
|
80
|
+
|
81
|
+
before do
|
82
|
+
@e1 = FactoryGirl.create(:employee_with_urgent_reports, :group_id => 1)
|
83
|
+
@e2 = FactoryGirl.create(:employee, :group_id => 1)
|
84
|
+
@urgent_titles = @e1.reports.select(&:urgent_title?)
|
85
|
+
@urgent_bodys = @e1.reports.select(&:urgent_body?)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'find urgent reports via normal named scopes by normal owner' do
|
89
|
+
assert_same_elements @urgent_titles, @e1.reports(true).with_urgent_title
|
90
|
+
assert_same_elements @urgent_bodys, @e1.reports(true).with_urgent_body
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'find urgent reports via group reflection by group member' do
|
94
|
+
assert_same_elements @urgent_titles, @e2.group.reports(true).with_urgent_title
|
95
|
+
assert_same_elements @urgent_bodys, @e2.group.reports(true).with_urgent_body
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'use named scope SQL along with group reflection' do
|
99
|
+
assert_sql(select_from_reports, where_for_groups(@e2.group_id), where_for_urgent_body, where_for_urgent_title) do
|
100
|
+
@e2.group.reports.with_urgent_title.with_urgent_body.inspect
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'For a LegacyEmployee' do
|
109
|
+
|
110
|
+
before do
|
111
|
+
@employee = FactoryGirl.create(:legacy_employee)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'scope existing association to owner' do
|
115
|
+
assert_sql(/"legacy_reports"."email" = '#{@employee.id}'/) do
|
116
|
+
@employee.reports(true)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'scope group association to owner, since no group is present' do
|
121
|
+
assert_sql(/"legacy_reports"."email" = '#{@employee.id}'/) do
|
122
|
+
@employee.group.reports(true)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'scopes group association to owners group when present' do
|
127
|
+
@employee.update_attribute :group_id, 43
|
128
|
+
assert_sql(/"legacy_reports"."email" IN \(SELECT "legacy_employees"\."email" FROM "legacy_employees" WHERE "legacy_employees"\."group_id" = 43\)/) do
|
129
|
+
@employee.group.reports(true)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
protected
|
137
|
+
|
138
|
+
def select_from_reports
|
139
|
+
/SELECT "reports"\.\* FROM "reports"/
|
140
|
+
end
|
141
|
+
|
142
|
+
def where_for_groups(id)
|
143
|
+
/WHERE "reports"."employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = #{id}\)/
|
144
|
+
end
|
145
|
+
|
146
|
+
def where_for_urgent_body
|
147
|
+
/WHERE.*body LIKE '%URGENT%'/
|
148
|
+
end
|
149
|
+
|
150
|
+
def where_for_urgent_title
|
151
|
+
/WHERE.*"?reports"?."?title"? = 'URGENT'/
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class GroupedScope::HasManyThroughTest < GroupedScope::TestCase
|
4
|
+
|
5
|
+
before do
|
6
|
+
@e1 = FactoryGirl.create(:employee, :group_id => 1)
|
7
|
+
@e1.departments << Department.hr << Department.finance
|
8
|
+
@e2 = FactoryGirl.create(:employee, :group_id => 1)
|
9
|
+
@e2.departments << Department.it
|
10
|
+
@all_group_departments = [Department.hr, Department.it, Department.finance]
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'For default association' do
|
14
|
+
|
15
|
+
it 'scope to owner' do
|
16
|
+
assert_sql(/"employee_id" = #{@e1.id}/) do
|
17
|
+
@e1.departments(true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'scope count to owner' do
|
22
|
+
assert_sql(/"employee_id" = #{@e1.id}/) do
|
23
|
+
@e1.departments(true).count
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'For grouped association' do
|
30
|
+
|
31
|
+
it 'scope to group' do
|
32
|
+
|
33
|
+
assert_sql(/"employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = #{@e1.group_id}\)/) do
|
34
|
+
@e2.group.departments(true)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'scope count to group' do
|
39
|
+
assert_sql(/"employee_id" IN \(SELECT "employees"\."id" FROM "employees" WHERE "employees"\."group_id" = #{@e1.group_id}\)/) do
|
40
|
+
@e1.group.departments(true).count
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'have a group count equal to sum of separate owner counts' do
|
45
|
+
assert_equal @e1.departments(true).count + @e2.departments(true).count, @e2.group.departments(true).count
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class GroupedScope::ReflectionTest < GroupedScope::TestCase
|
4
|
+
|
5
|
+
it 'creates a has_many association named :grouped_scope_* using existing association as a suffix' do
|
6
|
+
assert FactoryGirl.create(:employee).respond_to?(:grouped_scope_reports)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'does not add the #grouped_scope to existing reflection' do
|
10
|
+
assert_nil Employee.reflections[:reports].grouped_scope?
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should have added the #grouped_scope to new grouped reflection' do
|
14
|
+
assert Employee.reflections[:grouped_scope_reports].grouped_scope?
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'Class attribute for #grouped_reflections' do
|
18
|
+
|
19
|
+
it 'should one' do
|
20
|
+
assert_instance_of Hash, Employee.grouped_reflections
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'populate with new grouped scopes' do
|
24
|
+
assert_nil Employee.grouped_reflections[:newgroupes]
|
25
|
+
Employee.class_eval { has_many(:newgroupes) ; grouped_scope(:newgroupes) }
|
26
|
+
assert Employee.grouped_reflections[:newgroupes]
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'Raise and exception' do
|
32
|
+
|
33
|
+
it 'when a association does not exist' do
|
34
|
+
begin
|
35
|
+
raised = false
|
36
|
+
Employee.class_eval{ grouped_scope(:doesnotexist) }
|
37
|
+
rescue ArgumentError => e
|
38
|
+
raised = true
|
39
|
+
e.message.must_match %r{Cannot create a group scope for :doesnotexist}
|
40
|
+
ensure
|
41
|
+
assert raised, 'Did not raise an ArgumentError'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'when the association is not a has_many or a has_and_belongs_to_many' do
|
46
|
+
begin
|
47
|
+
raised = false
|
48
|
+
Employee.class_eval { belongs_to(:belongstowillnotwork) ; grouped_scope(:belongstowillnotwork) }
|
49
|
+
rescue ArgumentError => e
|
50
|
+
raised = true
|
51
|
+
e.message.must_match %r{:belongstowillnotwork.*the reflection is blank or not supported}
|
52
|
+
ensure
|
53
|
+
assert raised, 'Did not raise an ArgumentError'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
end
|