mingusbabcock-composite_primary_keys 2.2.2 → 2.2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/History.txt +156 -0
  2. data/Manifest.txt +122 -0
  3. data/README.txt +41 -0
  4. data/README_DB2.txt +33 -0
  5. data/Rakefile +65 -0
  6. data/init.rb +2 -0
  7. data/install.rb +30 -0
  8. data/lib/adapter_helper/base.rb +63 -0
  9. data/lib/adapter_helper/mysql.rb +13 -0
  10. data/lib/adapter_helper/oracle.rb +12 -0
  11. data/lib/adapter_helper/postgresql.rb +13 -0
  12. data/lib/adapter_helper/sqlite3.rb +13 -0
  13. data/lib/composite_primary_keys.rb +56 -0
  14. data/lib/composite_primary_keys/association_preload.rb +253 -0
  15. data/lib/composite_primary_keys/associations.rb +428 -0
  16. data/lib/composite_primary_keys/attribute_methods.rb +84 -0
  17. data/lib/composite_primary_keys/base.rb +341 -0
  18. data/lib/composite_primary_keys/calculations.rb +69 -0
  19. data/lib/composite_primary_keys/composite_arrays.rb +30 -0
  20. data/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb +21 -0
  21. data/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb +15 -0
  22. data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +53 -0
  23. data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +15 -0
  24. data/lib/composite_primary_keys/fixtures.rb +8 -0
  25. data/lib/composite_primary_keys/migration.rb +20 -0
  26. data/lib/composite_primary_keys/reflection.rb +19 -0
  27. data/lib/composite_primary_keys/version.rb +8 -0
  28. data/loader.rb +24 -0
  29. data/local/database_connections.rb.sample +10 -0
  30. data/local/paths.rb.sample +2 -0
  31. data/local/tasks.rb.sample +2 -0
  32. data/scripts/console.rb +48 -0
  33. data/scripts/txt2html +67 -0
  34. data/scripts/txt2js +59 -0
  35. data/tasks/activerecord_selection.rake +43 -0
  36. data/tasks/databases.rake +12 -0
  37. data/tasks/databases/mysql.rake +30 -0
  38. data/tasks/databases/oracle.rake +25 -0
  39. data/tasks/databases/postgresql.rake +26 -0
  40. data/tasks/databases/sqlite3.rake +28 -0
  41. data/tasks/deployment.rake +22 -0
  42. data/tasks/local_setup.rake +13 -0
  43. data/tasks/website.rake +18 -0
  44. data/test/README_tests.txt +67 -0
  45. data/test/abstract_unit.rb +94 -0
  46. data/test/connections/native_ibm_db/connection.rb +23 -0
  47. data/test/connections/native_mysql/connection.rb +13 -0
  48. data/test/connections/native_oracle/connection.rb +14 -0
  49. data/test/connections/native_postgresql/connection.rb +9 -0
  50. data/test/connections/native_sqlite/connection.rb +9 -0
  51. data/test/fixtures/article.rb +5 -0
  52. data/test/fixtures/articles.yml +6 -0
  53. data/test/fixtures/comment.rb +6 -0
  54. data/test/fixtures/comments.yml +16 -0
  55. data/test/fixtures/db_definitions/db2-create-tables.sql +113 -0
  56. data/test/fixtures/db_definitions/db2-drop-tables.sql +16 -0
  57. data/test/fixtures/db_definitions/mysql.sql +174 -0
  58. data/test/fixtures/db_definitions/oracle.drop.sql +39 -0
  59. data/test/fixtures/db_definitions/oracle.sql +188 -0
  60. data/test/fixtures/db_definitions/postgresql.sql +199 -0
  61. data/test/fixtures/db_definitions/sqlite.sql +160 -0
  62. data/test/fixtures/department.rb +5 -0
  63. data/test/fixtures/departments.yml +3 -0
  64. data/test/fixtures/employee.rb +4 -0
  65. data/test/fixtures/employees.yml +9 -0
  66. data/test/fixtures/group.rb +3 -0
  67. data/test/fixtures/groups.yml +3 -0
  68. data/test/fixtures/hack.rb +6 -0
  69. data/test/fixtures/hacks.yml +2 -0
  70. data/test/fixtures/membership.rb +7 -0
  71. data/test/fixtures/membership_status.rb +3 -0
  72. data/test/fixtures/membership_statuses.yml +10 -0
  73. data/test/fixtures/memberships.yml +6 -0
  74. data/test/fixtures/product.rb +7 -0
  75. data/test/fixtures/product_tariff.rb +5 -0
  76. data/test/fixtures/product_tariffs.yml +12 -0
  77. data/test/fixtures/products.yml +6 -0
  78. data/test/fixtures/reading.rb +4 -0
  79. data/test/fixtures/readings.yml +10 -0
  80. data/test/fixtures/reference_code.rb +7 -0
  81. data/test/fixtures/reference_codes.yml +28 -0
  82. data/test/fixtures/reference_type.rb +7 -0
  83. data/test/fixtures/reference_types.yml +9 -0
  84. data/test/fixtures/street.rb +3 -0
  85. data/test/fixtures/streets.yml +15 -0
  86. data/test/fixtures/suburb.rb +6 -0
  87. data/test/fixtures/suburbs.yml +9 -0
  88. data/test/fixtures/tariff.rb +6 -0
  89. data/test/fixtures/tariffs.yml +13 -0
  90. data/test/fixtures/user.rb +10 -0
  91. data/test/fixtures/users.yml +6 -0
  92. data/test/hash_tricks.rb +34 -0
  93. data/test/plugins/pagination.rb +405 -0
  94. data/test/plugins/pagination_helper.rb +135 -0
  95. data/test/test_associations.rb +160 -0
  96. data/test/test_attribute_methods.rb +22 -0
  97. data/test/test_attributes.rb +84 -0
  98. data/test/test_clone.rb +34 -0
  99. data/test/test_composite_arrays.rb +51 -0
  100. data/test/test_create.rb +68 -0
  101. data/test/test_delete.rb +96 -0
  102. data/test/test_dummy.rb +28 -0
  103. data/test/test_exists.rb +29 -0
  104. data/test/test_find.rb +73 -0
  105. data/test/test_ids.rb +97 -0
  106. data/test/test_miscellaneous.rb +39 -0
  107. data/test/test_pagination.rb +38 -0
  108. data/test/test_polymorphic.rb +31 -0
  109. data/test/test_santiago.rb +27 -0
  110. data/test/test_tutorial_examle.rb +26 -0
  111. data/test/test_update.rb +40 -0
  112. data/website/index.html +199 -0
  113. data/website/index.txt +159 -0
  114. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  115. data/website/stylesheets/screen.css +126 -0
  116. data/website/template.js +3 -0
  117. data/website/template.rhtml +53 -0
  118. data/website/version-raw.js +3 -0
  119. data/website/version-raw.txt +2 -0
  120. data/website/version.js +4 -0
  121. data/website/version.txt +3 -0
  122. metadata +180 -18
