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.
Files changed (34) hide show
  1. data/.travis.yml +5 -0
  2. data/CHANGELOG +13 -0
  3. data/Gemfile +4 -6
  4. data/README.md +173 -0
  5. data/lib/grouped_scope.rb +7 -7
  6. data/lib/grouped_scope/arish/associations/association_scope.rb +90 -0
  7. data/lib/grouped_scope/arish/associations/builder/grouped_association.rb +50 -0
  8. data/lib/grouped_scope/arish/associations/builder/grouped_collection_association.rb +32 -0
  9. data/lib/grouped_scope/arish/associations/collection_association.rb +25 -0
  10. data/lib/grouped_scope/arish/base.rb +24 -0
  11. data/lib/grouped_scope/arish/reflection.rb +18 -0
  12. data/lib/grouped_scope/arish/relation/predicate_builer.rb +27 -0
  13. data/lib/grouped_scope/errors.rb +2 -3
  14. data/lib/grouped_scope/self_grouping.rb +59 -23
  15. data/lib/grouped_scope/version.rb +2 -4
  16. data/test/cases/has_many_test.rb +155 -0
  17. data/test/cases/has_many_through_test.rb +51 -0
  18. data/test/cases/reflection_test.rb +62 -0
  19. data/test/cases/self_grouping_test.rb +201 -0
  20. data/test/helper.rb +48 -35
  21. metadata +27 -30
  22. data/README.rdoc +0 -98
  23. data/lib/grouped_scope/association_reflection.rb +0 -54
  24. data/lib/grouped_scope/class_methods.rb +0 -32
  25. data/lib/grouped_scope/core_ext.rb +0 -29
  26. data/lib/grouped_scope/grouping.rb +0 -9
  27. data/lib/grouped_scope/has_many_association.rb +0 -28
  28. data/lib/grouped_scope/has_many_through_association.rb +0 -28
  29. data/lib/grouped_scope/instance_methods.rb +0 -10
  30. data/test/grouped_scope/association_reflection_test.rb +0 -73
  31. data/test/grouped_scope/class_methods_test.rb +0 -51
  32. data/test/grouped_scope/has_many_association_test.rb +0 -156
  33. data/test/grouped_scope/has_many_through_association_test.rb +0 -51
  34. 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
@@ -7,6 +7,5 @@ module GroupedScope
7
7
  def initialize(owner) ; @owner = owner ; end
8
8
  def message ; %|The #{@owner.class} class does not have a "group_id" attribute.| ; end
9
9
  end
10
-
11
-
12
- end
10
+
11
+ end
@@ -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 :primary_key, :quote_value, :columns_hash, :to => :proxy_class
6
+ delegate :quote_value, :columns_hash, :to => :proxy_class
7
7
 
8
8
  [].methods.each do |m|
9
- unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|respond_to\?)/
10
- delegate m, :to => :group_proxy
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
- @ids ||= find_selves(group_id_scope_options).map(&:id)
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 || !proxy_class.grouped_scopes[method].blank?
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 group_proxy
36
- @group_proxy ||= find_selves(group_scope_options)
54
+ def primary_key
55
+ reflection ? reflection.association_primary_key : proxy_class.primary_key
37
56
  end
38
57
 
39
- def all_grouped?
40
- proxy_owner.all_grouped? rescue false
58
+ def arel_group_id
59
+ arel_table['group_id']
41
60
  end
42
61
 
43
- def no_group?
44
- proxy_owner.group_id.blank?
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 find_selves(options={})
48
- proxy_owner.class.find :all, options
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 group_scope_options
52
- return {} if all_grouped?
53
- conditions = no_group? ? { primary_key => proxy_owner.id } : { :group_id => proxy_owner.group_id }
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 group_id_scope_options
58
- { :select => primary_key }.merge(group_scope_options)
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, &block)
69
- if proxy_class.grouped_scopes[method]
70
- proxy_owner.send("grouped_scope_#{method}", *args, &block)
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
@@ -1,5 +1,3 @@
1
1
  module GroupedScope
2
-
3
- VERSION = '0.6.1'
4
-
5
- end
2
+ VERSION = '3.1.0'
3
+ 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