decisiv-grouped_scope 0.5.1.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,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
data/test/helper.rb ADDED
@@ -0,0 +1,108 @@
1
+ require File.join(File.dirname(__FILE__),'lib/boot') unless defined?(ActiveRecord)
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'factory_girl'
6
+ require 'lib/test_case'
7
+ require 'grouped_scope'
8
+
9
+ class GroupedScope::TestCase
10
+
11
+ def setup_environment(options={})
12
+ options.reverse_merge! :group_column => :group_id
13
+ setup_database(options)
14
+ Department.create! :name => 'IT'
15
+ Department.create! :name => 'Human Resources'
16
+ Department.create! :name => 'Finance'
17
+ end
18
+
19
+ protected
20
+
21
+ def setup_database(options)
22
+ ActiveRecord::Base.class_eval do
23
+ silence do
24
+ connection.create_table :employees, :force => true do |t|
25
+ t.column :name, :string
26
+ t.column :email, :string
27
+ t.column options[:group_column], :integer
28
+ end
29
+ connection.create_table :reports, :force => true do |t|
30
+ t.column :title, :string
31
+ t.column :body, :string
32
+ t.column :employee_id, :integer
33
+ end
34
+ connection.create_table :departments, :force => true do |t|
35
+ t.column :name, :string
36
+ end
37
+ connection.create_table :department_memberships, :force => true do |t|
38
+ t.column :employee_id, :integer
39
+ t.column :department_id, :integer
40
+ t.column :meta_info, :string
41
+ end
42
+ connection.create_table :legacy_employees, :force => true, :id => false do |t|
43
+ t.column :name, :string
44
+ t.column :email, :string
45
+ t.column options[:group_column], :integer
46
+ end
47
+ connection.create_table :legacy_reports, :force => true do |t|
48
+ t.column :title, :string
49
+ t.column :body, :string
50
+ t.column :email, :string
51
+ end
52
+ connection.create_table :foo_bars, :force => true do |t|
53
+ t.column :foo, :string
54
+ t.column :bar, :string
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ class Employee < ActiveRecord::Base
63
+ has_many :reports do ; def urgent ; find(:all,:conditions => {:title => 'URGENT'}) ; end ; end
64
+ has_many :taxonomies, :as => :classable
65
+ has_many :department_memberships
66
+ has_many :departments, :through => :department_memberships
67
+ grouped_scope :reports, :departments
68
+ end
69
+
70
+ class Report < ActiveRecord::Base
71
+ named_scope :with_urgent_title, :conditions => {:title => 'URGENT'}
72
+ named_scope :with_urgent_body, :conditions => "body LIKE '%URGENT%'"
73
+ belongs_to :employee
74
+ def urgent_title? ; self[:title] == 'URGENT' ; end
75
+ def urgent_body? ; self[:body] =~ /URGENT/ ; end
76
+ end
77
+
78
+ class Department < ActiveRecord::Base
79
+ named_scope :it, :conditions => {:name => 'IT'}
80
+ named_scope :hr, :conditions => {:name => 'Human Resources'}
81
+ named_scope :finance, :conditions => {:name => 'Finance'}
82
+ has_many :department_memberships
83
+ has_many :employees, :through => :department_memberships
84
+ end
85
+
86
+ class DepartmentMembership < ActiveRecord::Base
87
+ belongs_to :employee
88
+ belongs_to :department
89
+ end
90
+
91
+ class LegacyEmployee < ActiveRecord::Base
92
+ set_primary_key :email
93
+ has_many :reports, :class_name => 'LegacyReport', :foreign_key => 'email'
94
+ grouped_scope :reports
95
+ alias_method :email=, :id=
96
+ end
97
+
98
+ class LegacyReport < ActiveRecord::Base
99
+ belongs_to :employee, :class_name => 'LegacyEmployee', :foreign_key => 'email'
100
+ end
101
+
102
+ class FooBar < ActiveRecord::Base
103
+ has_many :reports
104
+ grouped_scope :reports
105
+ end
106
+
107
+
108
+
data/test/lib/boot.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+
3
+ plugin_root = File.expand_path(File.join(File.dirname(__FILE__),'..'))
4
+ framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].detect { |p| File.directory? p }
5
+ rails_version = ENV['RAILS_VERSION']
6
+ rails_version = nil if rails_version && rails_version == ''
7
+
8
+ ['.','lib','test'].each do |plugin_lib|
9
+ load_path = File.expand_path("#{plugin_root}/#{plugin_lib}")
10
+ $LOAD_PATH.unshift(load_path) unless $LOAD_PATH.include?(load_path)
11
+ end
12
+
13
+ if rails_version.nil? && framework_root
14
+ puts "Found framework root: #{framework_root}"
15
+ $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib"
16
+ else
17
+ puts "Using rails#{" #{rails_version}" if rails_version} from gems"
18
+ if rails_version
19
+ gem 'rails', rails_version
20
+ else
21
+ gem 'activerecord'
22
+ end
23
+ end
24
+
25
+ require 'active_record'
26
+ require 'active_support'
27
+
28
+ gem 'mislav-will_paginate', '2.3.4'
29
+ require 'will_paginate'
30
+ WillPaginate.enable_activerecord
31
+ require 'named_scope'
32
+
@@ -0,0 +1,20 @@
1
+
2
+ unless Hash.instance_methods.include? 'except'
3
+
4
+ Hash.class_eval do
5
+
6
+ # Returns a new hash without the given keys.
7
+ def except(*keys)
8
+ rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
9
+ reject { |key,| rejected.include?(key) }
10
+ end
11
+
12
+ # Replaces the hash without only the given keys.
13
+ def except!(*keys)
14
+ replace(except(*keys))
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
@@ -0,0 +1,82 @@
1
+
2
+ unless Hash.instance_methods.include? 'except'
3
+ Hash.class_eval do
4
+
5
+ # Returns a new hash without the given keys.
6
+ def except(*keys)
7
+ rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
8
+ reject { |key,| rejected.include?(key) }
9
+ end
10
+
11
+ # Replaces the hash without only the given keys.
12
+ def except!(*keys)
13
+ replace(except(*keys))
14
+ end
15
+
16
+ end
17
+ end
18
+
19
+ class ActiveRecord::Base
20
+ class << self
21
+
22
+ def first(*args)
23
+ find(:first, *args)
24
+ end
25
+
26
+ def last(*args)
27
+ find(:last, *args)
28
+ end
29
+
30
+ def all(*args)
31
+ find(:all, *args)
32
+ end
33
+
34
+ private
35
+
36
+ def find_last(options)
37
+ order = options[:order]
38
+ if order
39
+ order = reverse_sql_order(order)
40
+ elsif !scoped?(:find, :order)
41
+ order = "#{table_name}.#{primary_key} DESC"
42
+ end
43
+ if scoped?(:find, :order)
44
+ scoped_order = reverse_sql_order(scope(:find, :order))
45
+ scoped_methods.select { |s| s[:find].update(:order => scoped_order) }
46
+ end
47
+ find_initial(options.merge({ :order => order }))
48
+ end
49
+
50
+ def reverse_sql_order(order_query)
51
+ reversed_query = order_query.split(/,/).each { |s|
52
+ if s.match(/\s(asc|ASC)$/)
53
+ s.gsub!(/\s(asc|ASC)$/, ' DESC')
54
+ elsif s.match(/\s(desc|DESC)$/)
55
+ s.gsub!(/\s(desc|DESC)$/, ' ASC')
56
+ elsif !s.match(/\s(asc|ASC|desc|DESC)$/)
57
+ s.concat(' DESC')
58
+ end
59
+ }.join(',')
60
+ end
61
+
62
+ end
63
+ end
64
+
65
+ ActiveRecord::Associations::AssociationCollection.class_eval do
66
+
67
+ def last(*args)
68
+ if fetch_first_or_last_using_find? args
69
+ find(:last, *args)
70
+ else
71
+ load_target unless loaded?
72
+ @target.last(*args)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def fetch_first_or_last_using_find?(args)
79
+ args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
80
+ end
81
+
82
+ end