buscar 1.0.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.
- data/.document +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +67 -0
- data/LICENSE +20 -0
- data/README.markdown +3 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/buscar.gemspec +114 -0
- data/lib/buscar.rb +3 -0
- data/lib/buscar/helpers.rb +79 -0
- data/lib/buscar/index.rb +171 -0
- data/lib/buscar/railtie.rb +18 -0
- data/spec/blueprint.rb +34 -0
- data/spec/helpers_spec.rb +82 -0
- data/spec/index_spec.rb +442 -0
- data/spec/matchers.rb +50 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/test_db.rb +60 -0
- metadata +360 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Buscar
|
2
|
+
if defined? Rails::Railtie
|
3
|
+
require 'rails'
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer 'buscar.insert_into_action_view' do
|
6
|
+
ActiveSupport.on_load :action_view do
|
7
|
+
Buscar::Railtie.insert
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Railtie
|
14
|
+
def self.insert
|
15
|
+
ActionView::Base.send(:include, Buscar::Helpers)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/blueprint.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'machinist/active_record'
|
2
|
+
require 'sham'
|
3
|
+
require 'faker'
|
4
|
+
|
5
|
+
Sham.define do
|
6
|
+
city { Faker::Address.city }
|
7
|
+
business { Faker::Company.name }
|
8
|
+
tag { Faker::Lorem.words(1) }
|
9
|
+
end
|
10
|
+
|
11
|
+
City.blueprint do
|
12
|
+
name { Sham.city }
|
13
|
+
end
|
14
|
+
|
15
|
+
Business.blueprint do
|
16
|
+
city { City.make }
|
17
|
+
name { Sham.business }
|
18
|
+
active true
|
19
|
+
end
|
20
|
+
|
21
|
+
Tag.blueprint do
|
22
|
+
name { Sham.tag }
|
23
|
+
published true
|
24
|
+
end
|
25
|
+
|
26
|
+
Tagging.blueprint do
|
27
|
+
business { Business.make }
|
28
|
+
tag { Tag.make }
|
29
|
+
end
|
30
|
+
|
31
|
+
def tag_business(business, tag_name)
|
32
|
+
tag = Tag.find_by_name(tag_name) || Tag.make(:name => tag_name)
|
33
|
+
Tagging.make(:tag => tag, :business => business)
|
34
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_support'
|
3
|
+
require 'action_view'
|
4
|
+
require 'action_view/base' # For the NonConcattingString class
|
5
|
+
require 'action_view/template/handlers/erb' # For the OutputBuffer class
|
6
|
+
require 'webrat'
|
7
|
+
|
8
|
+
describe Buscar::Helpers do
|
9
|
+
include Webrat::Matchers
|
10
|
+
include ActionView::Helpers
|
11
|
+
include Buscar::Helpers
|
12
|
+
|
13
|
+
# This magic code allows certain Rails helpers to work without loading the whole Rails environment.
|
14
|
+
# I'm assuming that CaptureHelper uses it to store the captured output.
|
15
|
+
attr_accessor :output_buffer
|
16
|
+
|
17
|
+
describe '#filter_menu' do
|
18
|
+
before :each do
|
19
|
+
@index = mock(:filter_param_options => [['breakfast', 'Breakfast Time'], ['lunch'], ['dinner']], :filter_param => 'lunch')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'yields each possible filter param' do
|
23
|
+
yielded = []
|
24
|
+
filter_menu(@index) do |filter_param|
|
25
|
+
yielded << filter_param
|
26
|
+
''
|
27
|
+
end
|
28
|
+
yielded.should == ['breakfast', 'lunch', 'dinner']
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'prints a link for each option, using the URL returned by the block and the humanized param or the overridden label as the text' do
|
32
|
+
html = filter_menu(@index) do |filter_param|
|
33
|
+
"http://test.host/#{filter_param}"
|
34
|
+
end
|
35
|
+
html.should include('<a href="http://test.host/breakfast">Breakfast Time</a>')
|
36
|
+
html.should include('<a href="http://test.host/lunch">Lunch</a>')
|
37
|
+
html.should include('<a href="http://test.host/dinner">Dinner</a>')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#sort_menu' do
|
42
|
+
before :each do
|
43
|
+
@index = mock(:sort_param_options => [['name'], ['dishes', 'Number of Dishes'], ['reviews']], :sort_param => 'dishes')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'yields each possible sort param' do
|
47
|
+
yielded = []
|
48
|
+
sort_menu(@index) do |sort_param|
|
49
|
+
yielded << sort_param
|
50
|
+
''
|
51
|
+
end
|
52
|
+
yielded.should == ['name', 'dishes', 'reviews']
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'prints a link for each option, using the URL returned by the block and the humanized param or the overridden label as the text' do
|
56
|
+
html = sort_menu(@index) do |sort_param, filter_param|
|
57
|
+
"http://test.host/#{sort_param}"
|
58
|
+
end
|
59
|
+
html.should include('<a href="http://test.host/name">Name</a>')
|
60
|
+
html.should include('<a href="http://test.host/dishes">Number of Dishes</a>')
|
61
|
+
html.should include('<a href="http://test.host/reviews">Reviews</a>')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#page_links' do
|
66
|
+
before :each do
|
67
|
+
@index = mock(:page_count => 3, :page => 1) # Index#page returns a zero-based offset. The helper must convert to one-based.
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'determines the correct, 1-based current page' do
|
71
|
+
page_links(@index) { |page| "/pages/#{page}" }.should have_selector('ul') do |ul|
|
72
|
+
ul.should have_selector('li') do |one|
|
73
|
+
one.should have_selector('a', 'href' => '/pages/1', :content => '1')
|
74
|
+
end
|
75
|
+
ul.should have_selector('li', :content => '2')
|
76
|
+
ul.should have_selector('li') do |three|
|
77
|
+
three.should have_selector('a', 'href' => '/pages/3', :content => '3')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/spec/index_spec.rb
ADDED
@@ -0,0 +1,442 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_db'
|
3
|
+
require 'blueprint'
|
4
|
+
require 'matchers'
|
5
|
+
|
6
|
+
# This class demontrates the bare minimum required of an Index subclass
|
7
|
+
class TagIndex < Buscar::Index
|
8
|
+
def finder
|
9
|
+
Tag
|
10
|
+
end
|
11
|
+
|
12
|
+
# This method is already defined in the superclass. By default, it looks at params[:records_per_page].
|
13
|
+
# If that is undefined, it returns 50. One reason to override the method would be to return a different number
|
14
|
+
# when params[:records_per_page] is undefined, as illustrated below. Or, you could make the method
|
15
|
+
# ignore params[:records_per_page], thus preventing the user from choosing a value.
|
16
|
+
def records_per_page
|
17
|
+
@params[:records_per_page] || 25
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TagIndexWithRecordsPerPage
|
22
|
+
# This could, if necessary, look at @params and intelligently decide what relationships to include.
|
23
|
+
# In this case, though, we just return the same thing no matter what.
|
24
|
+
def includes_clause
|
25
|
+
:businesses
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module CityTagIndex
|
30
|
+
# You can give this any arity you want, but you must at some point initialize @params as a hash.
|
31
|
+
# The superclass implementation takes one argument: the params hash.
|
32
|
+
def initialize(city, params = {})
|
33
|
+
@city = city
|
34
|
+
@params = params
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class TagIndexWithSingleFilter < TagIndex
|
39
|
+
# See comment for TagIndexWithChainedFilter#filter below.
|
40
|
+
def filter
|
41
|
+
{:published => true}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class TagIndexWithFilterOptions < TagIndex
|
46
|
+
include CityTagIndex
|
47
|
+
|
48
|
+
# Must return a hash. Keys correspond to possible values of params[:filter].
|
49
|
+
# Values can be a Proc or anything accepted by ActiveRecord's #where clause.
|
50
|
+
# If a Proc, it will be passed to #select (the Enumerable method, not the Relation method.)
|
51
|
+
# Otherwise, it will be passed to #where.
|
52
|
+
#
|
53
|
+
# The third element of each array is optional. If it's not specified, the helper will humanize
|
54
|
+
# the first param.
|
55
|
+
def filter_options
|
56
|
+
[
|
57
|
+
['short_name', 'LENGTH(name) < 4', 'Short Name'],
|
58
|
+
['medium_name', ['LENGTH(name) >= ? AND LENGTH(name) <= ?', 5, 10], 'Medium Name'],
|
59
|
+
['long_name', lambda { |tag| tag.name.length > 10 }, 'Long Name']
|
60
|
+
]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Optional to define. If not defined, the default filter option will be 'none', which will, of course,
|
64
|
+
# mean no filtering. The string 'none' will in fact be passed to the #filter_menu helper and will appear in URLs
|
65
|
+
# (assuming you use the helper).
|
66
|
+
def default_filter_option
|
67
|
+
'short_name'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class TagIndexWithChainedFilter < TagIndexWithFilterOptions
|
72
|
+
include CityTagIndex
|
73
|
+
|
74
|
+
# Must return one of the following:
|
75
|
+
# - Something that can be passed to #order.
|
76
|
+
# - A proc for #select.
|
77
|
+
# - A Chain where each element is one of the above. This will cause the filters to be chained, effectively ANDing them. Call #chain to build the Chain object, as shown below.
|
78
|
+
#
|
79
|
+
# If you're using automatic filter switching, you can implement this method to add
|
80
|
+
# on some filtering that will always be applied, regardless of which option is selected.
|
81
|
+
# To do that, return an array where one element is #super.
|
82
|
+
# The implementation below illustrates just that:
|
83
|
+
def filter
|
84
|
+
chain(
|
85
|
+
super, # Use automatic filter switching, i.e. use params[:filter] and #filter_options
|
86
|
+
{:published => true}, # Only find published tags
|
87
|
+
lambda { |tag| !tag.active_businesses_in_city(@city).empty? } # Only find tags with at least one active business
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class TagIndexWithStringSort < TagIndex
|
93
|
+
# Must return one of the following:
|
94
|
+
# - A string or symbol for #order
|
95
|
+
# - A proc for #sort_by
|
96
|
+
#
|
97
|
+
# Unlike #filter, defining this method is NOT compatible with auto-switching.
|
98
|
+
# This is because you cannot chain sorting--there can only be zero or one sort
|
99
|
+
# orders in use for a given result set. So, only define this method if you
|
100
|
+
# don't intend to use auto-switching for sorting. In this example, we always
|
101
|
+
# sort the same way, but you could implement something dynamic.
|
102
|
+
def sort
|
103
|
+
'name'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class TagIndexWithSymbolSort < TagIndex
|
108
|
+
# See comment for TagIndexWithStringSort#sort above.
|
109
|
+
def sort
|
110
|
+
:name
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class TagIndexWithProcSort < TagIndex
|
115
|
+
def sort
|
116
|
+
lambda { |tag| tag.name }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class TagIndexWithSortOptions < TagIndex
|
121
|
+
include CityTagIndex
|
122
|
+
|
123
|
+
# Must return a hash. Keys correspond to possible values of params[:sort].
|
124
|
+
# Values can be a string, a symbol, or a Proc. If a Proc, it will be passed to #sort_by.
|
125
|
+
# If a string or symbol, it will be passed to #order
|
126
|
+
#
|
127
|
+
# The third element of each array is optional. If it's not specified, the helper will humanize
|
128
|
+
# the first param.
|
129
|
+
def sort_options
|
130
|
+
[
|
131
|
+
['name', 'name', 'Name'], # Will be passed to #order
|
132
|
+
['businesses', lambda { |tag| -1 * tag.active_businesses_in_city(@city).length }, 'Number of Businesses']
|
133
|
+
]
|
134
|
+
end
|
135
|
+
|
136
|
+
# Optional to define. If not defined, the default sort order will be 'none',
|
137
|
+
# which will probably mean that the records will be returned in order of creation.
|
138
|
+
# (Unless some default ordering has been previously defined for the object returned by #finder.)
|
139
|
+
# The string 'none' will in fact be passed to the #sort_menu helper and will appear in URLs
|
140
|
+
# (assuming you use the helper).
|
141
|
+
def default_sort_option
|
142
|
+
'name'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe Buscar::Index do
|
147
|
+
include Buscar::IndexMatchers
|
148
|
+
|
149
|
+
def setup_paginated_records
|
150
|
+
@burgers = Tag.make(:name => 'Burgers')
|
151
|
+
@ethiopian = Tag.make(:name => 'Ethiopian')
|
152
|
+
@french = Tag.make(:name => 'French')
|
153
|
+
@indian = Tag.make(:name => 'Indian')
|
154
|
+
@mexican = Tag.make(:name => 'Mexican')
|
155
|
+
@pizza = Tag.make(:name => 'Pizza')
|
156
|
+
@thai = Tag.make(:name => 'Thai')
|
157
|
+
end
|
158
|
+
|
159
|
+
before :all do
|
160
|
+
@chicago = City.make(:name => 'Chicago')
|
161
|
+
@dc = City.make(:name => 'Washington')
|
162
|
+
end
|
163
|
+
|
164
|
+
before :each do
|
165
|
+
Business.delete_all
|
166
|
+
Tag.delete_all
|
167
|
+
Tagging.delete_all
|
168
|
+
end
|
169
|
+
|
170
|
+
describe '#each' do
|
171
|
+
it 'iterates over the results on the current page' do
|
172
|
+
setup_paginated_records
|
173
|
+
|
174
|
+
# Index mixes in enumerable, so calling to_a is an easy way
|
175
|
+
# to test what's yielded by #each
|
176
|
+
{
|
177
|
+
1 => [@burgers, @ethiopian],
|
178
|
+
2 => [@french, @indian],
|
179
|
+
3 => [@mexican, @pizza],
|
180
|
+
4 => [@thai]
|
181
|
+
}.each do |page, tags|
|
182
|
+
index = TagIndex.new(:page => page)
|
183
|
+
index.stub(:records_per_page => 2)
|
184
|
+
index.stub(:order_clause => :name)
|
185
|
+
index.generate!
|
186
|
+
index.to_a.should == tags
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'iterates over all records if paginate? returns false' do
|
191
|
+
setup_paginated_records
|
192
|
+
|
193
|
+
index = TagIndex.new
|
194
|
+
index.stub(:records_per_page => 2)
|
195
|
+
index.stub(:paginate? => false)
|
196
|
+
index.generate!
|
197
|
+
|
198
|
+
index.to_a.length.should == 7
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe '#empty?' do
|
203
|
+
it 'returns true if no records are found' do
|
204
|
+
TagIndex.generate.empty?.should be_true
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'returns false if records are found' do
|
208
|
+
Tag.make
|
209
|
+
TagIndex.generate.empty?.should be_false
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe '#filter_param' do
|
214
|
+
it 'returns whatever was given in the params' do
|
215
|
+
TagIndex.new(:filter => 'long_names').filter_param.should == 'long_names'
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'returns "none" if nothing was given and default_filter_option is undefined' do
|
219
|
+
TagIndex.new.filter_param.should == 'none'
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'returns the default if nothing was given and default_filter_option is defined' do
|
223
|
+
index = TagIndex.new
|
224
|
+
index.stub(:default_filter_option => 'long_names')
|
225
|
+
index.filter_param.should == 'long_names'
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe '#filter_param_options' do
|
230
|
+
it 'returns a nested array of all the possible filter_options' do
|
231
|
+
TagIndexWithFilterOptions.new(@chicago).filter_param_options.should == [['short_name', 'Short Name'], ['medium_name', 'Medium Name'], ['long_name', 'Long Name']]
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'raises if filter_options is not defined' do
|
235
|
+
lambda { TagIndex.new.filter_param_options }.should raise_error
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe '.generate' do
|
240
|
+
it 'does not require subclasses to define anything other than #finder' do
|
241
|
+
TagIndex.generate
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'returns an instance of Index' do
|
245
|
+
TagIndex.generate.should be_a(Buscar::Index)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'uses the return value of #sort in an SQL ORDER clause when #sort returns a string' do
|
249
|
+
italian = Tag.make(:name => 'Italian')
|
250
|
+
pizza = Tag.make(:name => 'Pizza')
|
251
|
+
burgers = Tag.make(:name => 'Burgers')
|
252
|
+
TagIndexWithStringSort.generate.records.should == [burgers, italian, pizza]
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'uses the return value of #sort in an SQL order clause when #sort returns a symbol' do
|
256
|
+
italian = Tag.make(:name => 'Italian')
|
257
|
+
pizza = Tag.make(:name => 'Pizza')
|
258
|
+
burgers = Tag.make(:name => 'Burgers')
|
259
|
+
TagIndexWithSymbolSort.generate.records.should == [burgers, italian, pizza]
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'uses the return value of #sort in #sort_by when #sort returns a Proc' do
|
263
|
+
italian = Tag.make(:name => 'Italian')
|
264
|
+
pizza = Tag.make(:name => 'Pizza')
|
265
|
+
burgers = Tag.make(:name => 'Burgers')
|
266
|
+
TagIndexWithProcSort.generate.records.should == [burgers, italian, pizza]
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'auto-switches the sorting when #sort_options is defined' do
|
270
|
+
italian = Tag.make(:name => 'Italian')
|
271
|
+
Tagging.make(:tag => italian, :business => Business.make(:city => @chicago))
|
272
|
+
Tagging.make(:tag => italian, :business => Business.make(:city => @chicago))
|
273
|
+
|
274
|
+
pizza = Tag.make(:name => 'Pizza')
|
275
|
+
|
276
|
+
burgers = Tag.make(:name => 'Burgers')
|
277
|
+
Tagging.make(:tag => burgers, :business => Business.make(:city => @chicago))
|
278
|
+
|
279
|
+
TagIndexWithSortOptions.generate(@chicago, :sort => 'name').records.should == [burgers, italian, pizza]
|
280
|
+
TagIndexWithSortOptions.generate(@chicago, :sort => 'businesses').records.should == [italian, burgers, pizza]
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'uses the return value of #filter in #where when a non-array is returned' do
|
284
|
+
italian = Tag.make(:name => 'Italian')
|
285
|
+
pizza = Tag.make(:name => 'Pizza', :published => false)
|
286
|
+
burgers = Tag.make(:name => 'Burgers')
|
287
|
+
TagIndexWithSingleFilter.generate.records.should == [italian, burgers]
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'chains the filters when #filter returns an array' do
|
291
|
+
# This one should be included.
|
292
|
+
italian = Tag.make(:name => 'Italian')
|
293
|
+
Tagging.make(:tag => italian, :business => Business.make(:city => @chicago))
|
294
|
+
|
295
|
+
# This one should be excluded because we're using auto-switching, and the name is too long.
|
296
|
+
mexican = Tag.make(:name => 'Mexican-American')
|
297
|
+
Tagging.make(:tag => mexican, :business => Business.make(:city => @chicago))
|
298
|
+
|
299
|
+
# This one should be excluded because it's not published
|
300
|
+
pizza = Tag.make(:name => 'Pizza', :published => false)
|
301
|
+
Tagging.make(:tag => pizza, :business => Business.make(:city => @chicago))
|
302
|
+
|
303
|
+
# This one should be excluded because it has no active businesses
|
304
|
+
burgers = Tag.make(:name => 'Burgers')
|
305
|
+
Tagging.make(:tag => burgers, :business => Business.make(:city => @chicago, :active => false))
|
306
|
+
|
307
|
+
TagIndexWithChainedFilter.generate(@chicago, :filter => 'medium_name').records.should == [italian]
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'auto-switches the filter when #filter_options is defined' do
|
311
|
+
short = Tag.make(:name => 'foo')
|
312
|
+
medium = Tag.make(:name => 'foobar')
|
313
|
+
long = Tag.make(:name => 'foobarfoobar')
|
314
|
+
TagIndexWithFilterOptions.generate(@chicago, :filter => 'short_name').records.should == [short]
|
315
|
+
TagIndexWithFilterOptions.generate(@chicago, :filter => 'medium_name').records.should == [medium]
|
316
|
+
TagIndexWithFilterOptions.generate(@chicago, :filter => 'long_name').records.should == [long]
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe '#length' do
|
321
|
+
it 'returns the number of records' do
|
322
|
+
3.times { Tag.make }
|
323
|
+
TagIndex.generate.length.should == 3
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
describe '#optional_params' do
|
328
|
+
it 'accepts param keys and returns a hash of all the matching params that are defined' do
|
329
|
+
TagIndex.new('name' => 'Jarrett', 'age' => '23', 'location' => 'chicago').optional_params('name', 'location').should == {'name' => 'Jarrett', 'location' => 'chicago'}
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe '#page' do
|
334
|
+
it 'returns the current zero-based page number, as determined by params, or defaults to 0' do
|
335
|
+
TagIndex.new(:page => '5').page.should == 4
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
describe '#page_count' do
|
340
|
+
it 'returns the number of pages, taking into account records_per_page and the total number of records' do
|
341
|
+
setup_paginated_records
|
342
|
+
|
343
|
+
index = TagIndex.new
|
344
|
+
index.stub(:records_per_page => 2)
|
345
|
+
index.generate!
|
346
|
+
|
347
|
+
index.page_count.should == 4
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
describe '#params' do
|
352
|
+
it 'returns @params' do
|
353
|
+
index = TagIndex.new('foo' => 'bar')
|
354
|
+
index.params.should == {'foo' => 'bar'}
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
describe '#records_on_page' do
|
359
|
+
it 'paginates the results using records_per_page' do
|
360
|
+
setup_paginated_records
|
361
|
+
|
362
|
+
index = TagIndex.new
|
363
|
+
index.stub(:records_per_page => 2)
|
364
|
+
index.stub(:order_clause => :name)
|
365
|
+
index.generate!
|
366
|
+
|
367
|
+
index.records_on_page(0).should == [@burgers, @ethiopian]
|
368
|
+
index.records_on_page(1).should == [@french, @indian]
|
369
|
+
index.records_on_page(2).should == [@mexican, @pizza]
|
370
|
+
index.records_on_page(3).should == [@thai]
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'does not paginate if paginate? returns false' do
|
374
|
+
setup_paginated_records
|
375
|
+
|
376
|
+
index = TagIndex.new
|
377
|
+
index.stub(:records_per_page => 2)
|
378
|
+
index.stub(:paginate? => false)
|
379
|
+
index.generate!
|
380
|
+
|
381
|
+
index.records_on_page(0).length.should == 7
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
describe '#sort_param' do
|
386
|
+
it 'returns whatever was given in the params' do
|
387
|
+
TagIndex.new(:sort => 'name').sort_param.should == 'name'
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'returns "none" if nothing was given and default_sort_option is undefined' do
|
391
|
+
TagIndex.new.sort_param.should == 'none'
|
392
|
+
end
|
393
|
+
|
394
|
+
it 'returns the default if nothing was given and default_sort_option is defined' do
|
395
|
+
TagIndexWithSortOptions.new(@chicago).sort_param.should == 'name'
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
describe '#sort_param_options' do
|
400
|
+
it 'returns a nested array of all the possible sort_options' do
|
401
|
+
TagIndexWithSortOptions.new(@chicago).sort_param_options.should == [['name', 'Name'], ['businesses', 'Number of Businesses']]
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'raises if sort_options is not defined' do
|
405
|
+
lambda { TagIndex.new.sort_param_options }.should raise_error
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
# Just to make sure our example models are working properly. We could just mock this stuff out, but I
|
411
|
+
# feel much more comfortable using real models when the subject of the test is the model layer. I've
|
412
|
+
# seen some weird bugs related to the interaction between plugins and ActiveRecord which would NOT
|
413
|
+
# have been caught had AR been mocked.
|
414
|
+
describe Tag do
|
415
|
+
before :each do
|
416
|
+
City.delete_all
|
417
|
+
Business.delete_all
|
418
|
+
Tag.delete_all
|
419
|
+
Tagging.delete_all
|
420
|
+
end
|
421
|
+
|
422
|
+
describe '#active_businesses_in_city' do
|
423
|
+
it 'filters inactive businesses' do
|
424
|
+
chicago = City.make
|
425
|
+
active = Business.make(:city => chicago)
|
426
|
+
inactive = Business.make(:city => chicago, :active => false)
|
427
|
+
tag_business(active, 'Pizza')
|
428
|
+
tag_business(inactive, 'Pizza')
|
429
|
+
Tag.find_by_name('Pizza').active_businesses_in_city(chicago).should == [active]
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'filters businesses in other cities' do
|
433
|
+
chicago = City.make
|
434
|
+
dc = City.make
|
435
|
+
chi_pizza = Business.make(:city => chicago)
|
436
|
+
dc_pizza = Business.make(:city => dc)
|
437
|
+
tag_business(chi_pizza, 'Pizza')
|
438
|
+
tag_business(dc_pizza, 'Pizza')
|
439
|
+
Tag.find_by_name('Pizza').active_businesses_in_city(chicago).should == [chi_pizza]
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|