buscar 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|