cehoffman-acts_as_ferret 0.4.4

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.
Files changed (162) hide show
  1. data/LICENSE +20 -0
  2. data/README +68 -0
  3. data/bin/aaf_install +23 -0
  4. data/config/ferret_server.yml +24 -0
  5. data/doc/README.win32 +23 -0
  6. data/doc/demo/README +154 -0
  7. data/doc/demo/README_DEMO +23 -0
  8. data/doc/demo/Rakefile +10 -0
  9. data/doc/demo/app/controllers/admin/backend_controller.rb +14 -0
  10. data/doc/demo/app/controllers/admin_area_controller.rb +4 -0
  11. data/doc/demo/app/controllers/application.rb +5 -0
  12. data/doc/demo/app/controllers/contents_controller.rb +49 -0
  13. data/doc/demo/app/controllers/searches_controller.rb +8 -0
  14. data/doc/demo/app/helpers/admin/backend_helper.rb +2 -0
  15. data/doc/demo/app/helpers/application_helper.rb +3 -0
  16. data/doc/demo/app/helpers/content_helper.rb +2 -0
  17. data/doc/demo/app/helpers/search_helper.rb +2 -0
  18. data/doc/demo/app/models/comment.rb +48 -0
  19. data/doc/demo/app/models/content.rb +12 -0
  20. data/doc/demo/app/models/content_base.rb +28 -0
  21. data/doc/demo/app/models/search.rb +19 -0
  22. data/doc/demo/app/models/shared_index1.rb +3 -0
  23. data/doc/demo/app/models/shared_index2.rb +3 -0
  24. data/doc/demo/app/models/special_content.rb +3 -0
  25. data/doc/demo/app/models/stats.rb +20 -0
  26. data/doc/demo/app/views/admin/backend/search.rhtml +18 -0
  27. data/doc/demo/app/views/contents/_form.rhtml +10 -0
  28. data/doc/demo/app/views/contents/edit.rhtml +9 -0
  29. data/doc/demo/app/views/contents/index.rhtml +24 -0
  30. data/doc/demo/app/views/contents/new.rhtml +8 -0
  31. data/doc/demo/app/views/contents/show.rhtml +8 -0
  32. data/doc/demo/app/views/layouts/application.html.erb +17 -0
  33. data/doc/demo/app/views/searches/_content.html.erb +2 -0
  34. data/doc/demo/app/views/searches/search.html.erb +20 -0
  35. data/doc/demo/config/boot.rb +109 -0
  36. data/doc/demo/config/database.yml +38 -0
  37. data/doc/demo/config/environment.rb +69 -0
  38. data/doc/demo/config/environments/development.rb +16 -0
  39. data/doc/demo/config/environments/production.rb +19 -0
  40. data/doc/demo/config/environments/test.rb +21 -0
  41. data/doc/demo/config/ferret_server.yml +18 -0
  42. data/doc/demo/config/lighttpd.conf +40 -0
  43. data/doc/demo/config/routes.rb +9 -0
  44. data/doc/demo/db/development_structure.sql +15 -0
  45. data/doc/demo/db/migrate/001_initial_migration.rb +18 -0
  46. data/doc/demo/db/migrate/002_add_type_to_contents.rb +9 -0
  47. data/doc/demo/db/migrate/003_create_shared_index1s.rb +11 -0
  48. data/doc/demo/db/migrate/004_create_shared_index2s.rb +11 -0
  49. data/doc/demo/db/migrate/005_special_field.rb +9 -0
  50. data/doc/demo/db/migrate/006_create_stats.rb +15 -0
  51. data/doc/demo/db/schema.sql +18 -0
  52. data/doc/demo/doc/README_FOR_APP +2 -0
  53. data/doc/demo/doc/howto.txt +70 -0
  54. data/doc/demo/public/.htaccess +40 -0
  55. data/doc/demo/public/404.html +8 -0
  56. data/doc/demo/public/500.html +8 -0
  57. data/doc/demo/public/dispatch.cgi +10 -0
  58. data/doc/demo/public/dispatch.fcgi +24 -0
  59. data/doc/demo/public/dispatch.rb +10 -0
  60. data/doc/demo/public/favicon.ico +0 -0
  61. data/doc/demo/public/images/rails.png +0 -0
  62. data/doc/demo/public/index.html +277 -0
  63. data/doc/demo/public/robots.txt +1 -0
  64. data/doc/demo/public/stylesheets/scaffold.css +74 -0
  65. data/doc/demo/script/about +3 -0
  66. data/doc/demo/script/breakpointer +3 -0
  67. data/doc/demo/script/console +3 -0
  68. data/doc/demo/script/destroy +3 -0
  69. data/doc/demo/script/ferret_server +10 -0
  70. data/doc/demo/script/generate +3 -0
  71. data/doc/demo/script/performance/benchmarker +3 -0
  72. data/doc/demo/script/performance/profiler +3 -0
  73. data/doc/demo/script/plugin +3 -0
  74. data/doc/demo/script/process/inspector +3 -0
  75. data/doc/demo/script/process/reaper +3 -0
  76. data/doc/demo/script/process/spawner +3 -0
  77. data/doc/demo/script/process/spinner +3 -0
  78. data/doc/demo/script/runner +3 -0
  79. data/doc/demo/script/server +3 -0
  80. data/doc/demo/test/fixtures/comments.yml +12 -0
  81. data/doc/demo/test/fixtures/contents.yml +13 -0
  82. data/doc/demo/test/fixtures/remote_contents.yml +9 -0
  83. data/doc/demo/test/fixtures/shared_index1s.yml +7 -0
  84. data/doc/demo/test/fixtures/shared_index2s.yml +7 -0
  85. data/doc/demo/test/functional/admin/backend_controller_test.rb +35 -0
  86. data/doc/demo/test/functional/contents_controller_test.rb +81 -0
  87. data/doc/demo/test/functional/searches_controller_test.rb +71 -0
  88. data/doc/demo/test/smoke/drb_smoke_test.rb +321 -0
  89. data/doc/demo/test/smoke/process_stats.rb +21 -0
  90. data/doc/demo/test/test_helper.rb +30 -0
  91. data/doc/demo/test/unit/comment_test.rb +217 -0
  92. data/doc/demo/test/unit/content_test.rb +705 -0
  93. data/doc/demo/test/unit/ferret_result_test.rb +24 -0
  94. data/doc/demo/test/unit/multi_index_test.rb +329 -0
  95. data/doc/demo/test/unit/remote_index_test.rb +23 -0
  96. data/doc/demo/test/unit/shared_index1_test.rb +108 -0
  97. data/doc/demo/test/unit/shared_index2_test.rb +13 -0
  98. data/doc/demo/test/unit/sort_test.rb +21 -0
  99. data/doc/demo/test/unit/special_content_test.rb +25 -0
  100. data/doc/demo/vendor/plugins/will_paginate/LICENSE +18 -0
  101. data/doc/demo/vendor/plugins/will_paginate/README +108 -0
  102. data/doc/demo/vendor/plugins/will_paginate/Rakefile +23 -0
  103. data/doc/demo/vendor/plugins/will_paginate/init.rb +21 -0
  104. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/collection.rb +45 -0
  105. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb +44 -0
  106. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/finder.rb +159 -0
  107. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb +95 -0
  108. data/doc/demo/vendor/plugins/will_paginate/test/array_pagination_test.rb +23 -0
  109. data/doc/demo/vendor/plugins/will_paginate/test/boot.rb +27 -0
  110. data/doc/demo/vendor/plugins/will_paginate/test/console +10 -0
  111. data/doc/demo/vendor/plugins/will_paginate/test/finder_test.rb +219 -0
  112. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/admin.rb +3 -0
  113. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/companies.yml +24 -0
  114. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/company.rb +23 -0
  115. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/developer.rb +11 -0
  116. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/developers_projects.yml +13 -0
  117. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/project.rb +4 -0
  118. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/projects.yml +7 -0
  119. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/replies.yml +20 -0
  120. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/reply.rb +5 -0
  121. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/schema.sql +44 -0
  122. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/topic.rb +19 -0
  123. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/topics.yml +30 -0
  124. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/user.rb +2 -0
  125. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/users.yml +35 -0
  126. data/doc/demo/vendor/plugins/will_paginate/test/helper.rb +42 -0
  127. data/doc/demo/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb +64 -0
  128. data/doc/demo/vendor/plugins/will_paginate/test/lib/load_fixtures.rb +10 -0
  129. data/doc/demo/vendor/plugins/will_paginate/test/pagination_test.rb +136 -0
  130. data/doc/monit-example +22 -0
  131. data/init.rb +24 -0
  132. data/install.rb +18 -0
  133. data/lib/act_methods.rb +147 -0
  134. data/lib/acts_as_ferret.rb +584 -0
  135. data/lib/ar_mysql_auto_reconnect_patch.rb +41 -0
  136. data/lib/blank_slate.rb +53 -0
  137. data/lib/bulk_indexer.rb +38 -0
  138. data/lib/class_methods.rb +270 -0
  139. data/lib/ferret_extensions.rb +188 -0
  140. data/lib/ferret_find_methods.rb +141 -0
  141. data/lib/ferret_result.rb +53 -0
  142. data/lib/ferret_server.rb +238 -0
  143. data/lib/index.rb +99 -0
  144. data/lib/instance_methods.rb +171 -0
  145. data/lib/local_index.rb +205 -0
  146. data/lib/more_like_this.rb +217 -0
  147. data/lib/multi_index.rb +126 -0
  148. data/lib/rdig_adapter.rb +148 -0
  149. data/lib/remote_functions.rb +23 -0
  150. data/lib/remote_index.rb +54 -0
  151. data/lib/remote_multi_index.rb +20 -0
  152. data/lib/search_results.rb +50 -0
  153. data/lib/server_manager.rb +58 -0
  154. data/lib/unix_daemon.rb +64 -0
  155. data/lib/without_ar.rb +52 -0
  156. data/rakefile +141 -0
  157. data/recipes/aaf_recipes.rb +114 -0
  158. data/script/ferret_daemon +94 -0
  159. data/script/ferret_server +10 -0
  160. data/script/ferret_service +178 -0
  161. data/tasks/ferret.rake +22 -0
  162. metadata +258 -0