@@ -0,0 +1,5 @@
1
+ class ProductTariff < ActiveRecord::Base
2
+ set_primary_keys :product_id, :tariff_id, :tariff_start_date
3
+ belongs_to :product, :foreign_key => :product_id
4
+ belongs_to :tariff, :foreign_key => [:tariff_id, :tariff_start_date]
5
+ end
@@ -0,0 +1,12 @@
1
+ first_flat:
2
+ product_id: 1
3
+ tariff_id: 1
4
+ tariff_start_date: <%= Date.today.to_s(:db) %>
5
+ first_free:
6
+ product_id: 1
7
+ tariff_id: 2
8
+ tariff_start_date: <%= Date.today.to_s(:db) %>
9
+ second_free:
10
+ product_id: 2
11
+ tariff_id: 2
12
+ tariff_start_date: <%= Date.today.to_s(:db) %>
@@ -0,0 +1,6 @@
1
+ first_product:
2
+ id: 1
3
+ name: Product One
4
+ second_product:
5
+ id: 2
6
+ name: Product Two
@@ -0,0 +1,4 @@
1
+ class Reading < ActiveRecord::Base
2
+ belongs_to :article
3
+ belongs_to :user
4
+ end
@@ -0,0 +1,10 @@
1
+ santiago_first:
2
+ id: 1
3
+ user_id: 1
4
+ article_id: 1
5
+ rating: 4
6
+ santiago_second:
7
+ id: 2
8
+ user_id: 1
9
+ article_id: 2
10
+ rating: 5
@@ -0,0 +1,7 @@
1
+ class ReferenceCode < ActiveRecord::Base
2
+ set_primary_keys :reference_type_id, :reference_code
3
+
4
+ belongs_to :reference_type, :foreign_key => "reference_type_id"
5
+
6
+ validates_presence_of :reference_code, :code_label, :abbreviation
7
+ end
@@ -0,0 +1,28 @@
1
+ name_prefix_mr:
2
+ reference_type_id: 1
3
+ reference_code: 1
4
+ code_label: MR
5
+ abbreviation: Mr
6
+ name_prefix_mrs:
7
+ reference_type_id: 1
8
+ reference_code: 2
9
+ code_label: MRS
10
+ abbreviation: Mrs
11
+ name_prefix_ms:
12
+ reference_type_id: 1
13
+ reference_code: 3
14
+ code_label: MS
15
+ abbreviation: Ms
16
+
17
+ gender_male:
18
+ reference_type_id: 2
19
+ reference_code: 1
20
+ code_label: MALE
21
+ abbreviation: Male
22
+ gender_female:
23
+ reference_type_id: 2
24
+ reference_code: 2
25
+ code_label: FEMALE
26
+ abbreviation: Female
27
+
28
+
@@ -0,0 +1,7 @@
1
+ class ReferenceType < ActiveRecord::Base
2
+ set_primary_key :reference_type_id
3
+ has_many :reference_codes, :foreign_key => "reference_type_id"
4
+
5
+ validates_presence_of :type_label, :abbreviation
6
+ validates_uniqueness_of :type_label
7
+ end
@@ -0,0 +1,9 @@
1
+ name_prefix:
2
+ reference_type_id: 1
3
+ type_label: NAME_PREFIX
4
+ abbreviation: Name Prefix
5
+
6
+ gender:
7
+ reference_type_id: 2
8
+ type_label: GENDER
9
+ abbreviation: Gender
@@ -0,0 +1,3 @@
1
+ class Street < ActiveRecord::Base
2
+ belongs_to :suburb, :foreign_key => [:city_id, :suburb_id]
3
+ end
@@ -0,0 +1,15 @@
1
+ first:
2
+ id: 1
3
+ city_id: 1
4
+ suburb_id: 1
5
+ name: First Street
6
+ second1:
7
+ id: 2
8
+ city_id: 2
9
+ suburb_id: 1
10
+ name: First Street
11
+ second2:
12
+ id: 3
13
+ city_id: 2
14
+ suburb_id: 1
15
+ name: Second Street
@@ -0,0 +1,6 @@
1
+ class Suburb < ActiveRecord::Base
2
+ set_primary_keys :city_id, :suburb_id
3
+ has_many :streets, :foreign_key => [:city_id, :suburb_id]
4
+ has_many :first_streets, :foreign_key => [:city_id, :suburb_id],
5
+ :class_name => 'Street', :conditions => "streets.name = 'First Street'"
6
+ end
@@ -0,0 +1,9 @@
1
+ first:
2
+ city_id: 1
3
+ suburb_id: 1
4
+ name: First Suburb
5
+ second:
6
+ city_id: 2
7
+ suburb_id: 1
8
+ name: Second Suburb
9
+
@@ -0,0 +1,6 @@
1
+ class Tariff < ActiveRecord::Base
2
+ set_primary_keys [:tariff_id, :start_date]
3
+ has_many :product_tariffs, :foreign_key => [:tariff_id, :tariff_start_date]
4
+ has_one :product_tariff, :foreign_key => [:tariff_id, :tariff_start_date]
5
+ has_many :products, :through => :product_tariffs, :foreign_key => [:tariff_id, :tariff_start_date]
6
+ end
@@ -0,0 +1,13 @@
1
+ flat:
2
+ tariff_id: 1
3
+ start_date: <%= Date.today.to_s(:db) %>
4
+ amount: 50
5
+ free:
6
+ tariff_id: 2
7
+ start_date: <%= Date.today.to_s(:db) %>
8
+ amount: 0
9
+ flat_future:
10
+ tariff_id: 1
11
+ start_date: <%= Date.today.next.to_s(:db) %>
12
+ amount: 100
13
+
@@ -0,0 +1,10 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :readings
3
+ has_many :articles, :through => :readings
4
+ has_many :comments, :as => :person
5
+ has_many :hacks, :through => :comments, :source => :hack
6
+
7
+ def find_custom_articles
8
+ articles.find(:all, :conditions => ["name = ?", "Article One"])
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ santiago:
2
+ id: 1
3
+ name: Santiago
4
+ drnic:
5
+ id: 2
6
+ name: Dr Nic
@@ -0,0 +1,34 @@
1
+ # From:
2
+ # http://www.bigbold.com/snippets/posts/show/2178
3
+ # http://blog.caboo.se/articles/2006/06/11/stupid-hash-tricks
4
+ #
5
+ # An example utilisation of these methods in a controller is:
6
+ # def some_action
7
+ # # some script kiddie also passed in :bee, which we don't want tampered with _here_.
8
+ # @model = Model.create(params.pass(:foo, :bar))
9
+ # end
10
+ class Hash
11
+
12
+ # lets through the keys in the argument
13
+ # >> {:one => 1, :two => 2, :three => 3}.pass(:one)
14
+ # => {:one=>1}
15
+ def pass(*keys)
16
+ keys = keys.first if keys.first.is_a?(Array)
17
+ tmp = self.clone
18
+ tmp.delete_if {|k,v| ! keys.include?(k.to_sym) }
19
+ tmp.delete_if {|k,v| ! keys.include?(k.to_s) }
20
+ tmp
21
+ end
22
+
23
+ # blocks the keys in the arguments
24
+ # >> {:one => 1, :two => 2, :three => 3}.block(:one)
25
+ # => {:two=>2, :three=>3}
26
+ def block(*keys)
27
+ keys = keys.first if keys.first.is_a?(Array)
28
+ tmp = self.clone
29
+ tmp.delete_if {|k,v| keys.include?(k.to_sym) }
30
+ tmp.delete_if {|k,v| keys.include?(k.to_s) }
31
+ tmp
32
+ end
33
+
34
+ end
@@ -0,0 +1,405 @@
1
+ module ActionController
2
+ # === Action Pack pagination for Active Record collections
3
+ #
4
+ # The Pagination module aids in the process of paging large collections of
5
+ # Active Record objects. It offers macro-style automatic fetching of your
6
+ # model for multiple views, or explicit fetching for single actions. And if
7
+ # the magic isn't flexible enough for your needs, you can create your own
8
+ # paginators with a minimal amount of code.
9
+ #
10
+ # The Pagination module can handle as much or as little as you wish. In the
11
+ # controller, have it automatically query your model for pagination; or,
12
+ # if you prefer, create Paginator objects yourself.
13
+ #
14
+ # Pagination is included automatically for all controllers.
15
+ #
16
+ # For help rendering pagination links, see
17
+ # ActionView::Helpers::PaginationHelper.
18
+ #
19
+ # ==== Automatic pagination for every action in a controller
20
+ #
21
+ # class PersonController < ApplicationController
22
+ # model :person
23
+ #
24
+ # paginate :people, :order => 'last_name, first_name',
25
+ # :per_page => 20
26
+ #
27
+ # # ...
28
+ # end
29
+ #
30
+ # Each action in this controller now has access to a <tt>@people</tt>
31
+ # instance variable, which is an ordered collection of model objects for the
32
+ # current page (at most 20, sorted by last name and first name), and a
33
+ # <tt>@person_pages</tt> Paginator instance. The current page is determined
34
+ # by the <tt>params[:page]</tt> variable.
35
+ #
36
+ # ==== Pagination for a single action
37
+ #
38
+ # def list
39
+ # @person_pages, @people =
40
+ # paginate :people, :order => 'last_name, first_name'
41
+ # end
42
+ #
43
+ # Like the previous example, but explicitly creates <tt>@person_pages</tt>
44
+ # and <tt>@people</tt> for a single action, and uses the default of 10 items
45
+ # per page.
46
+ #
47
+ # ==== Custom/"classic" pagination
48
+ #
49
+ # def list
50
+ # @person_pages = Paginator.new self, Person.count, 10, params[:page]
51
+ # @people = Person.find :all, :order => 'last_name, first_name',
52
+ # :limit => @person_pages.items_per_page,
53
+ # :offset => @person_pages.current.offset
54
+ # end
55
+ #
56
+ # Explicitly creates the paginator from the previous example and uses
57
+ # Paginator#to_sql to retrieve <tt>@people</tt> from the model.
58
+ #
59
+ module Pagination
60
+ unless const_defined?(:OPTIONS)
61
+ # A hash holding options for controllers using macro-style pagination
62
+ OPTIONS = Hash.new
63
+
64
+ # The default options for pagination
65
+ DEFAULT_OPTIONS = {
66
+ :class_name => nil,
67
+ :singular_name => nil,
68
+ :per_page => 10,
69
+ :conditions => nil,
70
+ :order_by => nil,
71
+ :order => nil,
72
+ :join => nil,
73
+ :joins => nil,
74
+ :count => nil,
75
+ :include => nil,
76
+ :select => nil,
77
+ :group => nil,
78
+ :parameter => 'page'
79
+ }
80
+ else
81
+ DEFAULT_OPTIONS[:group] = nil
82
+ end
83
+
84
+ def self.included(base) #:nodoc:
85
+ super
86
+ base.extend(ClassMethods)
87
+ end
88
+
89
+ def self.validate_options!(collection_id, options, in_action) #:nodoc:
90
+ options.merge!(DEFAULT_OPTIONS) {|key, old, new| old}
91
+
92
+ valid_options = DEFAULT_OPTIONS.keys
93
+ valid_options << :actions unless in_action
94
+
95
+ unknown_option_keys = options.keys - valid_options
96
+ raise ActionController::ActionControllerError,
97
+ "Unknown options: #{unknown_option_keys.join(', ')}" unless
98
+ unknown_option_keys.empty?
99
+
100
+ options[:singular_name] ||= ActiveSupport::Inflector.singularize(collection_id.to_s)
101
+ options[:class_name] ||= ActiveSupport::Inflector.camelize(options[:singular_name])
102
+ end
103
+
104
+ # Returns a paginator and a collection of Active Record model instances
105
+ # for the paginator's current page. This is designed to be used in a
106
+ # single action; to automatically paginate multiple actions, consider
107
+ # ClassMethods#paginate.
108
+ #
109
+ # +options+ are:
110
+ # <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by singularizing the collection name
111
+ # <tt>:class_name</tt>:: the class name to use, if it can't be inferred by
112
+ # camelizing the singular name
113
+ # <tt>:per_page</tt>:: the maximum number of items to include in a
114
+ # single page. Defaults to 10
115
+ # <tt>:conditions</tt>:: optional conditions passed to Model.find(:all, *params) and
116
+ # Model.count
117
+ # <tt>:order</tt>:: optional order parameter passed to Model.find(:all, *params)
118
+ # <tt>:order_by</tt>:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params)
119
+ # <tt>:joins</tt>:: optional joins parameter passed to Model.find(:all, *params)
120
+ # and Model.count
121
+ # <tt>:join</tt>:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params)
122
+ # and Model.count
123
+ # <tt>:include</tt>:: optional eager loading parameter passed to Model.find(:all, *params)
124
+ # and Model.count
125
+ # <tt>:select</tt>:: :select parameter passed to Model.find(:all, *params)
126
+ #
127
+ # <tt>:count</tt>:: parameter passed as :select option to Model.count(*params)
128
+ #
129
+ # <tt>:group</tt>:: :group parameter passed to Model.find(:all, *params). It forces the use of DISTINCT instead of plain COUNT to come up with the total number of records
130
+ #
131
+ def paginate(collection_id, options={})
132
+ Pagination.validate_options!(collection_id, options, true)
133
+ paginator_and_collection_for(collection_id, options)
134
+ end
135
+
136
+ # These methods become class methods on any controller
137
+ module ClassMethods
138
+ # Creates a +before_filter+ which automatically paginates an Active
139
+ # Record model for all actions in a controller (or certain actions if
140
+ # specified with the <tt>:actions</tt> option).
141
+ #
142
+ # +options+ are the same as PaginationHelper#paginate, with the addition
143
+ # of:
144
+ # <tt>:actions</tt>:: an array of actions for which the pagination is
145
+ # active. Defaults to +nil+ (i.e., every action)
146
+ def paginate(collection_id, options={})
147
+ Pagination.validate_options!(collection_id, options, false)
148
+ module_eval do
149
+ before_filter :create_paginators_and_retrieve_collections
150
+ OPTIONS[self] ||= Hash.new
151
+ OPTIONS[self][collection_id] = options
152
+ end
153
+ end
154
+ end
155
+
156
+ def create_paginators_and_retrieve_collections #:nodoc:
157
+ Pagination::OPTIONS[self.class].each do |collection_id, options|
158
+ next unless options[:actions].include? action_name if
159
+ options[:actions]
160
+
161
+ paginator, collection =
162
+ paginator_and_collection_for(collection_id, options)
163
+
164
+ paginator_name = "@#{options[:singular_name]}_pages"
165
+ self.instance_variable_set(paginator_name, paginator)
166
+
167
+ collection_name = "@#{collection_id.to_s}"
168
+ self.instance_variable_set(collection_name, collection)
169
+ end
170
+ end
171
+
172
+ # Returns the total number of items in the collection to be paginated for
173
+ # the +model+ and given +conditions+. Override this method to implement a
174
+ # custom counter.
175
+ def count_collection_for_pagination(model, options)
176
+ model.count(:conditions => options[:conditions],
177
+ :joins => options[:join] || options[:joins],
178
+ :include => options[:include],
179
+ :select => (options[:group] ? "DISTINCT #{options[:group]}" : options[:count]))
180
+ end
181
+
182
+ # Returns a collection of items for the given +model+ and +options[conditions]+,
183
+ # ordered by +options[order]+, for the current page in the given +paginator+.
184
+ # Override this method to implement a custom finder.
185
+ def find_collection_for_pagination(model, options, paginator)
186
+ model.find(:all, :conditions => options[:conditions],
187
+ :order => options[:order_by] || options[:order],
188
+ :joins => options[:join] || options[:joins], :include => options[:include],
189
+ :select => options[:select], :limit => options[:per_page],
190
+ :group => options[:group], :offset => paginator.current.offset)
191
+ end
192
+
193
+ protected :create_paginators_and_retrieve_collections,
194
+ :count_collection_for_pagination,
195
+ :find_collection_for_pagination
196
+
197
+ def paginator_and_collection_for(collection_id, options) #:nodoc:
198
+ klass = options[:class_name].constantize
199
+ page = params[options[:parameter]]
200
+ count = count_collection_for_pagination(klass, options)
201
+ paginator = Paginator.new(self, count, options[:per_page], page)
202
+ collection = find_collection_for_pagination(klass, options, paginator)
203
+
204
+ return paginator, collection
205
+ end
206
+
207
+ private :paginator_and_collection_for
208
+
209
+ # A class representing a paginator for an Active Record collection.
210
+ class Paginator
211
+ include Enumerable
212
+
213
+ # Creates a new Paginator on the given +controller+ for a set of items
214
+ # of size +item_count+ and having +items_per_page+ items per page.
215
+ # Raises ArgumentError if items_per_page is out of bounds (i.e., less
216
+ # than or equal to zero). The page CGI parameter for links defaults to
217
+ # "page" and can be overridden with +page_parameter+.
218
+ def initialize(controller, item_count, items_per_page, current_page=1)
219
+ raise ArgumentError, 'must have at least one item per page' if
220
+ items_per_page <= 0
221
+
222
+ @controller = controller
223
+ @item_count = item_count || 0
224
+ @items_per_page = items_per_page
225
+ @pages = {}
226
+
227
+ self.current_page = current_page
228
+ end
229
+ attr_reader :controller, :item_count, :items_per_page
230
+
231
+ # Sets the current page number of this paginator. If +page+ is a Page
232
+ # object, its +number+ attribute is used as the value; if the page does
233
+ # not belong to this Paginator, an ArgumentError is raised.
234
+ def current_page=(page)
235
+ if page.is_a? Page
236
+ raise ArgumentError, 'Page/Paginator mismatch' unless
237
+ page.paginator == self
238
+ end
239
+ page = page.to_i
240
+ @current_page_number = has_page_number?(page) ? page : 1
241
+ end
242
+
243
+ # Returns a Page object representing this paginator's current page.
244
+ def current_page
245
+ @current_page ||= self[@current_page_number]
246
+ end
247
+ alias current :current_page
248
+
249
+ # Returns a new Page representing the first page in this paginator.
250
+ def first_page
251
+ @first_page ||= self[1]
252
+ end
253
+ alias first :first_page
254
+
255
+ # Returns a new Page representing the last page in this paginator.
256
+ def last_page
257
+ @last_page ||= self[page_count]
258
+ end
259
+ alias last :last_page
260
+
261
+ # Returns the number of pages in this paginator.
262
+ def page_count
263
+ @page_count ||= @item_count.zero? ? 1 :
264
+ (q,r=@item_count.divmod(@items_per_page); r==0? q : q+1)
265
+ end
266
+
267
+ alias length :page_count
268
+
269
+ # Returns true if this paginator contains the page of index +number+.
270
+ def has_page_number?(number)
271
+ number >= 1 and number <= page_count
272
+ end
273
+
274
+ # Returns a new Page representing the page with the given index
275
+ # +number+.
276
+ def [](number)
277
+ @pages[number] ||= Page.new(self, number)
278
+ end
279
+
280
+ # Successively yields all the paginator's pages to the given block.
281
+ def each(&block)
282
+ page_count.times do |n|
283
+ yield self[n+1]
284
+ end
285
+ end
286
+
287
+ # A class representing a single page in a paginator.
288
+ class Page
289
+ include Comparable
290
+
291
+ # Creates a new Page for the given +paginator+ with the index
292
+ # +number+. If +number+ is not in the range of valid page numbers or
293
+ # is not a number at all, it defaults to 1.
294
+ def initialize(paginator, number)
295
+ @paginator = paginator
296
+ @number = number.to_i
297
+ @number = 1 unless @paginator.has_page_number? @number
298
+ end
299
+ attr_reader :paginator, :number
300
+ alias to_i :number
301
+
302
+ # Compares two Page objects and returns true when they represent the
303
+ # same page (i.e., their paginators are the same and they have the
304
+ # same page number).
305
+ def ==(page)
306
+ return false if page.nil?
307
+ @paginator == page.paginator and
308
+ @number == page.number
309
+ end
310
+
311
+ # Compares two Page objects and returns -1 if the left-hand page comes
312
+ # before the right-hand page, 0 if the pages are equal, and 1 if the
313
+ # left-hand page comes after the right-hand page. Raises ArgumentError
314
+ # if the pages do not belong to the same Paginator object.
315
+ def <=>(page)
316
+ raise ArgumentError unless @paginator == page.paginator
317
+ @number <=> page.number
318
+ end
319
+
320
+ # Returns the item offset for the first item in this page.
321
+ def offset
322
+ @paginator.items_per_page * (@number - 1)
323
+ end
324
+
325
+ # Returns the number of the first item displayed.
326
+ def first_item
327
+ offset + 1
328
+ end
329
+
330
+ # Returns the number of the last item displayed.
331
+ def last_item
332
+ [@paginator.items_per_page * @number, @paginator.item_count].min
333
+ end
334
+
335
+ # Returns true if this page is the first page in the paginator.
336
+ def first?
337
+ self == @paginator.first
338
+ end
339
+
340
+ # Returns true if this page is the last page in the paginator.
341
+ def last?
342
+ self == @paginator.last
343
+ end
344
+
345
+ # Returns a new Page object representing the page just before this
346
+ # page, or nil if this is the first page.
347
+ def previous
348
+ if first? then nil else @paginator[@number - 1] end
349
+ end
350
+
351
+ # Returns a new Page object representing the page just after this
352
+ # page, or nil if this is the last page.
353
+ def next
354
+ if last? then nil else @paginator[@number + 1] end
355
+ end
356
+
357
+ # Returns a new Window object for this page with the specified
358
+ # +padding+.
359
+ def window(padding=2)
360
+ Window.new(self, padding)
361
+ end
362
+
363
+ # Returns the limit/offset array for this page.
364
+ def to_sql
365
+ [@paginator.items_per_page, offset]
366
+ end
367
+
368
+ def to_param #:nodoc:
369
+ @number.to_s
370
+ end
371
+ end
372
+
373
+ # A class for representing ranges around a given page.
374
+ class Window
375
+ # Creates a new Window object for the given +page+ with the specified
376
+ # +padding+.
377
+ def initialize(page, padding=2)
378
+ @paginator = page.paginator
379
+ @page = page
380
+ self.padding = padding
381
+ end
382
+ attr_reader :paginator, :page
383
+
384
+ # Sets the window's padding (the number of pages on either side of the
385
+ # window page).
386
+ def padding=(padding)
387
+ @padding = padding < 0 ? 0 : padding
388
+ # Find the beginning and end pages of the window
389
+ @first = @paginator.has_page_number?(@page.number - @padding) ?
390
+ @paginator[@page.number - @padding] : @paginator.first
391
+ @last = @paginator.has_page_number?(@page.number + @padding) ?
392
+ @paginator[@page.number + @padding] : @paginator.last
393
+ end
394
+ attr_reader :padding, :first, :last
395
+
396
+ # Returns an array of Page objects in the current window.
397
+ def pages
398
+ (@first.number..@last.number).to_a.collect! {|n| @paginator[n]}
399
+ end
400
+ alias to_a :pages
401
+ end
402
+ end
403
+
404
+ end
405
+ end