buscar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm gemset use buscar
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem 'bundler', '~> 1.0.0'
5
+ gem 'jeweler', '~> 1.5.1'
6
+ gem 'rspec', '>= 2.2.0'
7
+ gem 'rcov'
8
+ gem 'machinist', '>= 1.0.6'
9
+ gem 'faker', '>= 0.3.1'
10
+ gem 'activesupport', '>= 3.0.3'
11
+ gem 'actionpack', '>= 3.0.3'
12
+ gem 'activerecord', '>= 3.0.3'
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ actionpack (3.0.3)
6
+ activemodel (= 3.0.3)
7
+ activesupport (= 3.0.3)
8
+ builder (~> 2.1.2)
9
+ erubis (~> 2.6.6)
10
+ i18n (~> 0.4)
11
+ rack (~> 1.2.1)
12
+ rack-mount (~> 0.6.13)
13
+ rack-test (~> 0.5.6)
14
+ tzinfo (~> 0.3.23)
15
+ activemodel (3.0.3)
16
+ activesupport (= 3.0.3)
17
+ builder (~> 2.1.2)
18
+ i18n (~> 0.4)
19
+ activerecord (3.0.3)
20
+ activemodel (= 3.0.3)
21
+ activesupport (= 3.0.3)
22
+ arel (~> 2.0.2)
23
+ tzinfo (~> 0.3.23)
24
+ activesupport (3.0.3)
25
+ arel (2.0.6)
26
+ builder (2.1.2)
27
+ diff-lcs (1.1.2)
28
+ erubis (2.6.6)
29
+ abstract (>= 1.0.0)
30
+ faker (0.3.1)
31
+ git (1.2.5)
32
+ i18n (0.5.0)
33
+ jeweler (1.5.1)
34
+ bundler (~> 1.0.0)
35
+ git (>= 1.2.5)
36
+ rake
37
+ machinist (1.0.6)
38
+ rack (1.2.1)
39
+ rack-mount (0.6.13)
40
+ rack (>= 1.0.0)
41
+ rack-test (0.5.6)
42
+ rack (>= 1.0)
43
+ rake (0.8.7)
44
+ rcov (0.9.9)
45
+ rspec (2.2.0)
46
+ rspec-core (~> 2.2)
47
+ rspec-expectations (~> 2.2)
48
+ rspec-mocks (~> 2.2)
49
+ rspec-core (2.2.1)
50
+ rspec-expectations (2.2.0)
51
+ diff-lcs (~> 1.1.2)
52
+ rspec-mocks (2.2.0)
53
+ tzinfo (0.3.23)
54
+
55
+ PLATFORMS
56
+ ruby
57
+
58
+ DEPENDENCIES
59
+ actionpack (>= 3.0.3)
60
+ activerecord (>= 3.0.3)
61
+ activesupport (>= 3.0.3)
62
+ bundler (~> 1.0.0)
63
+ faker (>= 0.3.1)
64
+ jeweler (~> 1.5.1)
65
+ machinist (>= 1.0.6)
66
+ rcov
67
+ rspec (>= 2.2.0)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 jarrett
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,3 @@
1
+ # Buscar
2
+
3
+ # See TagIndex in spec/index_spec.rb for detailed usage information.
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "buscar"
15
+ gem.summary = %Q{Searching, sorting, and pagination for Rails}
16
+ gem.description = %Q{Simplifies searching, sorting, and pagination of ActiveRecord models. Includes a model class and a view helper.}
17
+ gem.email = "jarrettcolby@gmail.com"
18
+ gem.homepage = "http://github.com/jarrett/buscar"
19
+ gem.authors = ["jarrett"]
20
+ gem.add_dependency "activesupport", ">= 3.0.2"
21
+ gem.add_dependency "activerecord", ">= 3.0.2"
22
+ # Strictly speaking, you don't need ActionView to use the helpers. They rely on some methods ActionView provides,
23
+ # but in principle, those methods could be defined by something other than ActionView. So, we make ActionView
24
+ # (part of ActionPack) a development dependency for the sake of the specs.
25
+ gem.add_development_dependency "actionpack", ">= 3.0.2"
26
+ gem.add_development_dependency "rspec", ">= 2.1.0"
27
+ gem.add_development_dependency "sqlite3-ruby", ">= 1.2.5"
28
+ gem.add_development_dependency "machinist", ">= 1.0.6"
29
+ gem.add_development_dependency "faker", ">= 0.3.1"
30
+ gem.add_development_dependency "webrat", ">= 0.7.2"
31
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settingsend
32
+ end
33
+ Jeweler::RubygemsDotOrgTasks.new
34
+
35
+ require 'rake/testtask'
36
+ Rake::TestTask.new(:test) do |test|
37
+ test.libs << 'lib' << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ end
41
+
42
+ require 'rcov/rcovtask'
43
+ Rcov::RcovTask.new do |test|
44
+ test.libs << 'test'
45
+ test.pattern = 'test/**/test_*.rb'
46
+ test.verbose = true
47
+ end
48
+
49
+ task :default => :test
50
+
51
+ require 'rake/rdoctask'
52
+ Rake::RDocTask.new do |rdoc|
53
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
54
+
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.title = "Buscar #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/buscar.gemspec ADDED
@@ -0,0 +1,114 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{buscar}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["jarrett"]
12
+ s.date = %q{2010-12-07}
13
+ s.description = %q{Simplifies searching, sorting, and pagination of ActiveRecord models. Includes a model class and a view helper.}
14
+ s.email = %q{jarrettcolby@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rvmrc",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE",
25
+ "README.markdown",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "buscar.gemspec",
29
+ "lib/buscar.rb",
30
+ "lib/buscar/helpers.rb",
31
+ "lib/buscar/index.rb",
32
+ "lib/buscar/railtie.rb",
33
+ "spec/blueprint.rb",
34
+ "spec/helpers_spec.rb",
35
+ "spec/index_spec.rb",
36
+ "spec/matchers.rb",
37
+ "spec/spec_helper.rb",
38
+ "spec/test_db.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/jarrett/buscar}
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.7}
43
+ s.summary = %q{Searching, sorting, and pagination for Rails}
44
+ s.test_files = [
45
+ "spec/blueprint.rb",
46
+ "spec/helpers_spec.rb",
47
+ "spec/index_spec.rb",
48
+ "spec/matchers.rb",
49
+ "spec/spec_helper.rb",
50
+ "spec/test_db.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
60
+ s.add_development_dependency(%q<rspec>, [">= 2.2.0"])
61
+ s.add_development_dependency(%q<rcov>, [">= 0"])
62
+ s.add_development_dependency(%q<machinist>, [">= 1.0.6"])
63
+ s.add_development_dependency(%q<faker>, [">= 0.3.1"])
64
+ s.add_development_dependency(%q<activesupport>, [">= 3.0.3"])
65
+ s.add_development_dependency(%q<actionpack>, [">= 3.0.3"])
66
+ s.add_development_dependency(%q<activerecord>, [">= 3.0.3"])
67
+ s.add_runtime_dependency(%q<activesupport>, [">= 3.0.2"])
68
+ s.add_runtime_dependency(%q<activerecord>, [">= 3.0.2"])
69
+ s.add_development_dependency(%q<actionpack>, [">= 3.0.2"])
70
+ s.add_development_dependency(%q<rspec>, [">= 2.1.0"])
71
+ s.add_development_dependency(%q<sqlite3-ruby>, [">= 1.2.5"])
72
+ s.add_development_dependency(%q<machinist>, [">= 1.0.6"])
73
+ s.add_development_dependency(%q<faker>, [">= 0.3.1"])
74
+ s.add_development_dependency(%q<webrat>, [">= 0.7.2"])
75
+ else
76
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
77
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
78
+ s.add_dependency(%q<rspec>, [">= 2.2.0"])
79
+ s.add_dependency(%q<rcov>, [">= 0"])
80
+ s.add_dependency(%q<machinist>, [">= 1.0.6"])
81
+ s.add_dependency(%q<faker>, [">= 0.3.1"])
82
+ s.add_dependency(%q<activesupport>, [">= 3.0.3"])
83
+ s.add_dependency(%q<actionpack>, [">= 3.0.3"])
84
+ s.add_dependency(%q<activerecord>, [">= 3.0.3"])
85
+ s.add_dependency(%q<activesupport>, [">= 3.0.2"])
86
+ s.add_dependency(%q<activerecord>, [">= 3.0.2"])
87
+ s.add_dependency(%q<actionpack>, [">= 3.0.2"])
88
+ s.add_dependency(%q<rspec>, [">= 2.1.0"])
89
+ s.add_dependency(%q<sqlite3-ruby>, [">= 1.2.5"])
90
+ s.add_dependency(%q<machinist>, [">= 1.0.6"])
91
+ s.add_dependency(%q<faker>, [">= 0.3.1"])
92
+ s.add_dependency(%q<webrat>, [">= 0.7.2"])
93
+ end
94
+ else
95
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
96
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
97
+ s.add_dependency(%q<rspec>, [">= 2.2.0"])
98
+ s.add_dependency(%q<rcov>, [">= 0"])
99
+ s.add_dependency(%q<machinist>, [">= 1.0.6"])
100
+ s.add_dependency(%q<faker>, [">= 0.3.1"])
101
+ s.add_dependency(%q<activesupport>, [">= 3.0.3"])
102
+ s.add_dependency(%q<actionpack>, [">= 3.0.3"])
103
+ s.add_dependency(%q<activerecord>, [">= 3.0.3"])
104
+ s.add_dependency(%q<activesupport>, [">= 3.0.2"])
105
+ s.add_dependency(%q<activerecord>, [">= 3.0.2"])
106
+ s.add_dependency(%q<actionpack>, [">= 3.0.2"])
107
+ s.add_dependency(%q<rspec>, [">= 2.1.0"])
108
+ s.add_dependency(%q<sqlite3-ruby>, [">= 1.2.5"])
109
+ s.add_dependency(%q<machinist>, [">= 1.0.6"])
110
+ s.add_dependency(%q<faker>, [">= 0.3.1"])
111
+ s.add_dependency(%q<webrat>, [">= 0.7.2"])
112
+ end
113
+ end
114
+
data/lib/buscar.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'buscar/index.rb'
2
+ require 'buscar/helpers.rb'
3
+ require 'buscar/railtie.rb'
@@ -0,0 +1,79 @@
1
+ module Buscar
2
+ module Helpers
3
+ # Accepts an instance of Buscar::Index, which will tell it the total number of pages, the current page, and the number of records per page.
4
+ # Accepts a block, which is yielded each page number and must return a URL to that page.
5
+ def page_links(index)
6
+ unless block_given?
7
+ raise ArgumentError, 'page_links requires a block.'
8
+ end
9
+ total_pages = index.page_count
10
+ if total_pages > 1
11
+ current_page = index.page + 1
12
+ content_tag('ul', 'class' => 'pagination') do
13
+ lis = ''
14
+ min_page = current_page - 2
15
+ min_page = 1 if min_page < 1
16
+ max_page = current_page + 2
17
+ max_page = total_pages if max_page > total_pages
18
+ if current_page > 1
19
+ lis << content_tag('li', link_to('&laquo; Back'.html_safe, yield(current_page - 1)))
20
+ end
21
+ if min_page > 1
22
+ lis << content_tag('li', link_to('1', yield(1)))
23
+ end
24
+ if min_page > 2
25
+ lis << content_tag('li', '...')
26
+ end
27
+ (min_page..max_page).each do |page|
28
+ lis << content_tag('li') do
29
+ if current_page.to_i == page
30
+ page.to_s
31
+ else
32
+ link_to page, yield(page)
33
+ end
34
+ end
35
+ end
36
+ if max_page < total_pages - 1
37
+ lis << content_tag('li', '...')
38
+ end
39
+ if max_page < total_pages
40
+ lis << content_tag('li', link_to(total_pages, yield(total_pages)))
41
+
42
+ end
43
+ if current_page < total_pages
44
+ lis << content_tag('li', link_to('Next &raquo;'.html_safe, yield(current_page + 1)))
45
+ end
46
+ lis.html_safe
47
+ end.html_safe
48
+ else
49
+ nil
50
+ end
51
+ end
52
+
53
+ def buscar_index_menu(index, type, options)
54
+ options.reverse_merge! :link_to_current => true
55
+ content_tag('ul', :class => "#{type}_menu") do
56
+ choices = ''
57
+ index.send("#{type}_param_options").each do |param, label_str|
58
+ choices << content_tag('li') do
59
+ label_str = param.to_s.humanize if label_str.nil?
60
+ if !options[:link_to_current] and index.filter_param.to_s == param.to_s
61
+ ('<span class="selected">' + label_str + '</span>').html_safe
62
+ else
63
+ link_to label_str, yield(param)
64
+ end
65
+ end
66
+ end
67
+ choices.html_safe
68
+ end.html_safe
69
+ end
70
+
71
+ def filter_menu(index, options = {}, &block)
72
+ buscar_index_menu(index, 'filter', options, &block)
73
+ end
74
+
75
+ def sort_menu(index, options = {}, &block)
76
+ buscar_index_menu(index, 'sort', options, &block)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,171 @@
1
+ module Buscar
2
+ class Index
3
+ include Enumerable
4
+
5
+ # Views can call this method to iterate over the current page's records. Uses
6
+ # the value returned by #page, which in turn uses the value of @params[:page]
7
+ def each
8
+ records_on_page(page).each do |record|
9
+ yield record
10
+ end
11
+ end
12
+
13
+ def empty?
14
+ @records.empty?
15
+ end
16
+
17
+ # Returns one of the following in descending order of preference:
18
+ # - params[:filter]
19
+ # - default_filter_option
20
+ # - 'none'
21
+ def filter_param
22
+ @params[:filter] || (respond_to?(:default_filter_option, true) ? default_filter_option : 'none')
23
+ end
24
+
25
+ def filter_param_options
26
+ filter_options.collect do |opt|
27
+ arr = [opt[0]]
28
+ arr << opt[2] if opt.length == 3
29
+ arr
30
+ end
31
+ end
32
+
33
+ def self.generate(*args)
34
+ index = new(*args)
35
+ index.generate!
36
+ index
37
+ end
38
+
39
+ def generate!
40
+ unless respond_to?(:finder, true)
41
+ raise 'Subclasses of Index must define #finder, which must return something that responds to #find. For example, #finder may return an ActiveRecord::Base subclass.'
42
+ end
43
+
44
+ raise "Buscar::Index#conditions is deprecated. Name your method where_clause instead." if respond_to?(:conditions, true)
45
+ raise "Buscar::Index#order is deprecated. Name your method order_clause instead." if respond_to?(:order, true)
46
+ raise "Buscar::Index#include_clause is deprecated. Name your method includes_clause instead." if respond_to?(:include, true)
47
+
48
+ @records = finder.scoped # Get the bare relation object in case none of the modifiers are used
49
+ # Use each AR query modifier other than #where and #order if applicable
50
+ %w(having select group limit offset joins includes lock readonly from).each do |meth|
51
+ @records = @records.send(meth, send("#{meth}_clause".to_sym)) if respond_to?("#{meth}_clause".to_sym, true)
52
+ end
53
+
54
+ chained_filters = filter
55
+ chained_filters = chain(chained_filters) unless chained_filters.is_a?(Chain) # filter might return a Chain or a single filtering rule. If it's a single one, we'll put it in an array.
56
+ @filter_procs = chained_filters.select { |f| f.is_a?(Proc) } # Procs will be triggered by the first call to #records so as not to defeat lazy loading
57
+ where_filters = chained_filters.select { |f| !f.is_a?(Proc) } # SQL filters can be used right away
58
+ where_filters.each do |filt|
59
+ @records = @records.where(filt)
60
+ end
61
+
62
+ sort_rule = sort
63
+ # If sorting by a proc, do it on the first call to #records so as not to defeat lazy loading
64
+ if sort_rule.is_a?(Proc)
65
+ @sort_proc = sort_rule
66
+ else
67
+ @records = @records.order(sort_rule)
68
+ end
69
+ end
70
+
71
+ def initialize(params = {})
72
+ @params = params
73
+ end
74
+
75
+ def length
76
+ @records.length
77
+ end
78
+
79
+ def optional_params(*keys)
80
+ keys.inject({}) do |hash, key|
81
+ unless @params[key].blank?
82
+ hash[key] = @params[key]
83
+ end
84
+ hash
85
+ end
86
+ end
87
+
88
+ # The current page, as determined by @params[:page]. Since we want the user to see the page array as one-based,
89
+ # but we use zero-based indices internally, we subtract one from the page number.
90
+ #
91
+ # Thus, the return value is a zero-based offset.
92
+ def page
93
+ (@params[:page] || @params['page'] || 1).to_i - 1
94
+ end
95
+
96
+ def page_count
97
+ (@records.length.to_f / records_per_page).ceil
98
+ end
99
+
100
+ attr_reader :params
101
+
102
+ # page_num is zero-based for this method.
103
+ def records_on_page(page_num)
104
+ if paginate?
105
+ @records.slice((page_num) * records_per_page, records_per_page)
106
+ else
107
+ @records
108
+ end
109
+ end
110
+
111
+ # Returns one of the following in descending order of preference:
112
+ # - params[:sort]
113
+ # - default_sort_option
114
+ # - 'none'
115
+ def sort_param
116
+ @params[:sort] || (respond_to?(:default_sort_option, true) ? default_sort_option : 'none')
117
+ end
118
+
119
+ def sort_param_options
120
+ sort_options.collect do |opt|
121
+ arr = [opt[0]]
122
+ arr << opt[2] if opt.length == 3
123
+ arr
124
+ end
125
+ end
126
+
127
+ def records
128
+ unless @procs_called
129
+ @filter_procs.each do |proc|
130
+ @records = @records.select(&proc)
131
+ end
132
+ @records = @records.sort_by(&@sort_proc) if @sort_proc
133
+ @procs_called = true
134
+ end
135
+ @records
136
+ end
137
+
138
+ private
139
+
140
+ class Chain < Array; end
141
+
142
+ def chain(*elements)
143
+ Chain.new(elements)
144
+ end
145
+
146
+ # Return something for #where, a Proc, or an array
147
+ def filter
148
+ if respond_to?(:filter_options, true) and (option = filter_options.assoc(filter_param))
149
+ option[1]
150
+ else
151
+ nil
152
+ end
153
+ end
154
+
155
+ def paginate?
156
+ true
157
+ end
158
+
159
+ def records_per_page
160
+ @params[:records_per_page] || 50
161
+ end
162
+
163
+ def sort
164
+ if respond_to?(:sort_options, true) and (option = sort_options.assoc(sort_param))
165
+ option[1]
166
+ else
167
+ nil
168
+ end
169
+ end
170
+ end
171
+ end