will_paginate 2.3.17 → 3.0.pre

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.

Potentially problematic release.


This version of will_paginate might be problematic. Click here for more details.

Files changed (83) hide show
  1. data/CHANGELOG.rdoc +24 -80
  2. data/LICENSE +1 -1
  3. data/README.rdoc +125 -0
  4. data/Rakefile +26 -22
  5. data/lib/will_paginate.rb +10 -84
  6. data/lib/will_paginate/array.rb +25 -8
  7. data/lib/will_paginate/collection.rb +15 -28
  8. data/lib/will_paginate/core_ext.rb +26 -0
  9. data/lib/will_paginate/deprecation.rb +50 -0
  10. data/lib/will_paginate/finders.rb +9 -0
  11. data/lib/will_paginate/finders/active_record.rb +158 -0
  12. data/lib/will_paginate/finders/active_resource.rb +51 -0
  13. data/lib/will_paginate/finders/base.rb +112 -0
  14. data/lib/will_paginate/finders/data_mapper.rb +30 -0
  15. data/lib/will_paginate/finders/sequel.rb +23 -0
  16. data/lib/will_paginate/railtie.rb +26 -0
  17. data/lib/will_paginate/version.rb +5 -5
  18. data/lib/will_paginate/view_helpers.rb +25 -436
  19. data/lib/will_paginate/view_helpers/action_view.rb +142 -0
  20. data/lib/will_paginate/view_helpers/base.rb +126 -0
  21. data/lib/will_paginate/view_helpers/link_renderer.rb +130 -0
  22. data/lib/will_paginate/view_helpers/link_renderer_base.rb +83 -0
  23. data/lib/will_paginate/view_helpers/merb.rb +13 -0
  24. data/spec/collection_spec.rb +147 -0
  25. data/spec/console +8 -0
  26. data/spec/console_fixtures.rb +8 -0
  27. data/spec/database.yml +22 -0
  28. data/spec/finders/active_record_spec.rb +377 -0
  29. data/spec/finders/active_resource_spec.rb +52 -0
  30. data/spec/finders/activerecord_test_connector.rb +114 -0
  31. data/spec/finders/data_mapper_spec.rb +62 -0
  32. data/spec/finders/data_mapper_test_connector.rb +20 -0
  33. data/spec/finders/sequel_spec.rb +53 -0
  34. data/spec/finders/sequel_test_connector.rb +9 -0
  35. data/spec/finders_spec.rb +76 -0
  36. data/{test → spec}/fixtures/admin.rb +0 -0
  37. data/{test → spec}/fixtures/developer.rb +2 -3
  38. data/{test → spec}/fixtures/developers_projects.yml +0 -0
  39. data/{test → spec}/fixtures/project.rb +2 -6
  40. data/{test → spec}/fixtures/projects.yml +1 -1
  41. data/{test → spec}/fixtures/replies.yml +0 -0
  42. data/{test → spec}/fixtures/reply.rb +1 -1
  43. data/{test → spec}/fixtures/schema.rb +0 -0
  44. data/spec/fixtures/topic.rb +7 -0
  45. data/{test → spec}/fixtures/topics.yml +0 -0
  46. data/{test → spec}/fixtures/user.rb +0 -0
  47. data/{test → spec}/fixtures/users.yml +0 -0
  48. data/spec/rcov.opts +2 -0
  49. data/spec/spec.opts +2 -0
  50. data/spec/spec_helper.rb +74 -0
  51. data/spec/tasks.rake +60 -0
  52. data/spec/view_helpers/action_view_spec.rb +345 -0
  53. data/spec/view_helpers/base_spec.rb +64 -0
  54. data/spec/view_helpers/link_renderer_base_spec.rb +84 -0
  55. data/spec/view_helpers/view_example_group.rb +103 -0
  56. metadata +60 -65
  57. data/README.md +0 -53
  58. data/lib/will_paginate/finder.rb +0 -269
  59. data/lib/will_paginate/i18n.rb +0 -29
  60. data/lib/will_paginate/locale/en.yml +0 -33
  61. data/lib/will_paginate/named_scope.rb +0 -170
  62. data/lib/will_paginate/named_scope_patch.rb +0 -37
  63. data/lib/will_paginate/per_page.rb +0 -27
  64. data/test/ci.rb +0 -60
  65. data/test/collection_test.rb +0 -160
  66. data/test/console +0 -8
  67. data/test/database.yml +0 -16
  68. data/test/finder_test.rb +0 -527
  69. data/test/fixtures/topic.rb +0 -12
  70. data/test/gemfiles/Gemfile.1.2 +0 -13
  71. data/test/gemfiles/Gemfile.1.2.lock +0 -39
  72. data/test/gemfiles/Gemfile.2.0 +0 -16
  73. data/test/gemfiles/Gemfile.2.0.lock +0 -28
  74. data/test/gemfiles/Gemfile.2.1 +0 -16
  75. data/test/gemfiles/Gemfile.2.1.lock +0 -28
  76. data/test/gemfiles/Gemfile.2.2 +0 -16
  77. data/test/gemfiles/Gemfile.2.2.lock +0 -28
  78. data/test/helper.rb +0 -34
  79. data/test/lib/activerecord_test_case.rb +0 -38
  80. data/test/lib/activerecord_test_connector.rb +0 -86
  81. data/test/lib/load_fixtures.rb +0 -12
  82. data/test/lib/view_test_process.rb +0 -186
  83. data/test/view_test.rb +0 -380
