mholling-paged_scopes 0.0.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/LICENSE +20 -0
- data/README.rdoc +7 -0
- data/Rakefile +53 -0
- data/VERSION.yml +4 -0
- data/lib/paged_scopes/collection.rb +45 -0
- data/lib/paged_scopes/context.rb +36 -0
- data/lib/paged_scopes/controller.rb +22 -0
- data/lib/paged_scopes/index.rb +89 -0
- data/lib/paged_scopes/pages.rb +188 -0
- data/lib/paged_scopes/paginator.rb +47 -0
- data/lib/paged_scopes/resources.rb +32 -0
- data/lib/paged_scopes.rb +9 -0
- data/rails/init.rb +1 -0
- data/spec/collection_spec.rb +66 -0
- data/spec/context_spec.rb +19 -0
- data/spec/controller_spec.rb +60 -0
- data/spec/index_spec.rb +31 -0
- data/spec/page_spec.rb +174 -0
- data/spec/paginator_spec.rb +147 -0
- data/spec/resources_spec.rb +70 -0
- data/spec/spec_helper.rb +132 -0
- metadata +90 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Controller" do
|
4
|
+
context "class" do
|
5
|
+
it "should add a protected get_page_for callback as a before filter when get_page_for is called" do
|
6
|
+
in_controller_class do
|
7
|
+
get_page_for :articles
|
8
|
+
before_filters.should include("get_page_for_articles")
|
9
|
+
protected_instance_methods.should include("get_page_for_articles")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "instance" do
|
15
|
+
it "should raise an error if no collection is set" do
|
16
|
+
in_controller_instance_with_paged(:articles) do
|
17
|
+
lambda { get_page_for_articles }.should raise_error(RuntimeError)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when the collection is set" do
|
22
|
+
before(:all) do
|
23
|
+
@articles = User.first.articles
|
24
|
+
@articles.per_page = 3
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should get the page from a page id in the params" do
|
28
|
+
in_controller_instance_with_paged(:articles) do
|
29
|
+
stub!(:params).and_return({ :page_id => @articles.pages.last.id })
|
30
|
+
get_page_for_articles
|
31
|
+
@page.articles.should include(@articles.last)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should otherwise get the page from the current object if no page id is present in the params" do
|
36
|
+
@article = @articles.last
|
37
|
+
in_controller_instance_with_paged(:articles) do
|
38
|
+
get_page_for_articles
|
39
|
+
@page.should == @articles.pages.find_by_article(@article)
|
40
|
+
@page.articles.should include(@article)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should get the first page if the current object is a new record" do
|
45
|
+
@article = @articles.new
|
46
|
+
in_controller_instance_with_paged(:articles) do
|
47
|
+
get_page_for_articles
|
48
|
+
@page.should == @articles.pages.first
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should otherwise get the first page" do
|
53
|
+
in_controller_instance_with_paged(:articles) do
|
54
|
+
get_page_for_articles
|
55
|
+
@page.should == @articles.pages.first
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/index_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Indexing" do
|
4
|
+
in_contexts do
|
5
|
+
it "should know the index of an object in the collection" do
|
6
|
+
@articles.all.each_with_index do |article, index|
|
7
|
+
@articles.index_of(article).should == index
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should raise an error if asked for the index of an object not in the collection" do
|
12
|
+
(Article.all - @articles.all).each do |article|
|
13
|
+
lambda { @articles.index_of(article) }.should raise_error(ActiveRecord::RecordNotFound)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should know the object after an object in the collection" do
|
18
|
+
articles = @articles.all
|
19
|
+
until articles.empty? do
|
20
|
+
@articles.after(articles.shift).should == articles.first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should know the object before an object in the collection" do
|
25
|
+
articles = @articles.all
|
26
|
+
until articles.empty? do
|
27
|
+
@articles.before(articles.pop).should == articles.last
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/page_spec.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Pages" do
|
4
|
+
in_contexts do
|
5
|
+
before(:each) do
|
6
|
+
@per_page = 3
|
7
|
+
@articles.stub!(:per_page).and_return(@per_page)
|
8
|
+
@articles.stub!(:page_name).and_return("Page")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should raise an error if per_page is not specified" do
|
12
|
+
@articles.stub!(:per_page).and_return(nil)
|
13
|
+
lambda { @pages.per_page }.should raise_error(RuntimeError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should paginate using per_page from its proxy if available" do
|
17
|
+
@articles.stub!(:per_page).and_return(@per_page)
|
18
|
+
@pages.per_page.should == @per_page
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be a class" do
|
22
|
+
@pages.should be_a_kind_of(Class)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be a class with name of the proxy's page name" do
|
26
|
+
@pages.name.should == "Page"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should know the page count" do
|
30
|
+
@pages.count.should == (@articles.all.length - 1)/@per_page + 1
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should find pages with valid numbers" do
|
34
|
+
(1..@pages.count).each do |number|
|
35
|
+
lambda { @pages.find(number) }.should_not raise_error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise an error containing the nearest substitute page for invalid page numbers" do
|
40
|
+
[ [ -1, @pages.first], [ 0, @pages.first ], [ @pages.count + 1, @pages.last ] ].each do |number, substitute_page|
|
41
|
+
lambda { @pages.find(number) }.should raise_error do |error|
|
42
|
+
error.substitute.should == substitute_page
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should be enumerable" do
|
48
|
+
@pages.metaclass.included_modules.should include(Enumerable)
|
49
|
+
@pages.should respond_to(:each)
|
50
|
+
args_for_each = []
|
51
|
+
@pages.each { |page| args_for_each << page }
|
52
|
+
args_for_each.should == (1..@pages.count).map { |number| @pages.find(number) }
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should find the first page" do
|
56
|
+
@pages.first.number.should == 1
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should find the last page" do
|
60
|
+
@pages.last.number.should == @pages.count
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should find all pages" do
|
64
|
+
@pages.all.should == (1..@pages.count).map { |number| @pages.find(number) }
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should find a page closest to a given number" do
|
68
|
+
@pages.closest_to(0 ).should == @pages.first
|
69
|
+
@pages.closest_to(@pages.count ).should == @pages.last
|
70
|
+
@pages.closest_to(@pages.count + 1).should == @pages.last
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should find the page of an object in the collection" do
|
74
|
+
@articles.all.each_with_index do |article, index|
|
75
|
+
@pages.find_by_article(article).number.should == 1 + index/@pages.per_page
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should not find the page of an object not in the collection" do
|
80
|
+
(Article.all - @articles.all).each do |article|
|
81
|
+
@pages.find_by_article(article).should be_nil
|
82
|
+
lambda { @pages.find_by_article!(article) }.should raise_error(PagedScopes::PageNotFound)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should find a page from a params hash with a pages name as an id in the key" do
|
87
|
+
@pages.stub!(:name).and_return("Group")
|
88
|
+
@pages.from_params(:group_id => "1").should == @pages.first
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "Page instance" do
|
94
|
+
in_contexts do
|
95
|
+
before(:each) do
|
96
|
+
@per_page = 3
|
97
|
+
@articles.stub!(:per_page).and_return(@per_page)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should have a scope representing the objects in the page" do
|
101
|
+
@pages.each { |page| page.articles.class.should == ActiveRecord::NamedScope::Scope }
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should know its number" do
|
105
|
+
@pages.find(1).number.should == 1
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should parameterise to the page number" do
|
109
|
+
@pages.map(&:to_param).should == @pages.map(&:number).map(&:to_s)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should have the page number as id" do
|
113
|
+
@pages.map(&:id).should == @pages.map(&:number)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should know whether it's first" do
|
117
|
+
pages = @pages.all
|
118
|
+
pages.shift.should be_first
|
119
|
+
pages.each { |page| page.should_not be_first }
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should know whether it's last" do
|
123
|
+
pages = @pages.all
|
124
|
+
pages.pop.should be_last
|
125
|
+
pages.each { |page| page.should_not be_last }
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should know the next page" do
|
129
|
+
pages = @pages.all
|
130
|
+
until pages.empty? do
|
131
|
+
pages.shift.next.should == pages.first
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should know the previous page" do
|
136
|
+
pages = @pages.all
|
137
|
+
until pages.empty? do
|
138
|
+
pages.pop.previous.should == pages.last
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should know the page which is offset by a specified amount" do
|
143
|
+
[ -2, 0, +2 ].each do |offset|
|
144
|
+
pages = @pages.all
|
145
|
+
pages.each_with_index do |page, index|
|
146
|
+
page.offset(offset).should == (index + offset < 0 ? nil : pages[index + offset])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should be equal based on its page number" do
|
152
|
+
@pages.find(1).should == @pages.find(1)
|
153
|
+
@pages.find(1).should_not == @pages.find(2)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should know the page count" do
|
157
|
+
@pages.first.page_count.should == @pages.count
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should be sortable by page number" do
|
161
|
+
@pages.all.reverse.sort.should == @pages.all
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should know whether it's full" do
|
165
|
+
@pages.each do |page|
|
166
|
+
page.articles.all.length == @per_page ? page.should(be_full) : page.should_not(be_full)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should be correctly paginated and ordered" do
|
171
|
+
@pages.map(&:articles).should == @articles.all.in_groups_of(@per_page, false)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Paginator" do
|
4
|
+
before(:each) do
|
5
|
+
@articles = Article.scoped({})
|
6
|
+
@articles.per_page = 3
|
7
|
+
@pages = @articles.pages
|
8
|
+
@size = 2 # window size
|
9
|
+
@page_count = @pages.count
|
10
|
+
(@page_count >= 14).should be_true # window specs won't work otherwise
|
11
|
+
@path = lambda { |page| "/path/to/page/#{page.to_param}" }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should raise an error if the paginator path is not set" do
|
15
|
+
lambda { @pages.first.paginator.next }.should raise_error
|
16
|
+
end
|
17
|
+
|
18
|
+
context "for the first page" do
|
19
|
+
before(:each) do
|
20
|
+
@page = @pages.first
|
21
|
+
@paginator = @page.paginator
|
22
|
+
@paginator.set_path(&@path)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should call the path proc with the next page when #next is called" do
|
26
|
+
@path.should_receive(:call).with(@page.next)
|
27
|
+
@paginator.next
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not call the path proc when #previous is called" do
|
31
|
+
@path.should_not_receive(:call)
|
32
|
+
@paginator.previous.should be_nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "for the last page" do
|
37
|
+
before(:each) do
|
38
|
+
@page = @pages.first
|
39
|
+
@paginator = @page.paginator
|
40
|
+
@paginator.set_path(&@path)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should call the path proc with the next page when #next is called" do
|
44
|
+
@path.should_receive(:call).with(@page.next)
|
45
|
+
@paginator.next
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not call the path proc when #previous is called" do
|
49
|
+
@path.should_not_receive(:call)
|
50
|
+
@paginator.previous.should be_nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "for any other page" do
|
55
|
+
before(:each) do
|
56
|
+
@page = @pages.all.second
|
57
|
+
@paginator = @page.paginator
|
58
|
+
@paginator.set_path(&@path)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should call the path proc with the next page when #next is called" do
|
62
|
+
@path.should_receive(:call).with(@page.next)
|
63
|
+
@paginator.next
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should call the path proc with the previous page when #previous is called" do
|
67
|
+
@path.should_receive(:call).with(@page.previous)
|
68
|
+
@paginator.previous
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "window generator" do
|
73
|
+
it "should raise an error if no block is provided" do
|
74
|
+
lambda { @pages.first.paginator.window({}) }.should raise_error(ArgumentError)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should call the block with the page and the path for each page in a window surrounding the page" do
|
78
|
+
[ [ 6, 6-@size..6+@size ], [ 2, 1..2+@size ], [ 1, 1..1+@size ], [ @page_count-1, @page_count-1-@size..@page_count ], [ @page_count, @page_count-@size..@page_count ] ].each do |number, range|
|
79
|
+
page = @pages.find(number)
|
80
|
+
page.paginator.set_path(&@path)
|
81
|
+
expected_args = []
|
82
|
+
range.map { |nearby_number| @pages.find(nearby_number) }.each do |nearby_page|
|
83
|
+
expected_args << [ nearby_page, @path.call(nearby_page) ]
|
84
|
+
end
|
85
|
+
actual_args = []
|
86
|
+
page.paginator.window(:size => @size) do |*args|
|
87
|
+
actual_args << args
|
88
|
+
end
|
89
|
+
actual_args.should == expected_args
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
[ :first, :last ].each do |extra|
|
94
|
+
it "should call the block with #{extra.inspect} and the path for the #{extra} page if #{extra.inspect} is specified as an extra" do
|
95
|
+
page = @pages.find(6)
|
96
|
+
page.paginator.set_path(&@path)
|
97
|
+
page.paginator.window(:size => @size, :extras => [ extra ]) do |page, path|
|
98
|
+
@received = true if extra == page && path == @path.call(@pages.send(extra))
|
99
|
+
end
|
100
|
+
@received.should be_true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
[ [ :first, "1+@size" ], [ :last, "@page_count-@size" ] ].each do |extra, number|
|
105
|
+
it "should not call the block with #{extra.inspect} if #{extra.inspect} is specified as an extra but the #{extra} page is within the window" do
|
106
|
+
page = @pages.find(eval(number))
|
107
|
+
page.paginator.set_path(&@path)
|
108
|
+
page.paginator.window(:size => @size, :extras => [ extra ]) do |page, path|
|
109
|
+
@received = true if extra == page
|
110
|
+
end
|
111
|
+
@received.should be_nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
[ [ :previous, 8, "8-2*@size-1" ], [ :next, 4, "4+2*@size+1" ] ].each do |extra, number, new_number|
|
116
|
+
it "should call the block with #{extra.inspect} and the path for the #{extra} page if #{extra.inspect} is specified as an extra" do
|
117
|
+
page = @pages.find(number)
|
118
|
+
page.paginator.set_path(&@path)
|
119
|
+
page.paginator.window(:size => @size, :extras => [ extra ]) do |page, path|
|
120
|
+
@received = true if extra == page && path == @path.call(@pages.find(eval(new_number)))
|
121
|
+
end
|
122
|
+
@received.should be_true
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
[ [ :previous, "1+2*@size" ], [ :next, "@page_count-2*@size" ] ].each do |extra, number|
|
127
|
+
it "should not call the block with #{extra.inspect} if #{extra.inspect} is specified as an extra but the #{extra} window is out of range" do
|
128
|
+
page = @pages.find(eval(number))
|
129
|
+
page.paginator.set_path(&@path)
|
130
|
+
page.paginator.window(:size => @size, :extras => [ extra ]) do |page, path|
|
131
|
+
@received = true if extra == page
|
132
|
+
end
|
133
|
+
@received.should be_nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should call the block with :first, :previous, pages, :next, :last in that order" do
|
138
|
+
page = @pages.find(6)
|
139
|
+
page.paginator.set_path(&@path)
|
140
|
+
pages_in_order = []
|
141
|
+
page.paginator.window(:size => 1, :extras => [ :first, :previous, :next, :last ]) do |page, path|
|
142
|
+
pages_in_order << page
|
143
|
+
end
|
144
|
+
pages_in_order.should == [ :first, :previous, @pages.find(5), @pages.find(6), @pages.find(7), :next, :last ]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
describe "Resources" do
|
2
|
+
before(:each) do
|
3
|
+
ActionController::Routing::Routes.clear!
|
4
|
+
end
|
5
|
+
|
6
|
+
after(:each) do
|
7
|
+
ActionController::Routing::Routes.clear!
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should not affect normal resource mapping if :paged option is not specified" do
|
11
|
+
drawing_routes { |map| map.resources :articles }.should change { number_of_routes }.by(7)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should add a paged index route if a :paged option is specified" do
|
15
|
+
drawing_routes { |map| map.resources :articles, :paged => true }.should change { number_of_routes }.by(7+1)
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with a :paged options" do
|
19
|
+
it "should map a paged index route for GET only" do
|
20
|
+
draw_routes { |map| map.resources :articles, :paged => true }
|
21
|
+
recognise_path( :get, "/pages/1/articles").should == { :controller => "articles", :action => "index", :page_id => "1" }
|
22
|
+
recognise_path( :put, "/pages/1/articles").should be_nil
|
23
|
+
recognise_path( :post, "/pages/1/articles").should be_nil
|
24
|
+
recognise_path(:delete, "/pages/1/articles").should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should add a named route for the paged index route" do
|
28
|
+
draw_routes { |map| map.resources :articles, :paged => true }
|
29
|
+
named_routes.names.should include(:page_articles)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should observe the :path_prefix option in the paged route" do
|
33
|
+
draw_routes { |map| map.resources :articles, :paged => true, :path_prefix => "foo" }
|
34
|
+
recognise_path(:get, "/foo/pages/1/articles").should == { :controller => "articles", :action => "index", :page_id => "1" }
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should observe a :namespace option in the paged route" do
|
38
|
+
draw_routes { |map| map.resources :articles, :paged => true, :namespace => "bar/" }
|
39
|
+
recognise_path(:get, "/pages/1/articles").should == { :controller => "bar/articles", :action => "index", :page_id => "1" }
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should accept an :as option in the :paged option" do
|
43
|
+
draw_routes { |map| map.resources :articles, :paged => { :as => "page" } }
|
44
|
+
recognise_path(:get, "/page/1/articles").should == { :controller => "articles", :action => "index", :page_id => "1" }
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should accept a :name option in the :paged option" do
|
48
|
+
draw_routes { |map| map.resources :articles, :paged => { :name => :groups } }
|
49
|
+
recognise_path(:get, "/groups/1/articles").should == { :controller => "articles", :action => "index", :group_id => "1" }
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should accept a :path_prefix hash as the :paged option" do
|
53
|
+
draw_routes { |map| map.resources :articles, :paged => true, :name_prefix => "baz_" }
|
54
|
+
named_routes.names.should include(:baz_page_articles)
|
55
|
+
end
|
56
|
+
|
57
|
+
context "and nested resources" do
|
58
|
+
it "should not change the nested routes" do
|
59
|
+
drawing_routes do |map|
|
60
|
+
map.resources :articles, :paged => true do |article|
|
61
|
+
article.resources :comments
|
62
|
+
end
|
63
|
+
end.should change { number_of_routes }.by(7+1+7)
|
64
|
+
drawing_routes do |map|
|
65
|
+
map.resources :articles, :paged => true, :has_many => :comments
|
66
|
+
end.should change { number_of_routes }.by(7+1+7)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'spec'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
require 'rubygems'
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_record'
|
8
|
+
require 'action_controller'
|
9
|
+
require 'action_controller/test_process'
|
10
|
+
require 'action_view/test_case'
|
11
|
+
require 'paged_scopes'
|
12
|
+
|
13
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
|
14
|
+
ActiveRecord::Schema.define do
|
15
|
+
create_table "users", :force => true do |t|
|
16
|
+
t.column "name", :text
|
17
|
+
end
|
18
|
+
create_table "articles", :force => true do |t|
|
19
|
+
t.column "user_id", :integer
|
20
|
+
t.column "title", :text
|
21
|
+
end
|
22
|
+
create_table "comments", :force => true do |t|
|
23
|
+
t.column "article_id", :integer
|
24
|
+
t.column "user_id", :integer
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ::User < ActiveRecord::Base
|
29
|
+
has_many :articles
|
30
|
+
has_many :comments
|
31
|
+
has_many :commented_articles, :through => :comments, :source => :article
|
32
|
+
end
|
33
|
+
class ::Article < ActiveRecord::Base
|
34
|
+
belongs_to :user
|
35
|
+
has_many :comments
|
36
|
+
end
|
37
|
+
class ::Comment < ActiveRecord::Base
|
38
|
+
belongs_to :article
|
39
|
+
belongs_to :user
|
40
|
+
end
|
41
|
+
|
42
|
+
[ "first user", nil, "last user" ].each { |name| User.create(:name => name) }
|
43
|
+
7.times do
|
44
|
+
User.all.each do |user|
|
45
|
+
user.articles.create.comments << User.first.comments.new
|
46
|
+
user.articles.create(:title => "%03d title" % Article.count).comments << User.first.comments.new << User.last.comments.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module ControllerHelpers
|
51
|
+
def in_controller_class(&block)
|
52
|
+
Class.new(ActionController::Base) do
|
53
|
+
extend Spec::Matchers
|
54
|
+
instance_eval(&block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def in_controller_instance_with_paged(collection, &block)
|
59
|
+
controller = Class.new(ActionController::Base) do
|
60
|
+
get_page_for collection
|
61
|
+
end.new
|
62
|
+
controller.copy_instance_variables_from(self)
|
63
|
+
controller.instance_eval do
|
64
|
+
extend Spec::Matchers
|
65
|
+
stub!(:params).and_return({})
|
66
|
+
instance_eval(&block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module RoutingHelpers
|
72
|
+
def draw_routes(&block)
|
73
|
+
ActionController::Routing::Routes.draw(&block)
|
74
|
+
end
|
75
|
+
|
76
|
+
def drawing_routes(&block)
|
77
|
+
lambda { draw_routes(&block) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def number_of_routes
|
81
|
+
ActionController::Routing::Routes.routes.size
|
82
|
+
end
|
83
|
+
|
84
|
+
def named_routes
|
85
|
+
ActionController::Routing::Routes.named_routes
|
86
|
+
end
|
87
|
+
|
88
|
+
def recognise_path(method, path)
|
89
|
+
request = ActionController::TestRequest.new
|
90
|
+
request.request_method = method
|
91
|
+
ActionController::Routing::Routes.recognize_path(path, ActionController::Routing::Routes.extract_request_environment(request))
|
92
|
+
rescue ActionController::RoutingError, ActionController::MethodNotAllowed
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
module Contexts
|
98
|
+
def in_contexts(&block)
|
99
|
+
[ [ "a scoped ActiveRecord class", "Article.scoped({})" ],
|
100
|
+
[ "a has_many association", "User.last.articles" ], # not tested for habtm!
|
101
|
+
[ "a has_many, :through association", "User.first.commented_articles" ] ].each do |base_type, base|
|
102
|
+
[ [ "", "" ],
|
103
|
+
[ "scoped with :conditions", ".scoped(:conditions => { :title => nil })" ],
|
104
|
+
[ "scoped with :include", ".scoped(:include => :comments)" ],
|
105
|
+
[ "scoped with :joins", ".scoped(:joins => 'INNER JOIN users ON users.id = articles.user_id')" ],
|
106
|
+
[ "scoped with :joins & :conditions", ".scoped(:joins => 'INNER JOIN users ON users.id = articles.user_id', :conditions => [ 'users.name IS NOT :nil', { :nil => nil } ])" ],
|
107
|
+
[ "scoped with :joins, :conditions & :order", ".scoped(:joins => 'INNER JOIN users ON users.id = articles.user_id', :conditions => [ 'users.name IS NOT :nil', { :nil => nil } ], :order => 'users.name')" ],
|
108
|
+
[ "scoped with :joins & :group", ".scoped(:joins => 'INNER JOIN comments AS article_comments ON article_comments.article_id = articles.id', :group => 'articles.id')" ],
|
109
|
+
[ "scoped with :joins, :group & :limit", ".scoped(:joins => 'INNER JOIN comments AS article_comments ON article_comments.article_id = articles.id', :group => 'articles.id', :limit => 4)" ],
|
110
|
+
[ "scoped with :includes, :joins & subquery", ".scoped(:include => :comments, :joins => 'INNER JOIN (SELECT count(id) AS count, article_id FROM comments GROUP BY article_id) article_comments ON article_comments.article_id = articles.id', :conditions => 'article_comments.count > 1')"],
|
111
|
+
[ "scoped with :limit", ".scoped(:limit => 5)" ],
|
112
|
+
[ "scoped with :limit & :offset", ".scoped(:limit => 5, :offset => 7)" ],
|
113
|
+
[ "scoped with :order", ".scoped(:order => 'articles.id DESC')" ] ].each do |scope_type, scope|
|
114
|
+
context "for #{base_type} #{scope_type}" do
|
115
|
+
before(:each) do
|
116
|
+
@articles = eval("#{base}#{scope}")
|
117
|
+
@articles.all.should_not be_empty
|
118
|
+
@pages = @articles.pages
|
119
|
+
end
|
120
|
+
instance_eval(&block)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
Spec::Runner.configure do |config|
|
128
|
+
config.extend Contexts
|
129
|
+
config.include RoutingHelpers
|
130
|
+
config.include ControllerHelpers
|
131
|
+
end
|
132
|
+
|