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.
- data/CHANGELOG +34 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +98 -0
- data/Rakefile +43 -0
- data/init.rb +2 -0
- data/lib/grouped_scope/association_reflection.rb +54 -0
- data/lib/grouped_scope/class_methods.rb +32 -0
- data/lib/grouped_scope/core_ext.rb +21 -0
- data/lib/grouped_scope/errors.rb +12 -0
- data/lib/grouped_scope/grouping.rb +9 -0
- data/lib/grouped_scope/has_many_association.rb +30 -0
- data/lib/grouped_scope/has_many_through_association.rb +28 -0
- data/lib/grouped_scope/instance_methods.rb +10 -0
- data/lib/grouped_scope/self_grouping.rb +78 -0
- data/lib/grouped_scope.rb +15 -0
- data/test/factories.rb +46 -0
- data/test/grouped_scope/association_reflection_test.rb +83 -0
- data/test/grouped_scope/class_methods_test.rb +51 -0
- data/test/grouped_scope/has_many_association_test.rb +157 -0
- data/test/grouped_scope/has_many_through_association_test.rb +53 -0
- data/test/grouped_scope/self_grouping_test.rb +147 -0
- data/test/helper.rb +108 -0
- data/test/lib/boot.rb +32 -0
- data/test/lib/core_ext.rb +20 -0
- data/test/lib/named_scope/core_ext.rb +82 -0
- data/test/lib/named_scope/named_scope.rb +168 -0
- data/test/lib/named_scope/named_scope_patch_1.2.rb +85 -0
- data/test/lib/named_scope/named_scope_patch_2.0.rb +55 -0
- data/test/lib/named_scope.rb +7 -0
- data/test/lib/test_case.rb +44 -0
- metadata +84 -0
@@ -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
|