metaskills-grouped_scope 0.5.1

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.
@@ -0,0 +1,15 @@
1
+ require 'grouped_scope/errors'
2
+ require 'grouped_scope/grouping'
3
+ require 'grouped_scope/self_grouping'
4
+ require 'grouped_scope/association_reflection'
5
+ require 'grouped_scope/class_methods'
6
+ require 'grouped_scope/has_many_association'
7
+ require 'grouped_scope/has_many_through_association'
8
+ require 'grouped_scope/core_ext'
9
+
10
+ module GroupedScope
11
+
12
+ VERSION = '0.5.1'
13
+
14
+ end
15
+
data/test/factories.rb ADDED
@@ -0,0 +1,46 @@
1
+
2
+ Factory.sequence(:id) { |n| n }
3
+ Factory.sequence(:email) { |n| "test_#{n}@domain.com" }
4
+ Factory.sequence(:title) { |n| "Report Title ##{n}" }
5
+
6
+ Factory.define :employee do |e|
7
+ e.name { "Factory Employee ##{Factory.next(:id)}" }
8
+ e.email { Factory.next(:email) }
9
+ end
10
+
11
+ Factory.define :report do |r|
12
+ r.title { Factory.next(:title) }
13
+ r.body 'Bla bla bla. Bla. Bla bla.'
14
+ end
15
+
16
+ Factory.define :employee_with_reports, :class => 'Employee' do |e|
17
+ e.name { "Factory Employee ##{Factory.next(:id)}" }
18
+ e.email { Factory.next(:email) }
19
+ e.reports { |employee| [employee.association(:report),employee.association(:report)] }
20
+ end
21
+
22
+ Factory.define :employee_with_urgent_reports, :class => 'Employee' do |e|
23
+ e.name { "Factory Employee ##{Factory.next(:id)}" }
24
+ e.email { Factory.next(:email) }
25
+ e.reports { |employee| [employee.association(:report), employee.association(:report,:title=>'URGENT'),
26
+ employee.association(:report), employee.association(:report,:body=>'This is URGENT.')] }
27
+ end
28
+
29
+
30
+ Factory.define :legacy_employee do |e|
31
+ e.name { "Legacy Factory Employee ##{Factory.next(:id)}" }
32
+ e.email { Factory.next(:email) }
33
+ end
34
+
35
+ Factory.define :legacy_report do |r|
36
+ r.title { Factory.next(:title) }
37
+ r.body 'Legacy bla bla. Legacy. Legacy bla.'
38
+ end
39
+
40
+ Factory.define :legacy_employee_with_reports, :class => 'LegacyEmployee' do |e|
41
+ e.name { "Legacy Factory Employee ##{Factory.next(:id)}" }
42
+ e.email { Factory.next(:email) }
43
+ e.reports { |employee| [employee.association(:legacy_report),employee.association(:legacy_report)] }
44
+ end
45
+
46
+
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/../helper'
2
+
3
+ class GroupedScope::AssociationReflectionTest < GroupedScope::TestCase
4
+
5
+ def setup
6
+ setup_environment
7
+ end
8
+
9
+ context 'For initialization' do
10
+
11
+ context 'Raise and exception' do
12
+
13
+ setup { @reflection_klass = GroupedScope::AssociationReflection }
14
+
15
+ should 'when a association does not exist' do
16
+ assert_raise(ArgumentError) { @reflection_klass.new(Employee,:foobars) }
17
+ end
18
+
19
+ should 'when the association is not a has_many or a has_and_belongs_to_many' do
20
+ Employee.class_eval { belongs_to(:foo) }
21
+ assert_raise(ArgumentError) { @reflection_klass.new(Employee,:foo) }
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+
29
+ context 'For #ungrouped_reflection' do
30
+
31
+ setup do
32
+ @ungrouped_reflection = Employee.reflections[:reports]
33
+ @grouped_reflection = Employee.reflections[:grouped_scope_reports]
34
+ end
35
+
36
+ should 'access ungrouped reflection' do
37
+ assert_equal @ungrouped_reflection, @grouped_reflection.ungrouped_reflection
38
+ end
39
+
40
+ should 'delegate instance methods to #ungrouped_reflection' do
41
+ methods = [:class_name,:klass,:table_name,:primary_key_name,:active_record,
42
+ :association_foreign_key,:counter_cache_column,:source_reflection]
43
+ # CHANGED [Rails 1.2.6] Account for quoted_table_name.
44
+ methods << :quoted_table_name if @ungrouped_reflection.respond_to?(:quoted_table_name)
45
+ methods.each do |m|
46
+ assert_equal @ungrouped_reflection.send(m), @grouped_reflection.send(m),
47
+ "The method #{m.inspect} does not appear to be proxied to the ungrouped reflection."
48
+ end
49
+ end
50
+
51
+ should 'not delegate to #ungrouped_reflection for #options and #name' do
52
+ assert_not_equal @ungrouped_reflection.name, @grouped_reflection.name
53
+ assert_not_equal @ungrouped_reflection.options, @grouped_reflection.options
54
+ end
55
+
56
+ should 'derive class name to same as ungrouped reflection' do
57
+ # CHANGED [Rails 1.2.6] Account for quoted_table_name.
58
+ if @ungrouped_reflection.respond_to?(:derive_class_name)
59
+ assert_equal @ungrouped_reflection.send(:derive_class_name), @grouped_reflection.send(:derive_class_name)
60
+ end
61
+ end
62
+
63
+ should 'derive primary key name to same as ungrouped reflection' do
64
+ # CHANGED [Rails 1.2.6] Account for quoted_table_name.
65
+ if @ungrouped_reflection.respond_to?(:derive_primary_key_name)
66
+ assert_equal @ungrouped_reflection.send(:derive_primary_key_name), @grouped_reflection.send(:derive_primary_key_name)
67
+ end
68
+ end
69
+
70
+ should 'honor explicit legacy reports association options like class_name and foreign_key' do
71
+ @ungrouped_reflection = LegacyEmployee.reflections[:reports]
72
+ @grouped_reflection = LegacyEmployee.reflections[:grouped_scope_reports]
73
+ [:class_name,:primary_key_name].each do |m|
74
+ assert_equal @ungrouped_reflection.send(m), @grouped_reflection.send(m),
75
+ "The method #{m.inspect} does not appear to be proxied to the ungrouped reflection."
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+
82
+
83
+ end
@@ -0,0 +1,51 @@
1
+ require File.dirname(__FILE__) + '/../helper'
2
+
3
+ class GroupedScope::ClassMethodsTest < GroupedScope::TestCase
4
+
5
+ def setup
6
+ setup_environment
7
+ end
8
+
9
+ context 'For .grouped_scopes' do
10
+
11
+ should 'have a inheritable attribute hash' do
12
+ assert_instance_of Hash, Employee.grouped_scopes
13
+ end
14
+
15
+ should 'add to inheritable attributes with new grouped_scope' do
16
+ Employee.grouped_scopes[:foobars] = nil
17
+ Employee.class_eval { has_many(:foobars) ; grouped_scope(:foobars) }
18
+ assert Employee.grouped_scopes[:foobars]
19
+ end
20
+
21
+ end
22
+
23
+ context 'For .grouped_scope' do
24
+
25
+ should 'create a belongs_to :grouping association' do
26
+ assert Employee.reflections[:grouping]
27
+ end
28
+
29
+ should 'not recreate belongs_to :grouping on additional calls' do
30
+ Employee.stubs(:belongs_to).never
31
+ Employee.class_eval { has_many(:foobars) ; grouped_scope(:foobars) }
32
+ end
33
+
34
+ should 'create a has_many assoc named :grouped_scope_* using existing association as a suffix' do
35
+ grouped_reports_assoc = Employee.reflections[:grouped_scope_reports]
36
+ assert_instance_of GroupedScope::AssociationReflection, grouped_reports_assoc
37
+ assert Factory(:employee).respond_to?(:grouped_scope_reports)
38
+ end
39
+
40
+ should 'not add the :grouped_scope option to existing reflection' do
41
+ assert_nil Employee.reflections[:reports].options[:grouped_scope]
42
+ end
43
+
44
+ should 'have added the :grouped_scope option to new grouped reflection' do
45
+ assert Employee.reflections[:grouped_scope_reports].options[:grouped_scope]
46
+ end
47
+
48
+ end
49
+
50
+
51
+ end
@@ -0,0 +1,157 @@
1
+ require File.dirname(__FILE__) + '/../helper'
2
+
3
+ class GroupedScope::HasManyAssociationTest < GroupedScope::TestCase
4
+
5
+ def setup
6
+ setup_environment
7
+ end
8
+
9
+
10
+ context 'For an Employee' do
11
+
12
+ setup do
13
+ @employee = Factory(:employee)
14
+ end
15
+
16
+ should 'scope existing association to owner' do
17
+ assert_sql(/employee_id = #{@employee.id}/) do
18
+ @employee.reports(true)
19
+ end
20
+ end
21
+
22
+ should 'scope group association to group' do
23
+ assert_sql(/employee_id IN \(#{@employee.id}\)/) do
24
+ @employee.group.reports(true)
25
+ end
26
+ end
27
+
28
+ context 'for counting sql' do
29
+
30
+ setup do
31
+ @e1 = Factory(:employee_with_reports, :group_id => 1)
32
+ @e2 = Factory(:employee_with_reports, :group_id => 1)
33
+ end
34
+
35
+ should 'scope count sql to owner' do
36
+ assert_sql(/SELECT count\(\*\)/,/employee_id = #{@e1.id}/) do
37
+ @e1.reports(true).count
38
+ end
39
+ end
40
+
41
+ should 'scope count sql to group' do
42
+ assert_sql(/SELECT count\(\*\)/,/employee_id IN \(#{@e1.id},#{@e2.id}\)/) do
43
+ @e1.group.reports(true).count
44
+ end
45
+ end
46
+
47
+ should 'have a group count equal to sum of seperate owner counts' do
48
+ assert_equal @e1.reports(true).count + @e2.reports(true).count, @e2.group.reports(true).count
49
+ end
50
+
51
+ end
52
+
53
+ context 'training association extensions' do
54
+
55
+ setup do
56
+ @e1 = Factory(:employee_with_urgent_reports, :group_id => 1)
57
+ @e2 = Factory(:employee, :group_id => 1)
58
+ @urgent_reports = @e1.reports.select(&:urgent_title?)
59
+ end
60
+
61
+ should 'find urgent report via normal ungrouped association' do
62
+ assert_same_elements @urgent_reports, @e1.reports(true).urgent
63
+ end
64
+
65
+ should 'find urgent report via grouped reflection' do
66
+ assert_same_elements @urgent_reports, @e2.group.reports(true).urgent
67
+ end
68
+
69
+ should 'use assoc extension SQL along with group reflection' do
70
+ assert_sql(select_from_reports, where_for_groups, where_for_urgent_title) do
71
+ @e2.group.reports.urgent
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ context 'training named scopes' do
78
+
79
+ setup do
80
+ @e1 = Factory(:employee_with_urgent_reports, :group_id => 1)
81
+ @e2 = Factory(:employee, :group_id => 1)
82
+ @urgent_titles = @e1.reports.select(&:urgent_title?)
83
+ @urgent_bodys = @e1.reports.select(&:urgent_body?)
84
+ end
85
+
86
+ should 'find urgent reports via normal named scopes by normal owner' do
87
+ assert_same_elements @urgent_titles, @e1.reports(true).with_urgent_title
88
+ assert_same_elements @urgent_bodys, @e1.reports(true).with_urgent_body
89
+ end
90
+
91
+ should 'find urgent reports via group reflection by group member' do
92
+ assert_same_elements @urgent_titles, @e2.group.reports(true).with_urgent_title
93
+ assert_same_elements @urgent_bodys, @e2.group.reports(true).with_urgent_body
94
+ end
95
+
96
+ should 'use named scope SQL along with group reflection' do
97
+ assert_sql(select_from_reports, where_for_groups, where_for_urgent_body, where_for_urgent_title) do
98
+ @e2.group.reports.with_urgent_title.with_urgent_body.inspect
99
+ end
100
+ end
101
+
102
+ context 'with will paginate' do
103
+
104
+ should 'use group reflection, named scope, and paginate SQL' do
105
+ where_ends_with_limits = /WHERE.*LIMIT 2 OFFSET 0/
106
+ assert_sql(select_from_reports, where_for_groups, where_for_urgent_body, where_for_urgent_title, where_ends_with_limits) do
107
+ @e2.group.reports.with_urgent_title.with_urgent_body.paginate(:page=>1,:per_page=>2)
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+ context 'For a LegacyEmployee' do
118
+
119
+ setup do
120
+ @employee = Factory(:legacy_employee)
121
+ end
122
+
123
+ should 'scope existing association to owner' do
124
+ assert_sql(/"?legacy_reports"?.email = '#{@employee.id}'/) do
125
+ @employee.reports(true)
126
+ end
127
+ end
128
+
129
+ should 'scope group association to group' do
130
+ assert_sql(/"?legacy_reports"?.email IN \('#{@employee.id}'\)/) do
131
+ @employee.group.reports(true)
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+
138
+ protected
139
+
140
+ def select_from_reports
141
+ /SELECT \* FROM "?reports"?/
142
+ end
143
+
144
+ def where_for_groups
145
+ /WHERE.*"?reports"?.employee_id IN \(2,3\)/
146
+ end
147
+
148
+ def where_for_urgent_body
149
+ /WHERE.*body LIKE '%URGENT%'/
150
+ end
151
+
152
+ def where_for_urgent_title
153
+ /WHERE.*"?reports"?."?title"? = 'URGENT'/
154
+ end
155
+
156
+
157
+ end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/../helper'
2
+
3
+ class GroupedScope::HasManyThroughAssociationTest < GroupedScope::TestCase
4
+
5
+ def setup
6
+ setup_environment
7
+ @e1 = Factory(:employee, :group_id => 1)
8
+ @e1.departments << Department.hr << Department.finance
9
+ @e2 = Factory(:employee, :group_id => 1)
10
+ @e2.departments << Department.it
11
+ @all_group_departments = [Department.hr, Department.it, Department.finance]
12
+ end
13
+
14
+
15
+ context 'For default association' do
16
+
17
+ should 'scope to owner' do
18
+ assert_sql(/employee_id = #{@e1.id}/) do
19
+ @e1.departments(true)
20
+ end
21
+ end
22
+
23
+ should 'scope count to owner' do
24
+ assert_sql(/employee_id = #{@e1.id}/) do
25
+ @e1.departments(true).count
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ context 'For grouped association' do
32
+
33
+ should 'scope to group' do
34
+ assert_sql(/employee_id IN \(#{@e1.id},#{@e2.id}\)/) do
35
+ @e2.group.departments(true)
36
+ end
37
+ end
38
+
39
+ should 'scope count to group' do
40
+ assert_sql(/employee_id IN \(#{@e1.id},#{@e2.id}\)/) do
41
+ @e1.group.departments(true).count
42
+ end
43
+ end
44
+
45
+ should 'have a group count equal to sum of seperate owner counts' do
46
+ assert_equal @e1.departments(true).count + @e2.departments(true).count, @e2.group.departments(true).count
47
+ end
48
+
49
+ end
50
+
51
+
52
+
53
+ end
@@ -0,0 +1,147 @@
1
+ require File.dirname(__FILE__) + '/../helper'
2
+
3
+ class GroupedScope::SelfGrouppingTest < GroupedScope::TestCase
4
+
5
+ def setup
6
+ setup_environment
7
+ end
8
+
9
+ context 'General behavior' do
10
+
11
+ setup do
12
+ @employee = Factory(:employee)
13
+ end
14
+
15
+ should 'return an array' do
16
+ assert_instance_of Array, @employee.group
17
+ end
18
+
19
+ should 'return #ids array' do
20
+ assert_equal [@employee.id], @employee.group.ids
21
+ end
22
+
23
+ should 'return #quoted_ids string for use in sql statments' do
24
+ assert_equal "#{@employee.id}", @employee.group.quoted_ids
25
+ end
26
+
27
+ should 'respond true to grouped associations' do
28
+ assert @employee.group.respond_to?(:reports)
29
+ end
30
+
31
+ should 'raise a GroupedScope::NoGroupIdError exception for objects with no group_id schema' do
32
+ assert_does_not_contain FooBar.column_names, 'group_id'
33
+ assert_raise(GroupedScope::NoGroupIdError) { GroupedScope::SelfGroupping.new(FooBar.new) }
34
+ end
35
+
36
+ should 'return correct attribute_condition for GroupedScope::SelfGroupping object' do
37
+ assert_sql(/"?group_id"? IN \(#{@employee.id}\)/) do
38
+ Employee.find :all, :conditions => {:group_id => @employee.group}
39
+ end
40
+ end
41
+
42
+ context 'for Array delegates' do
43
+
44
+ should 'respond to first/last' do
45
+ [:first,:last].each do |method|
46
+ assert @employee.group.respond_to?(method), "Should respond to #{method.inspect}"
47
+ end
48
+ end
49
+
50
+ should 'respond to each' do
51
+ assert @employee.group.respond_to?(:each)
52
+ @employee.group.each do |employee|
53
+ assert_instance_of Employee, employee
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ context 'Calling #group' do
62
+
63
+ should 'return an array' do
64
+ assert_instance_of Array, Factory(:employee).group
65
+ end
66
+
67
+ context 'with a NIL group_id' do
68
+
69
+ setup do
70
+ @employee = Factory(:employee)
71
+ end
72
+
73
+ should 'return a collection of one' do
74
+ assert_equal 1, @employee.group.size
75
+ end
76
+
77
+ should 'include self in group' do
78
+ assert_contains @employee.group, @employee
79
+ end
80
+
81
+ end
82
+
83
+ context 'with a set group_id' do
84
+
85
+ setup do
86
+ @employee = Factory(:employee, :group_id => 1)
87
+ end
88
+
89
+ should 'return a collection of one' do
90
+ assert_equal 1, @employee.group.size
91
+ end
92
+
93
+ should 'include self in group' do
94
+ assert_contains @employee.group, @employee
95
+ end
96
+
97
+ end
98
+
99
+ context 'with different groups available' do
100
+
101
+ setup do
102
+ @e1 = Factory(:employee_with_reports, :group_id => 1)
103
+ @e2 = Factory(:employee, :group_id => 1)
104
+ @e3 = Factory(:employee_with_reports, :group_id => 2)
105
+ @e4 = Factory(:employee, :group_id => 2)
106
+ end
107
+
108
+ should 'return a collection of group members' do
109
+ assert_equal 2, @e1.group.size
110
+ end
111
+
112
+ should 'include all group members' do
113
+ assert_same_elements [@e1,@e2], @e1.group
114
+ end
115
+
116
+ should 'allow member to find grouped associations of other member' do
117
+ assert_same_elements @e1.reports, @e2.group.reports
118
+ end
119
+
120
+ should 'allow proxy owner to define all grouped which ignores group_id schema' do
121
+ @e2.stubs(:all_grouped?).returns(true)
122
+ assert_same_elements [@e1,@e2,@e3,@e4], @e2.group
123
+ assert_same_elements @e1.reports + @e3.reports, @e2.group.reports
124
+ end
125
+
126
+ end
127
+
128
+ context 'with different groups in legacy schema' do
129
+
130
+ setup do
131
+ @e1 = Factory(:legacy_employee_with_reports, :group_id => 1)
132
+ @e2 = Factory(:legacy_employee, :group_id => 1)
133
+ @e3 = Factory(:legacy_employee_with_reports, :group_id => 2)
134
+ @e4 = Factory(:legacy_employee, :group_id => 2)
135
+ end
136
+
137
+ should 'honor legacy reports association options like class_name and foreign_key' do
138
+ @e2.group.reports.all? { |r| r.is_a?(LegacyReport) }
139
+ end
140
+
141
+ end
142
+
143
+
144
+ end
145
+
146
+
147
+ end