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 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