watson-acts_as_ferret 0.4.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. data/LICENSE +20 -0
  2. data/README +104 -0
  3. data/acts_as_ferret.gemspec +58 -0
  4. data/bin/aaf_install +29 -0
  5. data/config/ferret_server.yml +24 -0
  6. data/doc/README.win32 +23 -0
  7. data/doc/demo/README +154 -0
  8. data/doc/demo/README_DEMO +23 -0
  9. data/doc/demo/Rakefile +10 -0
  10. data/doc/demo/app/controllers/admin/backend_controller.rb +14 -0
  11. data/doc/demo/app/controllers/admin_area_controller.rb +4 -0
  12. data/doc/demo/app/controllers/application.rb +5 -0
  13. data/doc/demo/app/controllers/contents_controller.rb +49 -0
  14. data/doc/demo/app/controllers/searches_controller.rb +8 -0
  15. data/doc/demo/app/helpers/admin/backend_helper.rb +2 -0
  16. data/doc/demo/app/helpers/application_helper.rb +3 -0
  17. data/doc/demo/app/helpers/content_helper.rb +2 -0
  18. data/doc/demo/app/helpers/search_helper.rb +2 -0
  19. data/doc/demo/app/models/comment.rb +48 -0
  20. data/doc/demo/app/models/content.rb +12 -0
  21. data/doc/demo/app/models/content_base.rb +28 -0
  22. data/doc/demo/app/models/search.rb +19 -0
  23. data/doc/demo/app/models/shared_index1.rb +3 -0
  24. data/doc/demo/app/models/shared_index2.rb +3 -0
  25. data/doc/demo/app/models/special_content.rb +3 -0
  26. data/doc/demo/app/models/stats.rb +20 -0
  27. data/doc/demo/app/views/admin/backend/search.rhtml +18 -0
  28. data/doc/demo/app/views/contents/_form.rhtml +10 -0
  29. data/doc/demo/app/views/contents/edit.rhtml +9 -0
  30. data/doc/demo/app/views/contents/index.rhtml +24 -0
  31. data/doc/demo/app/views/contents/new.rhtml +8 -0
  32. data/doc/demo/app/views/contents/show.rhtml +8 -0
  33. data/doc/demo/app/views/layouts/application.html.erb +17 -0
  34. data/doc/demo/app/views/searches/_content.html.erb +2 -0
  35. data/doc/demo/app/views/searches/search.html.erb +20 -0
  36. data/doc/demo/config/boot.rb +109 -0
  37. data/doc/demo/config/database.yml +38 -0
  38. data/doc/demo/config/environment.rb +69 -0
  39. data/doc/demo/config/environments/development.rb +16 -0
  40. data/doc/demo/config/environments/production.rb +19 -0
  41. data/doc/demo/config/environments/test.rb +21 -0
  42. data/doc/demo/config/ferret_server.yml +18 -0
  43. data/doc/demo/config/lighttpd.conf +40 -0
  44. data/doc/demo/config/routes.rb +9 -0
  45. data/doc/demo/db/development_structure.sql +15 -0
  46. data/doc/demo/db/migrate/001_initial_migration.rb +18 -0
  47. data/doc/demo/db/migrate/002_add_type_to_contents.rb +9 -0
  48. data/doc/demo/db/migrate/003_create_shared_index1s.rb +11 -0
  49. data/doc/demo/db/migrate/004_create_shared_index2s.rb +11 -0
  50. data/doc/demo/db/migrate/005_special_field.rb +9 -0
  51. data/doc/demo/db/migrate/006_create_stats.rb +15 -0
  52. data/doc/demo/db/schema.sql +18 -0
  53. data/doc/demo/db/schema.sqlite +14 -0
  54. data/doc/demo/doc/README_FOR_APP +2 -0
  55. data/doc/demo/doc/howto.txt +70 -0
  56. data/doc/demo/public/404.html +8 -0
  57. data/doc/demo/public/500.html +8 -0
  58. data/doc/demo/public/dispatch.cgi +10 -0
  59. data/doc/demo/public/dispatch.fcgi +24 -0
  60. data/doc/demo/public/dispatch.rb +10 -0
  61. data/doc/demo/public/favicon.ico +0 -0
  62. data/doc/demo/public/images/rails.png +0 -0
  63. data/doc/demo/public/index.html +277 -0
  64. data/doc/demo/public/robots.txt +1 -0
  65. data/doc/demo/public/stylesheets/scaffold.css +74 -0
  66. data/doc/demo/script/about +3 -0
  67. data/doc/demo/script/breakpointer +3 -0
  68. data/doc/demo/script/console +3 -0
  69. data/doc/demo/script/destroy +3 -0
  70. data/doc/demo/script/ferret_server +10 -0
  71. data/doc/demo/script/generate +3 -0
  72. data/doc/demo/script/performance/benchmarker +3 -0
  73. data/doc/demo/script/performance/profiler +3 -0
  74. data/doc/demo/script/plugin +3 -0
  75. data/doc/demo/script/process/inspector +3 -0
  76. data/doc/demo/script/process/reaper +3 -0
  77. data/doc/demo/script/process/spawner +3 -0
  78. data/doc/demo/script/process/spinner +3 -0
  79. data/doc/demo/script/runner +3 -0
  80. data/doc/demo/script/server +3 -0
  81. data/doc/demo/test/fixtures/comments.yml +12 -0
  82. data/doc/demo/test/fixtures/contents.yml +13 -0
  83. data/doc/demo/test/fixtures/remote_contents.yml +9 -0
  84. data/doc/demo/test/fixtures/shared_index1s.yml +7 -0
  85. data/doc/demo/test/fixtures/shared_index2s.yml +7 -0
  86. data/doc/demo/test/functional/admin/backend_controller_test.rb +35 -0
  87. data/doc/demo/test/functional/contents_controller_test.rb +81 -0
  88. data/doc/demo/test/functional/searches_controller_test.rb +71 -0
  89. data/doc/demo/test/smoke/drb_smoke_test.rb +321 -0
  90. data/doc/demo/test/smoke/process_stats.rb +21 -0
  91. data/doc/demo/test/test_helper.rb +30 -0
  92. data/doc/demo/test/unit/comment_test.rb +217 -0
  93. data/doc/demo/test/unit/content_test.rb +705 -0
  94. data/doc/demo/test/unit/ferret_result_test.rb +24 -0
  95. data/doc/demo/test/unit/multi_index_test.rb +329 -0
  96. data/doc/demo/test/unit/remote_index_test.rb +23 -0
  97. data/doc/demo/test/unit/shared_index1_test.rb +108 -0
  98. data/doc/demo/test/unit/shared_index2_test.rb +13 -0
  99. data/doc/demo/test/unit/sort_test.rb +21 -0
  100. data/doc/demo/test/unit/special_content_test.rb +25 -0
  101. data/doc/demo/vendor/plugins/will_paginate/LICENSE +18 -0
  102. data/doc/demo/vendor/plugins/will_paginate/README +108 -0
  103. data/doc/demo/vendor/plugins/will_paginate/Rakefile +23 -0
  104. data/doc/demo/vendor/plugins/will_paginate/init.rb +21 -0
  105. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/collection.rb +45 -0
  106. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb +44 -0
  107. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/finder.rb +159 -0
  108. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb +95 -0
  109. data/doc/demo/vendor/plugins/will_paginate/test/array_pagination_test.rb +23 -0
  110. data/doc/demo/vendor/plugins/will_paginate/test/boot.rb +27 -0
  111. data/doc/demo/vendor/plugins/will_paginate/test/console +10 -0
  112. data/doc/demo/vendor/plugins/will_paginate/test/finder_test.rb +219 -0
  113. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/admin.rb +3 -0
  114. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/companies.yml +24 -0
  115. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/company.rb +23 -0
  116. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/developer.rb +11 -0
  117. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/developers_projects.yml +13 -0
  118. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/project.rb +4 -0
  119. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/projects.yml +7 -0
  120. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/replies.yml +20 -0
  121. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/reply.rb +5 -0
  122. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/schema.sql +44 -0
  123. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/topic.rb +19 -0
  124. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/topics.yml +30 -0
  125. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/user.rb +2 -0
  126. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/users.yml +35 -0
  127. data/doc/demo/vendor/plugins/will_paginate/test/helper.rb +42 -0
  128. data/doc/demo/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb +64 -0
  129. data/doc/demo/vendor/plugins/will_paginate/test/lib/load_fixtures.rb +10 -0
  130. data/doc/demo/vendor/plugins/will_paginate/test/pagination_test.rb +136 -0
  131. data/doc/monit-example +22 -0
  132. data/init.rb +24 -0
  133. data/install.rb +18 -0
  134. data/lib/act_methods.rb +147 -0
  135. data/lib/acts_as_ferret.rb +593 -0
  136. data/lib/ar_mysql_auto_reconnect_patch.rb +41 -0
  137. data/lib/blank_slate.rb +54 -0
  138. data/lib/bulk_indexer.rb +56 -0
  139. data/lib/class_methods.rb +279 -0
  140. data/lib/ferret_extensions.rb +192 -0
  141. data/lib/ferret_find_methods.rb +142 -0
  142. data/lib/ferret_result.rb +58 -0
  143. data/lib/ferret_server.rb +238 -0
  144. data/lib/index.rb +99 -0
  145. data/lib/instance_methods.rb +172 -0
  146. data/lib/local_index.rb +202 -0
  147. data/lib/more_like_this.rb +217 -0
  148. data/lib/multi_index.rb +133 -0
  149. data/lib/rdig_adapter.rb +149 -0
  150. data/lib/remote_functions.rb +43 -0
  151. data/lib/remote_index.rb +54 -0
  152. data/lib/remote_multi_index.rb +20 -0
  153. data/lib/search_results.rb +50 -0
  154. data/lib/server_manager.rb +71 -0
  155. data/lib/unix_daemon.rb +86 -0
  156. data/lib/without_ar.rb +52 -0
  157. data/recipes/aaf_recipes.rb +116 -0
  158. data/script/ferret_daemon +94 -0
  159. data/script/ferret_server +12 -0
  160. data/script/ferret_service +178 -0
  161. data/tasks/ferret.rake +39 -0
  162. metadata +246 -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