@@ -0,0 +1,13 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class SharedIndex2Test < Test::Unit::TestCase
4
+ fixtures :shared_index2s, :shared_index1s
5
+
6
+ def setup
7
+ SharedIndex1.rebuild_index
8
+ end
9
+
10
+ def test_query_for_record
11
+ assert_match /SharedIndex2/, shared_index2s(:first).query_for_record.to_s
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class SortTest < Test::Unit::TestCase
4
+ include Ferret::Search
5
+
6
+ def test_sort_marshalling
7
+ [ Sort.new,
8
+ Sort.new( [], :reverse => true) ,
9
+ Sort.new([ Ferret::Search::SortField.new(:id, :reverse => true),
10
+ Ferret::Search::SortField::SCORE,
11
+ Ferret::Search::SortField::DOC_ID ],
12
+ :reverse => true),
13
+ Sort.new([ Ferret::Search::SortField.new(:id),
14
+ Ferret::Search::SortField::SCORE_REV,
15
+ Ferret::Search::SortField::DOC_ID_REV ])
16
+ ].each do |sort|
17
+ assert_equal sort.to_s, Sort._load(sort._dump(0)).to_s
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class SpecialContentTest < Test::Unit::TestCase
4
+ include Ferret::Index
5
+ include Ferret::Search
6
+ fixtures :contents, :comments
7
+
8
+ def setup
9
+ ContentBase.rebuild_index
10
+ Comment.rebuild_index
11
+ end
12
+
13
+ def test_class_index_dir
14
+ assert SpecialContent.aaf_configuration[:index_dir] =~ %r{^#{RAILS_ROOT}/index/test/content_base}
15
+ end
16
+
17
+ def test_find_with_ferret
18
+ contents_from_ferret = SpecialContent.find_with_ferret('single table')
19
+ assert_equal 1, contents_from_ferret.size
20
+ assert_equal ContentBase.find(3), contents_from_ferret.first
21
+ contents_from_ferret = SpecialContent.find_with_ferret('title')
22
+ assert contents_from_ferret.empty?
23
+
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2007 PJ Hyett and Mislav Marohnić
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,108 @@
1
+ = WillPaginate
2
+
3
+ Quick quiz: Where does pagination logic belong?
4
+
5
+ a) in the model;
6
+ b) in the controller;
7
+ c) in views;
8
+ d) all of the above.
9
+
10
+ We think you know the answer (if you think hard enough).
11
+
12
+ This plugin makes magic happen. You *will* paginate!
13
+
14
+
15
+ == Example usage:
16
+
17
+ Use a paginate finder in the controller:
18
+
19
+ @posts = Post.paginate_by_board_id @board.id, :page => params[:page]
20
+
21
+ Yeah, +paginate+ works just like +find+ -- it just doesn't fetch all the records.
22
+ Just don't forget to tell it which page you want!
23
+
24
+ Render the posts in your view like you would normally do. When you need to render
25
+ pagination, just stick this in:
26
+
27
+ <%= will_paginate @posts %>
28
+
29
+ You're done. (Copy and paste the example fancy CSS styles from the bottom.)
30
+
31
+ How does it know how much items to fetch per page? It asks your model by calling
32
+ +Post.per_page+. You can define it like this:
33
+
34
+ class Post < ActiveRecord::Base
35
+ cattr_reader :per_page
36
+ @@per_page = 50
37
+ end
38
+
39
+ ... or like this:
40
+
41
+ class Post < ActiveRecord::Base
42
+ def self.per_page
43
+ 50
44
+ end
45
+ end
46
+
47
+ ... or don't worry about it at all. (WillPaginate defines it to be 30 if missing.)
48
+ You can also specify the count explicitly when calling +paginate+:
49
+
50
+ @posts = Post.paginate :page => params[:page], :per_page => 50
51
+
52
+ The +paginate+ finder wraps the original finder and returns your resultset that now has
53
+ some new properties. You can use the collection as you would with any ActiveRecord
54
+ resultset, but WillPaginate view helpers also need that object to be able to render pagination:
55
+
56
+ <ol>
57
+ <% for post in @posts -%>
58
+ <li>Render `post` in some nice way.</li>
59
+ <% end -%>
60
+ </ol>
61
+
62
+ <p>Now let's render us some pagination!</p>
63
+ <%= will_paginate @posts %>
64
+
65
+
66
+ == Authors, credits
67
+
68
+ Ruby port by: PJ Hyett, Mislav Marohnić (Sulien)
69
+ Contributors: K. Adam Christensen, Chris Wanstrath, Dr. Nic Williams
70
+ Original announcement: http://errtheblog.com/post/929
71
+ Original PHP source: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php
72
+
73
+ REPORT BUGS on Lighthouse: http://err.lighthouseapp.com/projects/466-plugins/overview
74
+
75
+
76
+ == Want Digg style?
77
+
78
+ Copy the following css into your stylesheet for a good start:
79
+
80
+ .pagination {
81
+ padding: 3px;
82
+ margin: 3px;
83
+ }
84
+ .pagination a {
85
+ padding: 2px 5px 2px 5px;
86
+ margin: 2px;
87
+ border: 1px solid #aaaadd;
88
+ text-decoration: none;
89
+ color: #000099;
90
+ }
91
+ .pagination a:hover, .pagination a:active {
92
+ border: 1px solid #000099;
93
+ color: #000;
94
+ }
95
+ .pagination span.current {
96
+ padding: 2px 5px 2px 5px;
97
+ margin: 2px;
98
+ border: 1px solid #000099;
99
+ font-weight: bold;
100
+ background-color: #000099;
101
+ color: #FFF;
102
+ }
103
+ .pagination span.disabled {
104
+ padding: 2px 5px 2px 5px;
105
+ margin: 2px;
106
+ border: 1px solid #eee;
107
+ color: #ddd;
108
+ }
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the will_paginate plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.pattern = 'test/**/*_test.rb'
11
+ t.verbose = true
12
+ end
13
+
14
+ desc 'Generate RDoc documentation for the will_paginate plugin.'
15
+ Rake::RDocTask.new(:rdoc) do |rdoc|
16
+ files = ['README', 'LICENSE', 'lib/**/*.rb']
17
+ rdoc.rdoc_files.add(files)
18
+ rdoc.main = "README" # page to start on
19
+ rdoc.title = "will_paginate"
20
+ rdoc.template = File.exists?(t="/Users/chris/ruby/projects/err/rock/template.rb") ? t : "/var/www/rock/template.rb"
21
+ rdoc.rdoc_dir = 'doc' # rdoc output folder
22
+ rdoc.options << '--inline-source'
23
+ end
@@ -0,0 +1,21 @@
1
+ require 'will_paginate/core_ext'
2
+ require 'will_paginate/collection'
3
+ require 'will_paginate/finder'
4
+ require 'will_paginate/view_helpers'
5
+
6
+ ActionView::Base.send :include, WillPaginate::ViewHelpers
7
+ ActiveRecord::Base.send :include, WillPaginate::Finder
8
+
9
+ module ActiveRecord::Associations
10
+ # to support paginating finders on associations, we have to mix in the
11
+ # method_missing magic from WillPaginate::Finder::ClassMethods to AssociationProxy
12
+ # subclasses, but in a different way for Rails 1.2.x and 2.0
13
+ (AssociationCollection.instance_methods.include?(:create!) ?
14
+ AssociationCollection : AssociationCollection.subclasses.map(&:constantize)
15
+ ).push(HasManyThroughAssociation).each do |klass|
16
+ klass.class_eval do
17
+ include WillPaginate::Finder::ClassMethods
18
+ alias_method_chain :method_missing, :paginate
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,45 @@
1
+ module WillPaginate
2
+ # Arrays returned from paginating finds are, in fact, instances of this.
3
+ # You may think of WillPaginate::Collection as an ordinary array with some
4
+ # extra properties. Those properites are used by view helpers to generate
5
+ # correct page links.
6
+ #
7
+ class Collection < Array
8
+ attr_reader :current_page, :per_page
9
+ attr_accessor :total_entries
10
+
11
+ # These collection objects are instantiated by ActiveRecord paginating
12
+ # finders; there is no need to do it manually.
13
+ #
14
+ def initialize(page, per_page, total)
15
+ @current_page = page.to_i
16
+ @per_page = per_page.to_i
17
+ @total_entries = total.to_i
18
+ @total_pages = (@total_entries / @per_page.to_f).ceil
19
+ end
20
+
21
+ # The total number of pages.
22
+ def page_count
23
+ @total_pages
24
+ end
25
+
26
+ # Current offset of the paginated collection. If we're on the first page,
27
+ # it is always 0. If we're on the 2nd page and there are 30 entries per page,
28
+ # the offset is 30. This property is useful if you want to render ordinals
29
+ # besides your records: simply start with offset + 1.
30
+ #
31
+ def offset
32
+ (current_page - 1) * per_page
33
+ end
34
+
35
+ # current_page - 1 or nil if there is no previous page
36
+ def previous_page
37
+ current_page > 1 ? (current_page - 1) : nil
38
+ end
39
+
40
+ # current_page + 1 or nil if there is no next page
41
+ def next_page
42
+ current_page < page_count ? (current_page + 1) : nil
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ require 'set'
2
+
3
+ unless Hash.instance_methods.include? 'except'
4
+ Hash.class_eval do
5
+ # Returns a new hash without the given keys.
6
+ def except(*keys)
7
+ rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
8
+ reject { |key,| rejected.include?(key) }
9
+ end
10
+
11
+ # Replaces the hash without only the given keys.
12
+ def except!(*keys)
13
+ replace(except(*keys))
14
+ end
15
+ end
16
+ end
17
+
18
+ unless Hash.instance_methods.include? 'slice'
19
+ Hash.class_eval do
20
+ # Returns a new hash with only the given keys.
21
+ def slice(*keys)
22
+ allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
23
+ reject { |key,| !allowed.include?(key) }
24
+ end
25
+
26
+ # Replaces the hash with only the given keys.
27
+ def slice!(*keys)
28
+ replace(slice(*keys))
29
+ end
30
+ end
31
+ end
32
+
33
+ unless Array.instance_methods.include? 'paginate'
34
+ # http://www.desimcadam.com/archives/8
35
+ Array.class_eval do
36
+ def paginate(page = 1, per_page = 15)
37
+ pagination_array = WillPaginate::Collection.new(page, per_page, size)
38
+ start_index = pagination_array.offset
39
+ end_index = start_index + (per_page - 1)
40
+ array_to_concat = self[start_index..end_index]
41
+ array_to_concat.nil? ? [] : pagination_array.concat(array_to_concat)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,159 @@
1
+ module WillPaginate
2
+ # A mixin for ActiveRecord::Base. Provides `per_page` class method
3
+ # and makes `paginate` finders possible with some method_missing magic.
4
+ #
5
+ # Find out more in WillPaginate::Finder::ClassMethods
6
+ #
7
+ module Finder
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ class << base
11
+ alias_method_chain :method_missing, :paginate
12
+ define_method(:per_page) { 30 } unless respond_to?(:per_page)
13
+ end
14
+ end
15
+
16
+ # = Paginating finders for ActiveRecord models
17
+ #
18
+ # WillPaginate doesn't really add extra methods to your ActiveRecord models (except +per_page+
19
+ # unless it's already available). It simply intercepts
20
+ # the calls to paginating finders such as +paginate+, +paginate_by_user_id+ (and so on) and
21
+ # translates them to ordinary finders: +find+, +find_by_user_id+, etc. It does so with some
22
+ # method_missing magic, but you don't need to care for that. You simply use paginating finders
23
+ # same way you used ordinary ones. You only need to tell them what page you want in options.
24
+ #
25
+ # @topics = Topic.paginate :all, :page => params[:page]
26
+ #
27
+ # In paginating finders, "all" is implicit. No sense in paginating a single record, right? So:
28
+ #
29
+ # Post.paginate => Post.find :all
30
+ # Post.paginate_all_by_something => Post.find_all_by_something
31
+ # Post.paginate_by_something => Post.find_all_by_something
32
+ #
33
+ # Knowing that, the above example can be written simply as:
34
+ #
35
+ # @topics = Topic.paginate :page => params[:page]
36
+ #
37
+ # Don't forget to pass the +page+ parameter! Without it, paginating finders will raise an error.
38
+ #
39
+ # == Options
40
+ # Options for paginating finders are:
41
+ #
42
+ # page REQUIRED, but defaults to 1 if false or nil
43
+ # per_page (default is read from the model, which is 30 if not overriden)
44
+ # total entries not needed unless you want to count the records yourself somehow
45
+ # count hash of options that are used only for the call to count
46
+ #
47
+ module ClassMethods
48
+ # This methods wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
49
+ # based on the params otherwise used by paginating finds: +page+ and +per_page+.
50
+ #
51
+ # Example:
52
+ #
53
+ # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000],
54
+ # :page => params[:page], :per_page => 3
55
+ #
56
+ def paginate_by_sql(sql, options)
57
+ options, page, per_page = wp_parse_options!(options)
58
+ sanitized_query = sanitize_sql(sql)
59
+ total_entries = options[:total_entries] || count_by_sql("SELECT COUNT(*) FROM (#{sanitized_query}) AS count_table")
60
+
61
+ returning WillPaginate::Collection.new(page, per_page, total_entries) do |pager|
62
+ options.update :offset => pager.offset, :limit => pager.per_page
63
+ add_limit! sanitized_query, options
64
+ pager.replace find_by_sql(sanitized_query)
65
+ end
66
+ end
67
+
68
+ def respond_to?(method, include_priv = false)
69
+ case method.to_sym
70
+ when :paginate, :paginate_by_sql
71
+ true
72
+ else
73
+ super(method.to_s.sub(/^paginate/, 'find'), include_priv)
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ def method_missing_with_paginate(method, *args, &block)
80
+ # did somebody tried to paginate? if not, let them be
81
+ unless method.to_s.index('paginate') == 0
82
+ return method_missing_without_paginate(method, *args, &block)
83
+ end
84
+
85
+ options, page, per_page = wp_parse_options!(args.pop)
86
+ # paginate finders are really just find_* with limit and offset
87
+ finder = method.to_s.sub /^paginate/, 'find'
88
+ # magic counting for user convenience
89
+ total_entries = wp_count!(options, args, finder)
90
+
91
+ # :all is implicit
92
+ if finder == 'find'
93
+ args.unshift(:all) if args.empty?
94
+ elsif finder.index('find_by_') == 0
95
+ finder.sub! /^find/, 'find_all'
96
+ end
97
+
98
+ ::Object.returning WillPaginate::Collection.new(page, per_page, total_entries) do |pager|
99
+ args << options.update(:offset => pager.offset, :limit => pager.per_page)
100
+ pager.replace send(finder, *args)
101
+ end
102
+ end
103
+
104
+ def wp_count!(options, args, finder)
105
+ # :total_entries and :count are mutually exclusive!
106
+ unless options[:total_entries]
107
+ unless args.first.is_a? Array
108
+ # count expects (almost) the same options as find
109
+ count_options = options.except :count, :order, :select
110
+
111
+ # merge the hash found in :count
112
+ # this allows you to specify :select, :order, or anything else just for the count query
113
+ count_options.update(options.delete(:count)) if options.key? :count
114
+ # extract the conditions from calls like "paginate_by_foo_and_bar"
115
+ conditions = wp_extract_finder_conditions(finder, args, count_options)
116
+
117
+ # scope_out adds a 'with_finder' method which acts like with_scope, if it's present
118
+ # then execute the count with the scoping provided by the with_finder
119
+ count = nil
120
+ counter = Proc.new { count = count(count_options) }
121
+
122
+ if respond_to?(scoper = finder.sub(/^find/, 'with'))
123
+ send(scoper, &counter)
124
+ else
125
+ with_scope(:find => { :conditions => conditions }, &counter)
126
+ end
127
+
128
+ count.respond_to?(:length) ? count.length : count
129
+ else
130
+ # array of IDs was passed, so its size is the total number
131
+ args.first.size
132
+ end
133
+ else
134
+ options.delete(:total_entries)
135
+ end
136
+ end
137
+
138
+ def wp_parse_options!(options)
139
+ raise ArgumentError, 'hash parameters expected' unless options.respond_to? :symbolize_keys!
140
+ options.symbolize_keys!
141
+ raise ArgumentError, ':page parameter required' unless options.key? :page
142
+ page = options.delete(:page) || 1
143
+ per_page = options.delete(:per_page) || self.per_page
144
+ [options, page, per_page]
145
+ end
146
+
147
+ private
148
+
149
+ # thanks to active record for making us duplicate this code
150
+ def wp_extract_finder_conditions(finder, arguments, count_options)
151
+ return unless match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(finder.to_s)
152
+
153
+ attribute_names = extract_attribute_names_from_match(match)
154
+ raise "I can't make sense of #{finder}" unless all_attributes_exists?(attribute_names)
155
+ construct_attributes_from_arguments(attribute_names, arguments)
156
+ end
157
+ end
158
+ end
159
+ end