@@ -1,269 +0,0 @@
1
- require 'will_paginate/core_ext'
2
- require 'will_paginate/per_page'
3
-
4
- module WillPaginate
5
- # A mixin for ActiveRecord::Base. Provides +per_page+ class method
6
- # and hooks things up to provide paginating finders.
7
- #
8
- # Find out more in WillPaginate::Finder::ClassMethods
9
- #
10
- module Finder
11
- def self.included(base)
12
- base.extend PerPage
13
- base.extend ClassMethods
14
- class << base
15
- alias_method_chain :method_missing, :paginate
16
- end
17
- end
18
-
19
- # = Paginating finders for ActiveRecord models
20
- #
21
- # WillPaginate adds +paginate+, +per_page+ and other methods to
22
- # ActiveRecord::Base class methods and associations. It also hooks into
23
- # +method_missing+ to intercept pagination calls to dynamic finders such as
24
- # +paginate_by_user_id+ and translate them to ordinary finders
25
- # (+find_all_by_user_id+ in this case).
26
- #
27
- # In short, paginating finders are equivalent to ActiveRecord finders; the
28
- # only difference is that we start with "paginate" instead of "find" and
29
- # that <tt>:page</tt> is required parameter:
30
- #
31
- # @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC'
32
- #
33
- # In paginating finders, "all" is implicit. There is no sense in paginating
34
- # a single record, right? So, you can drop the <tt>:all</tt> argument:
35
- #
36
- # Post.paginate(...) => Post.find :all
37
- # Post.paginate_all_by_something => Post.find_all_by_something
38
- # Post.paginate_by_something => Post.find_all_by_something
39
- #
40
- # == The importance of the <tt>:order</tt> parameter
41
- #
42
- # In ActiveRecord finders, <tt>:order</tt> parameter specifies columns for
43
- # the <tt>ORDER BY</tt> clause in SQL. It is important to have it, since
44
- # pagination only makes sense with ordered sets. Without the <tt>ORDER
45
- # BY</tt> clause, databases aren't required to do consistent ordering when
46
- # performing <tt>SELECT</tt> queries; this is especially true for
47
- # PostgreSQL.
48
- #
49
- # Therefore, make sure you are doing ordering on a column that makes the
50
- # most sense in the current context. Make that obvious to the user, also.
51
- # For perfomance reasons you will also want to add an index to that column.
52
- module ClassMethods
53
- # This is the main paginating finder.
54
- #
55
- # == Special parameters for paginating finders
56
- # * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil
57
- # * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (which is 30 if not overridden)
58
- # * <tt>:total_entries</tt> -- use only if you manually count total entries
59
- # * <tt>:count</tt> -- additional options that are passed on to +count+
60
- # * <tt>:finder</tt> -- name of the ActiveRecord finder used (default: "find")
61
- #
62
- # All other options (+conditions+, +order+, ...) are forwarded to +find+
63
- # and +count+ calls.
64
- def paginate(*args)
65
- options = args.pop
66
- page, per_page, total_entries = wp_parse_options(options)
67
- finder = (options[:finder] || 'find').to_s
68
-
69
- if finder == 'find'
70
- # an array of IDs may have been given:
71
- total_entries ||= (Array === args.first and args.first.size)
72
- # :all is implicit
73
- args.unshift(:all) if args.empty?
74
- end
75
-
76
- WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
77
- count_options = options.except :page, :per_page, :total_entries, :finder
78
- find_options = count_options.except(:count).update(:offset => pager.offset, :limit => pager.per_page)
79
-
80
- args << find_options
81
- # @options_from_last_find = nil
82
- pager.replace(send(finder, *args) { |*a| yield(*a) if block_given? })
83
-
84
- # magic counting for user convenience:
85
- pager.total_entries = wp_count(count_options, args, finder) unless pager.total_entries
86
- end
87
- end
88
-
89
- # Iterates through all records by loading one page at a time. This is useful
90
- # for migrations or any other use case where you don't want to load all the
91
- # records in memory at once.
92
- #
93
- # It uses +paginate+ internally; therefore it accepts all of its options.
94
- # You can specify a starting page with <tt>:page</tt> (default is 1). Default
95
- # <tt>:order</tt> is <tt>"id"</tt>, override if necessary.
96
- #
97
- # See {Faking Cursors in ActiveRecord}[http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord]
98
- # where Jamis Buck describes this and a more efficient way for MySQL.
99
- def paginated_each(options = {})
100
- order = scope(:find, :order) || 'id'
101
- options = { :order => order, :page => 1 }.merge options
102
- options[:page] = options[:page].to_i
103
- options[:total_entries] = 0 # skip the individual count queries
104
- total = 0
105
-
106
- begin
107
- collection = paginate(options)
108
- with_exclusive_scope(:find => {}) do
109
- # using exclusive scope so that the block is yielded in scope-free context
110
- total += collection.each { |item| yield item }.size
111
- end
112
- options[:page] += 1
113
- end until collection.size < collection.per_page
114
-
115
- total
116
- end
117
-
118
- # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
119
- # based on the params otherwise used by paginating finds: +page+ and
120
- # +per_page+.
121
- #
122
- # Example:
123
- #
124
- # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000],
125
- # :page => params[:page], :per_page => 3
126
- #
127
- # A query for counting rows will automatically be generated if you don't
128
- # supply <tt>:total_entries</tt>. If you experience problems with this
129
- # generated SQL, you might want to perform the count manually in your
130
- # application.
131
- #
132
- def paginate_by_sql(sql, options)
133
- WillPaginate::Collection.create(*wp_parse_options(options)) do |pager|
134
- query = sanitize_sql(sql.dup)
135
- original_query = query.dup
136
- # add limit, offset
137
- add_limit! query, :offset => pager.offset, :limit => pager.per_page
138
- # perfom the find
139
- pager.replace find_by_sql(query)
140
-
141
- unless pager.total_entries
142
- count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s]+$/mi, ''
143
- count_query = "SELECT COUNT(*) FROM (#{count_query})"
144
-
145
- unless self.connection.adapter_name =~ /^(oracle|oci$)/i
146
- count_query << ' AS count_table'
147
- end
148
- # perform the count query
149
- pager.total_entries = count_by_sql(count_query)
150
- end
151
- end
152
- end
153
-
154
- def respond_to?(method, include_priv = false) #:nodoc:
155
- case method.to_sym
156
- when :paginate, :paginate_by_sql
157
- true
158
- else
159
- super || super(method.to_s.sub(/^paginate/, 'find'), include_priv)
160
- end
161
- end
162
-
163
- protected
164
-
165
- def respond_to_missing?(method, include_private = false)
166
- method.to_s.index('paginate') == 0 || super
167
- end
168
-
169
- def method_missing_with_paginate(method, *args) #:nodoc:
170
- # did somebody tried to paginate? if not, let them be
171
- unless method.to_s.index('paginate') == 0
172
- if block_given?
173
- return method_missing_without_paginate(method, *args) { |*a| yield(*a) }
174
- else
175
- return method_missing_without_paginate(method, *args)
176
- end
177
- end
178
-
179
- # paginate finders are really just find_* with limit and offset
180
- finder = method.to_s.sub('paginate', 'find')
181
- finder.sub!('find', 'find_all') if finder.index('find_by_') == 0
182
-
183
- options = args.pop
184
- raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys
185
- options = options.dup
186
- options[:finder] = finder
187
- args << options
188
-
189
- paginate(*args) { |*a| yield(*a) if block_given? }
190
- end
191
-
192
- # Does the not-so-trivial job of finding out the total number of entries
193
- # in the database. It relies on the ActiveRecord +count+ method.
194
- def wp_count(options, args, finder)
195
- excludees = [:count, :order, :limit, :offset, :readonly]
196
- excludees << :from unless ActiveRecord::Calculations::CALCULATIONS_OPTIONS.include?(:from)
197
-
198
- # we may be in a model or an association proxy
199
- klass = (@owner and @reflection) ? @reflection.klass : self
200
-
201
- # Use :select from scope if it isn't already present.
202
- options[:select] = scope(:find, :select) unless options[:select]
203
-
204
- if options[:select] and options[:select] =~ /^\s*DISTINCT\b/i
205
- # Remove quoting and check for table_name.*-like statement.
206
- if options[:select].gsub(/[`"]/, '') =~ /\w+\.\*/
207
- options[:select] = "DISTINCT #{klass.table_name}.#{klass.primary_key}"
208
- end
209
- else
210
- excludees << :select # only exclude the select param if it doesn't begin with DISTINCT
211
- end
212
-
213
- # count expects (almost) the same options as find
214
- count_options = options.except *excludees
215
-
216
- # merge the hash found in :count
217
- # this allows you to specify :select, :order, or anything else just for the count query
218
- count_options.update options[:count] if options[:count]
219
-
220
- # forget about includes if they are irrelevant (Rails 2.1)
221
- if count_options[:include] and
222
- klass.private_methods.include_method?(:references_eager_loaded_tables?) and
223
- !klass.send(:references_eager_loaded_tables?, count_options)
224
- count_options.delete :include
225
- end
226
-
227
- # we may have to scope ...
228
- counter = Proc.new { count(count_options) }
229
-
230
- count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with'))
231
- # scope_out adds a 'with_finder' method which acts like with_scope, if it's present
232
- # then execute the count with the scoping provided by the with_finder
233
- send(scoper, &counter)
234
- elsif finder =~ /^find_(all_by|by)_([_a-zA-Z]\w*)$/
235
- # extract conditions from calls like "paginate_by_foo_and_bar"
236
- attribute_names = $2.split('_and_')
237
- conditions = construct_attributes_from_arguments(attribute_names, args)
238
- with_scope(:find => { :conditions => conditions }, &counter)
239
- else
240
- counter.call
241
- end
242
-
243
- (!count.is_a?(Integer) && count.respond_to?(:length)) ? count.length : count
244
- end
245
-
246
- def wp_parse_options(options) #:nodoc:
247
- raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys
248
- options = options.symbolize_keys
249
- raise ArgumentError, ':page parameter required' unless options.key? :page
250
-
251
- if options[:count] and options[:total_entries]
252
- raise ArgumentError, ':count and :total_entries are mutually exclusive'
253
- end
254
-
255
- page = options[:page] || 1
256
- per_page = options[:per_page] || self.per_page
257
- total = options[:total_entries]
258
- [page, per_page, total]
259
- end
260
-
261
- private
262
-
263
- # def find_every_with_paginate(options)
264
- # @options_from_last_find = options
265
- # find_every_without_paginate(options)
266
- # end
267
- end
268
- end
269
- end
@@ -1,29 +0,0 @@
1
- module WillPaginate
2
- module I18n
3
- def self.locale_dir
4
- File.expand_path('../locale', __FILE__)
5
- end
6
-
7
- def self.load_path
8
- Dir["#{locale_dir}/*.{rb,yml}"]
9
- end
10
-
11
- def will_paginate_translate(keys, options = {})
12
- if defined? ::I18n
13
- defaults = Array(keys).dup
14
- if block_given?
15
- if defined? ::I18n::Backend::Simple::MATCH
16
- # procs in defaults array were not supported back then
17
- defaults << yield(defaults.first, options)
18
- else
19
- defaults << Proc.new
20
- end
21
- end
22
- ::I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => :will_paginate))
23
- else
24
- key = Array === keys ? keys.first : keys
25
- yield key, options
26
- end
27
- end
28
- end
29
- end
@@ -1,33 +0,0 @@
1
- en:
2
- will_paginate:
3
- previous_label: "&laquo; Previous"
4
- next_label: "Next &raquo;"
5
- page_gap: "&hellip;"
6
-
7
- page_entries_info:
8
- single_page:
9
- zero: "No %{model} found"
10
- one: "Displaying 1 %{model}"
11
- other: "Displaying all %{count} %{model}"
12
- single_page_html:
13
- zero: "No %{model} found"
14
- one: "Displaying <b>1</b> %{model}"
15
- other: "Displaying <b>all&nbsp;%{count}</b> %{model}"
16
-
17
- multi_page: "Displaying %{model} %{from} - %{to} of %{count} in total"
18
- multi_page_html: "Displaying %{model} <b>%{from}&nbsp;-&nbsp;%{to}</b> of <b>%{count}</b> in total"
19
-
20
- # models:
21
- # entry:
22
- # zero: entries
23
- # one: entry
24
- # few: entries
25
- # other: entries
26
-
27
- # line_item:
28
- # page_entries_info:
29
- # single_page:
30
- # zero: "Your shopping cart is empty"
31
- # one: "Displaying one item in your cart"
32
- # other: "Displaying all %{count} items"
33
- # multi_page: "Displaying items %{from} - %{to} of %{count} in total"
@@ -1,170 +0,0 @@
1
- module WillPaginate
2
- # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate,
3
- # but in other aspects when managing complex conditions that you want to be reusable.
4
- module NamedScope
5
- # All subclasses of ActiveRecord::Base have two named_scopes:
6
- # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
7
- # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
8
- #
9
- # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
10
- # intermediate values (scopes) around as first-class objects is convenient.
11
- def self.included(base)
12
- base.class_eval do
13
- extend ClassMethods
14
- named_scope :scoped, lambda { |scope| scope }
15
- end
16
- end
17
-
18
- module ClassMethods
19
- def scopes
20
- read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
21
- end
22
-
23
- # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
24
- # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
25
- #
26
- # class Shirt < ActiveRecord::Base
27
- # named_scope :red, :conditions => {:color => 'red'}
28
- # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
29
- # end
30
- #
31
- # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
32
- # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
33
- #
34
- # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
35
- # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
36
- # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
37
- # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
38
- # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
39
- #
40
- # These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
41
- # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
42
- # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
43
- #
44
- # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to
45
- # <tt>has_many</tt> associations. If,
46
- #
47
- # class Person < ActiveRecord::Base
48
- # has_many :shirts
49
- # end
50
- #
51
- # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
52
- # only shirts.
53
- #
54
- # Named scopes can also be procedural.
55
- #
56
- # class Shirt < ActiveRecord::Base
57
- # named_scope :colored, lambda { |color|
58
- # { :conditions => { :color => color } }
59
- # }
60
- # end
61
- #
62
- # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
63
- #
64
- # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
65
- #
66
- # class Shirt < ActiveRecord::Base
67
- # named_scope :red, :conditions => {:color => 'red'} do
68
- # def dom_id
69
- # 'red_shirts'
70
- # end
71
- # end
72
- # end
73
- #
74
- #
75
- # For testing complex named scopes, you can examine the scoping options using the
76
- # <tt>proxy_options</tt> method on the proxy itself.
77
- #
78
- # class Shirt < ActiveRecord::Base
79
- # named_scope :colored, lambda { |color|
80
- # { :conditions => { :color => color } }
81
- # }
82
- # end
83
- #
84
- # expected_options = { :conditions => { :colored => 'red' } }
85
- # assert_equal expected_options, Shirt.colored('red').proxy_options
86
- def named_scope(name, options = {})
87
- name = name.to_sym
88
- scopes[name] = lambda do |parent_scope, *args|
89
- Scope.new(parent_scope, case options
90
- when Hash
91
- options
92
- when Proc
93
- options.call(*args)
94
- end) { |*a| yield(*a) if block_given? }
95
- end
96
- (class << self; self end).instance_eval do
97
- define_method name do |*args|
98
- scopes[name].call(self, *args)
99
- end
100
- end
101
- end
102
- end
103
-
104
- class Scope
105
- attr_reader :proxy_scope, :proxy_options
106
-
107
- [].methods.each do |m|
108
- unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/
109
- delegate m, :to => :proxy_found
110
- end
111
- end
112
-
113
- delegate :scopes, :with_scope, :to => :proxy_scope
114
-
115
- def initialize(proxy_scope, options)
116
- [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
117
- extend Module.new { |*args| yield(*args) } if block_given?
118
- @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
119
- end
120
-
121
- def reload
122
- load_found; self
123
- end
124
-
125
- def first(*args)
126
- if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
127
- proxy_found.first(*args)
128
- else
129
- find(:first, *args)
130
- end
131
- end
132
-
133
- def last(*args)
134
- if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
135
- proxy_found.last(*args)
136
- else
137
- find(:last, *args)
138
- end
139
- end
140
-
141
- def empty?
142
- @found ? @found.empty? : count.zero?
143
- end
144
-
145
- def respond_to?(method, include_private = false)
146
- super || @proxy_scope.respond_to?(method, include_private)
147
- end
148
-
149
- protected
150
- def proxy_found
151
- @found || load_found
152
- end
153
-
154
- private
155
- def method_missing(method, *args)
156
- if scopes.include?(method)
157
- scopes[method].call(self, *args)
158
- else
159
- with_scope :find => proxy_options do
160
- proxy_scope.send(method, *args) { |*a| yield(*a) if block_given? }
161
- end
162
- end
163
- end
164
-
165
- def load_found
166
- @found = find(:all)
167
- end
168
- end
169
- end
170
- end