sequel_core 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. data/CHANGELOG +116 -0
  2. data/COPYING +19 -19
  3. data/README +83 -32
  4. data/Rakefile +9 -20
  5. data/bin/sequel +43 -112
  6. data/doc/cheat_sheet.rdoc +225 -0
  7. data/doc/dataset_filtering.rdoc +257 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
  9. data/lib/sequel_core/adapters/ado.rb +3 -1
  10. data/lib/sequel_core/adapters/db2.rb +4 -2
  11. data/lib/sequel_core/adapters/dbi.rb +127 -113
  12. data/lib/sequel_core/adapters/informix.rb +4 -2
  13. data/lib/sequel_core/adapters/jdbc.rb +5 -3
  14. data/lib/sequel_core/adapters/mysql.rb +112 -46
  15. data/lib/sequel_core/adapters/odbc.rb +5 -7
  16. data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
  17. data/lib/sequel_core/adapters/openbase.rb +3 -1
  18. data/lib/sequel_core/adapters/oracle.rb +11 -9
  19. data/lib/sequel_core/adapters/postgres.rb +261 -262
  20. data/lib/sequel_core/adapters/sqlite.rb +72 -22
  21. data/lib/sequel_core/connection_pool.rb +140 -73
  22. data/lib/sequel_core/core_ext.rb +201 -66
  23. data/lib/sequel_core/core_sql.rb +123 -153
  24. data/lib/sequel_core/database/schema.rb +156 -0
  25. data/lib/sequel_core/database.rb +321 -338
  26. data/lib/sequel_core/dataset/callback.rb +11 -12
  27. data/lib/sequel_core/dataset/convenience.rb +213 -240
  28. data/lib/sequel_core/dataset/pagination.rb +58 -43
  29. data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
  30. data/lib/sequel_core/dataset/query.rb +41 -0
  31. data/lib/sequel_core/dataset/schema.rb +15 -0
  32. data/lib/sequel_core/dataset/sequelizer.rb +41 -373
  33. data/lib/sequel_core/dataset/sql.rb +741 -632
  34. data/lib/sequel_core/dataset.rb +183 -168
  35. data/lib/sequel_core/deprecated.rb +1 -169
  36. data/lib/sequel_core/exceptions.rb +24 -19
  37. data/lib/sequel_core/migration.rb +44 -52
  38. data/lib/sequel_core/object_graph.rb +43 -42
  39. data/lib/sequel_core/pretty_table.rb +71 -76
  40. data/lib/sequel_core/schema/generator.rb +163 -105
  41. data/lib/sequel_core/schema/sql.rb +250 -93
  42. data/lib/sequel_core/schema.rb +2 -8
  43. data/lib/sequel_core/sql.rb +394 -0
  44. data/lib/sequel_core/worker.rb +37 -27
  45. data/lib/sequel_core.rb +99 -45
  46. data/spec/adapters/informix_spec.rb +0 -1
  47. data/spec/adapters/mysql_spec.rb +177 -124
  48. data/spec/adapters/oracle_spec.rb +0 -1
  49. data/spec/adapters/postgres_spec.rb +98 -58
  50. data/spec/adapters/sqlite_spec.rb +45 -4
  51. data/spec/blockless_filters_spec.rb +269 -0
  52. data/spec/connection_pool_spec.rb +21 -18
  53. data/spec/core_ext_spec.rb +169 -19
  54. data/spec/core_sql_spec.rb +56 -49
  55. data/spec/database_spec.rb +78 -17
  56. data/spec/dataset_spec.rb +300 -428
  57. data/spec/migration_spec.rb +1 -1
  58. data/spec/object_graph_spec.rb +5 -11
  59. data/spec/rcov.opts +1 -1
  60. data/spec/schema_generator_spec.rb +16 -4
  61. data/spec/schema_spec.rb +89 -10
  62. data/spec/sequelizer_spec.rb +56 -56
  63. data/spec/spec.opts +0 -5
  64. data/spec/spec_config.rb +7 -0
  65. data/spec/spec_config.rb.example +5 -5
  66. data/spec/spec_helper.rb +6 -0
  67. data/spec/worker_spec.rb +1 -1
  68. metadata +78 -63
@@ -1,17 +1,16 @@
1
+ # This file holds empty methods that can be
2
+ # overridden to provide callback behavior.
3
+
1
4
  module Sequel
2
5
  class Dataset
3
- # Module with empty methods that can be
4
- # override to provide callback behavior
5
- module Callback
6
- private
7
- # This is run inside .all, after all
8
- # of the records have been loaded
9
- # via .each, but before any block passed
10
- # to all is called. It is called with
11
- # a single argument, an array of all
12
- # returned records.
13
- def post_load(all_records)
14
- end
6
+ private
7
+ # This is run inside .all, after all
8
+ # of the records have been loaded
9
+ # via .each, but before any block passed
10
+ # to all is called. It is called with
11
+ # a single argument, an array of all
12
+ # returned records.
13
+ def post_load(all_records)
15
14
  end
16
15
  end
17
16
  end
@@ -1,270 +1,243 @@
1
- require 'enumerator'
2
-
3
1
  module Sequel
4
2
  class Dataset
5
- module Convenience
6
- # Returns true if no records exists in the dataset
7
- def empty?
8
- db.dataset.where(exists).get(1) == nil
9
- end
10
-
11
- # Returns the first record in the dataset.
12
- def single_record(opts = nil)
13
- each(opts) {|r| return r}
14
- nil
15
- end
16
-
17
- NAKED_HASH = {:naked => true}.freeze
18
-
19
- # Returns the first value of the first reecord in the dataset.
20
- # Returns nil if dataset is empty.
21
- def single_value(opts = nil)
22
- opts = opts ? NAKED_HASH.merge(opts) : NAKED_HASH
23
- # don't cache the columns
24
- each(opts) {|r| @columns = nil; return r.values.first}
25
- nil
26
- end
27
-
28
- def get(column)
29
- select(column).single_value
30
- end
3
+ COMMA_SEPARATOR = ', '.freeze
4
+ COUNT_OF_ALL_AS_COUNT = :count['*'.lit].as(:count)
31
5
 
32
- # Returns the first record in the dataset. If the num argument is specified,
33
- # an array is returned with the first <i>num</i> records.
34
- def first(*args, &block)
35
- if block
36
- return filter(&block).single_record(:limit => 1)
37
- end
38
- args = args.empty? ? 1 : (args.size == 1) ? args.first : args
39
- case args
40
- when 1
41
- single_record(:limit => 1)
42
- when Fixnum
43
- limit(args).all
44
- else
45
- filter(args, &block).single_record(:limit => 1)
46
- end
47
- end
48
-
49
- # Returns the first record matching the condition.
50
- def [](*conditions)
51
- first(*conditions)
52
- end
53
-
54
- def []=(conditions, values)
55
- filter(conditions).update(values)
56
- end
6
+ # Returns the first record matching the conditions.
7
+ def [](*conditions)
8
+ first(*conditions)
9
+ end
57
10
 
58
- # Returns the last records in the dataset by inverting the order. If no
59
- # order is given, an exception is raised. If num is not given, the last
60
- # record is returned. Otherwise an array is returned with the last
61
- # <i>num</i> records.
62
- def last(*args)
63
- raise Error, 'No order specified' unless
64
- @opts[:order] || (opts && opts[:order])
11
+ # Update all records matching the conditions
12
+ # with the values specified.
13
+ def []=(conditions, values)
14
+ filter(conditions).update(values)
15
+ end
65
16
 
66
- args = args.empty? ? 1 : (args.size == 1) ? args.first : args
17
+ # Returns the average value for the given column.
18
+ def avg(column)
19
+ get(:avg[column])
20
+ end
21
+
22
+ # Returns true if no records exists in the dataset
23
+ def empty?
24
+ db.dataset.where(exists).get(1) == nil
25
+ end
26
+
27
+ # Returns the first record in the dataset. If a numeric argument is
28
+ # given, it is interpreted as a limit, and then returns all
29
+ # matching records up to that limit. If no argument is passed,
30
+ # it returns the first matching record. If any other type of
31
+ # argument(s) is passed, it is given to filter and the
32
+ # first matching record is returned. If a block is given, it is used
33
+ # to filter the dataset before returning anything.
34
+ #
35
+ # Examples:
36
+ #
37
+ # ds.first => {:id=>7}
38
+ # ds.first(2) => [{:id=>6}, {:id=>4}]
39
+ # ds.order(:id).first(2) => [{:id=>1}, {:id=>2}]
40
+ # ds.first(:id=>2) => {:id=>2}
41
+ # ds.first("id = 3") => {:id=>3}
42
+ # ds.first("id = ?", 4) => {:id=>4}
43
+ # ds.first{:id > 2} => {:id=>5}
44
+ # ds.order(:id).first{:id > 2} => {:id=>3}
45
+ # ds.first{:id > 2} => {:id=>5}
46
+ # ds.first("id > ?", 4){:id < 6) => {:id=>5}
47
+ # ds.order(:id).first(2){:id < 2} => [{:id=>1}]
48
+ def first(*args, &block)
49
+ ds = block ? filter(&block) : self
67
50
 
68
- case args
69
- when Fixnum
70
- l = {:limit => args}
71
- opts = {:order => invert_order(@opts[:order])}. \
72
- merge(opts ? opts.merge(l) : l)
73
- if args == 1
74
- single_record(opts)
75
- else
76
- clone(opts).all
77
- end
51
+ if args.empty?
52
+ ds.single_record
53
+ else
54
+ args = (args.size == 1) ? args.first : args
55
+ if Integer === args
56
+ ds.limit(args).all
78
57
  else
79
- filter(args).last(1)
80
- end
81
- end
82
-
83
- # Maps column values for each record in the dataset (if a column name is
84
- # given), or performs the stock mapping functionality of Enumerable.
85
- def map(column_name = nil, &block)
86
- if column_name
87
- super() {|r| r[column_name]}
88
- else
89
- super(&block)
58
+ ds.filter(args).single_record
90
59
  end
91
60
  end
61
+ end
92
62
 
93
- # Returns a hash with one column used as key and another used as value.
94
- def to_hash(key_column, value_column)
95
- inject({}) do |m, r|
96
- m[r[key_column]] = r[value_column]
97
- m
98
- end
99
- end
63
+ # Return the column value for the first matching record in the dataset.
64
+ def get(column)
65
+ select(column).single_value
66
+ end
100
67
 
101
- # Returns the minimum value for the given column.
102
- def min(column)
103
- single_value(:select => [:min[column].as(:v)])
104
- end
68
+ # Returns a dataset grouped by the given column with count by group.
69
+ def group_and_count(*columns)
70
+ group(*columns).select(*(columns + [COUNT_OF_ALL_AS_COUNT])).order(:count)
71
+ end
72
+
73
+ # Returns the interval between minimum and maximum values for the given
74
+ # column.
75
+ def interval(column)
76
+ get("(max(#{literal(column)}) - min(#{literal(column)}))".lit)
77
+ end
105
78
 
106
- # Returns the maximum value for the given column.
107
- def max(column)
108
- single_value(:select => [:max[column].as(:v)])
79
+ # Reverses the order and then runs first. Note that this
80
+ # will not necessarily give you the last record in the dataset,
81
+ # unless you have an unambiguous order. If there is not
82
+ # currently an order for this dataset, raises an Error.
83
+ def last(*args, &block)
84
+ raise(Error, 'No order specified') unless @opts[:order]
85
+ reverse.first(*args, &block)
86
+ end
87
+
88
+ # Maps column values for each record in the dataset (if a column name is
89
+ # given), or performs the stock mapping functionality of Enumerable.
90
+ def map(column_name = nil, &block)
91
+ if column_name
92
+ super() {|r| r[column_name]}
93
+ else
94
+ super(&block)
109
95
  end
96
+ end
110
97
 
111
- # Returns the sum for the given column.
112
- def sum(column)
113
- single_value(:select => [:sum[column].as(:v)])
114
- end
98
+ # Returns the maximum value for the given column.
99
+ def max(column)
100
+ get(:max[column])
101
+ end
115
102
 
116
- # Returns the average value for the given column.
117
- def avg(column)
118
- single_value(:select => [:avg[column].as(:v)])
119
- end
120
-
121
- COUNT_OF_ALL_AS_COUNT = :count['*'.lit].as(:count)
122
-
123
- # Returns a dataset grouped by the given column with count by group.
124
- def group_and_count(*columns)
125
- group(*columns).select(columns + [COUNT_OF_ALL_AS_COUNT]).order(:count)
126
- end
127
-
128
- # Returns a Range object made from the minimum and maximum values for the
129
- # given column.
130
- def range(column)
131
- if r = select(:min[column].as(:v1), :max[column].as(:v2)).first
132
- (r[:v1]..r[:v2])
133
- end
134
- end
135
-
136
- # Returns the interval between minimum and maximum values for the given
137
- # column.
138
- def interval(column)
139
- if r = select("(max(#{literal(column)}) - min(#{literal(column)})) AS v".lit).first
140
- r[:v]
141
- end
142
- end
103
+ # Returns the minimum value for the given column.
104
+ def min(column)
105
+ get(:min[column])
106
+ end
143
107
 
144
- # Pretty prints the records in the dataset as plain-text table.
145
- def print(*cols)
146
- Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
147
- end
148
-
149
- COMMA_SEPARATOR = ', '.freeze
150
-
151
- # Returns a string in CSV format containing the dataset records. By
152
- # default the CSV representation includes the column titles in the
153
- # first line. You can turn that off by passing false as the
154
- # include_column_titles argument.
155
- def to_csv(include_column_titles = true)
156
- n = naked
157
- cols = n.columns
158
- csv = ''
159
- csv << "#{cols.join(COMMA_SEPARATOR)}\r\n" if include_column_titles
160
- n.each{|r| csv << "#{cols.collect{|c| r[c]}.join(COMMA_SEPARATOR)}\r\n"}
161
- csv
162
- end
163
-
164
- # Inserts multiple records into the associated table. This method can be
165
- # to efficiently insert a large amounts of records into a table. Inserts
166
- # are automatically wrapped in a transaction.
167
- #
168
- # This method can be called either with an array of hashes:
169
- #
170
- # dataset.multi_insert({:x => 1}, {:x => 2})
171
- #
172
- # Or with a columns array and an array of value arrays:
173
- #
174
- # dataset.multi_insert([:x, :y], [[1, 2], [3, 4]])
175
- #
176
- # The method also accepts a :slice or :commit_every option that specifies
177
- # the number of records to insert per transaction. This is useful especially
178
- # when inserting a large number of records, e.g.:
179
- #
180
- # # this will commit every 50 records
181
- # dataset.multi_insert(lots_of_records, :slice => 50)
182
- def multi_insert(*args)
183
- if args.empty?
184
- return
185
- elsif args[0].is_a?(Array) && args[1].is_a?(Array)
186
- columns, values, opts = *args
187
- elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
188
- table = @opts[:from].first
189
- columns, dataset = *args
190
- sql = "INSERT INTO #{table} (#{literal(columns)}) VALUES (#{dataset.sql})"
191
- return @db.transaction {@db.execute sql}
192
- else
193
- # we assume that an array of hashes is given
194
- hashes, opts = *args
195
- return if hashes.empty?
196
- columns = hashes.first.keys
197
- # convert the hashes into arrays
198
- values = hashes.map {|h| columns.map {|c| h[c]}}
199
- end
200
- # make sure there's work to do
201
- return if columns.empty? || values.empty?
202
-
203
- slice_size = opts && (opts[:commit_every] || opts[:slice])
204
-
205
- if slice_size
206
- values.each_slice(slice_size) do |slice|
207
- statements = multi_insert_sql(columns, slice)
208
- @db.transaction {statements.each {|st| @db.execute(st)}}
209
- end
210
- else
211
- statements = multi_insert_sql(columns, values)
108
+ # Inserts multiple records into the associated table. This method can be
109
+ # to efficiently insert a large amounts of records into a table. Inserts
110
+ # are automatically wrapped in a transaction.
111
+ #
112
+ # This method should be called with a columns array and an array of value arrays:
113
+ #
114
+ # dataset.multi_insert([:x, :y], [[1, 2], [3, 4]])
115
+ #
116
+ # This method can also be called with an array of hashes:
117
+ #
118
+ # dataset.multi_insert({:x => 1}, {:x => 2})
119
+ #
120
+ # Be aware that all hashes should have the same keys if you use this calling method,
121
+ # otherwise some columns could be missed or set to null instead of to default
122
+ # values.
123
+ #
124
+ # The method also accepts a :slice or :commit_every option that specifies
125
+ # the number of records to insert per transaction. This is useful especially
126
+ # when inserting a large number of records, e.g.:
127
+ #
128
+ # # this will commit every 50 records
129
+ # dataset.multi_insert(lots_of_records, :slice => 50)
130
+ def multi_insert(*args)
131
+ if args.empty?
132
+ return
133
+ elsif args[0].is_a?(Array) && args[1].is_a?(Array)
134
+ columns, values, opts = *args
135
+ elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
136
+ table = @opts[:from].first
137
+ columns, dataset = *args
138
+ sql = "INSERT INTO #{quote_identifier(table)} #{literal(columns)} VALUES #{literal(dataset)}"
139
+ return @db.transaction {@db.execute sql}
140
+ else
141
+ # we assume that an array of hashes is given
142
+ hashes, opts = *args
143
+ return if hashes.empty?
144
+ columns = hashes.first.keys
145
+ # convert the hashes into arrays
146
+ values = hashes.map {|h| columns.map {|c| h[c]}}
147
+ end
148
+ # make sure there's work to do
149
+ return if columns.empty? || values.empty?
150
+
151
+ slice_size = opts && (opts[:commit_every] || opts[:slice])
152
+
153
+ if slice_size
154
+ values.each_slice(slice_size) do |slice|
155
+ statements = multi_insert_sql(columns, slice)
212
156
  @db.transaction {statements.each {|st| @db.execute(st)}}
213
157
  end
158
+ else
159
+ statements = multi_insert_sql(columns, values)
160
+ @db.transaction {statements.each {|st| @db.execute(st)}}
214
161
  end
215
- alias_method :import, :multi_insert
216
-
217
- module QueryBlockCopy #:nodoc:
218
- def each(*args); raise Error, "#each cannot be invoked inside a query block."; end
219
- def insert(*args); raise Error, "#insert cannot be invoked inside a query block."; end
220
- def update(*args); raise Error, "#update cannot be invoked inside a query block."; end
221
- def delete(*args); raise Error, "#delete cannot be invoked inside a query block."; end
162
+ end
163
+ alias_method :import, :multi_insert
164
+
165
+ # Pretty prints the records in the dataset as plain-text table.
166
+ def print(*cols)
167
+ Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
168
+ end
169
+
170
+ # Returns a Range object made from the minimum and maximum values for the
171
+ # given column.
172
+ def range(column)
173
+ if r = select(:min[column].as(:v1), :max[column].as(:v2)).first
174
+ (r[:v1]..r[:v2])
175
+ end
176
+ end
177
+
178
+ # Returns the first record in the dataset.
179
+ def single_record(opts = nil)
180
+ each((opts||{}).merge(:limit=>1)){|r| return r}
181
+ nil
182
+ end
222
183
 
223
- def clone(opts = nil)
224
- @opts.merge!(opts)
225
- self
226
- end
184
+ # Returns the first value of the first record in the dataset.
185
+ # Returns nil if dataset is empty.
186
+ def single_value(opts = nil)
187
+ if r = naked.single_record(opts)
188
+ r.values.first
227
189
  end
228
-
229
- # Translates a query block into a dataset. Query blocks can be useful
230
- # when expressing complex SELECT statements, e.g.:
231
- #
232
- # dataset = DB[:items].query do
233
- # select :x, :y, :z
234
- # where {:x > 1 && :y > 2}
235
- # order_by :z.DESC
236
- # end
237
- #
238
- def query(&block)
239
- copy = clone({})
240
- copy.extend(QueryBlockCopy)
241
- copy.instance_eval(&block)
242
- clone(copy.opts)
190
+ end
191
+
192
+ # Returns the sum for the given column.
193
+ def sum(column)
194
+ get(:sum[column])
195
+ end
196
+
197
+ # Returns true if the table exists. Will raise an error
198
+ # if the dataset has fixed SQL or selects from another dataset
199
+ # or more than one table.
200
+ def table_exists?
201
+ if @opts[:sql]
202
+ raise Sequel::Error, "this dataset has fixed SQL"
243
203
  end
244
204
 
245
- def create_view(name)
246
- @db.create_view(name, self)
205
+ if @opts[:from].size != 1
206
+ raise Sequel::Error, "this dataset selects from multiple sources"
247
207
  end
248
208
 
249
- def create_or_replace_view(name)
250
- @db.create_or_replace_view(name, self)
209
+ t = @opts[:from].first
210
+ if t.is_a?(Dataset)
211
+ raise Sequel::Error, "this dataset selects from a sub query"
251
212
  end
252
213
 
253
- def table_exists?
254
- if @opts[:sql]
255
- raise Sequel::Error, "this dataset has fixed SQL"
256
- end
257
-
258
- if @opts[:from].size != 1
259
- raise Sequel::Error, "this dataset selects from multiple sources"
260
- end
261
-
262
- t = @opts[:from].first
263
- if t.is_a?(Dataset)
264
- raise Sequel::Error, "this dataset selects from a sub query"
265
- end
266
-
267
- @db.table_exists?(t.to_sym)
214
+ @db.table_exists?(t.to_sym)
215
+ end
216
+
217
+ # Returns a string in CSV format containing the dataset records. By
218
+ # default the CSV representation includes the column titles in the
219
+ # first line. You can turn that off by passing false as the
220
+ # include_column_titles argument.
221
+ #
222
+ # This does not use a CSV library or handle quoting of values in
223
+ # any way. If any values in any of the rows could include commas or line
224
+ # endings, you probably shouldn't use this.
225
+ def to_csv(include_column_titles = true)
226
+ n = naked
227
+ cols = n.columns
228
+ csv = ''
229
+ csv << "#{cols.join(COMMA_SEPARATOR)}\r\n" if include_column_titles
230
+ n.each{|r| csv << "#{cols.collect{|c| r[c]}.join(COMMA_SEPARATOR)}\r\n"}
231
+ csv
232
+ end
233
+
234
+ # Returns a hash with one column used as key and another used as value.
235
+ # If rows have duplicate values for the key column, the latter row(s)
236
+ # will overwrite the value of the previous row(s).
237
+ def to_hash(key_column, value_column)
238
+ inject({}) do |m, r|
239
+ m[r[key_column]] = r[value_column]
240
+ m
268
241
  end
269
242
  end
270
243
  end
@@ -1,62 +1,43 @@
1
- require 'enumerator'
2
-
3
1
  module Sequel
4
2
  class Dataset
5
- # Returns a paginated dataset. The resulting dataset also provides the
6
- # total number of pages (Dataset#page_count) and the current page number
7
- # (Dataset#current_page), as well as Dataset#prev_page and Dataset#next_page
8
- # for implementing pagination controls.
9
- def paginate(page_no, page_size)
3
+ # Returns a paginated dataset. The returned dataset is limited to
4
+ # the page size at the correct offset, and extended with the Pagination
5
+ # module. If a record count is not provided, does a count of total
6
+ # number of records for this dataset.
7
+ def paginate(page_no, page_size, record_count=nil)
10
8
  raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
11
- record_count = count
12
- total_pages = (record_count / page_size.to_f).ceil
13
9
  paginated = limit(page_size, (page_no - 1) * page_size)
14
10
  paginated.extend(Pagination)
15
- paginated.set_pagination_info(page_no, page_size, record_count)
16
- paginated
11
+ paginated.set_pagination_info(page_no, page_size, record_count || count)
17
12
  end
18
13
 
19
- def each_page(page_size)
14
+ # Yields a paginated dataset for each page and returns the receiver. Does
15
+ # a count to find the total number of records for this dataset.
16
+ def each_page(page_size, &block)
20
17
  raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
21
18
  record_count = count
22
19
  total_pages = (record_count / page_size.to_f).ceil
23
-
24
- (1..total_pages).each do |page_no|
25
- paginated = limit(page_size, (page_no - 1) * page_size)
26
- paginated.extend(Pagination)
27
- paginated.set_pagination_info(page_no, page_size, record_count)
28
- yield paginated
29
- end
30
-
20
+ (1..total_pages).each{|page_no| yield paginate(page_no, page_size, record_count)}
31
21
  self
32
22
  end
33
23
 
24
+ # Holds methods that only relate to paginated datasets. Paginated dataset
25
+ # have pages starting at 1 (page 1 is offset 0, page 1 is offset page_size).
34
26
  module Pagination
35
- attr_accessor :page_size, :page_count, :current_page, :pagination_record_count
27
+ # The number of records per page (the final page may have fewer than
28
+ # this number of records).
29
+ attr_accessor :page_size
36
30
 
37
- # Sets the pagination info
38
- def set_pagination_info(page_no, page_size, record_count)
39
- @current_page = page_no
40
- @page_size = page_size
41
- @pagination_record_count = record_count
42
- @page_count = (record_count / page_size.to_f).ceil
43
- end
44
-
45
- # Returns the previous page number or nil if the current page is the first
46
- def prev_page
47
- current_page > 1 ? (current_page - 1) : nil
48
- end
31
+ # The number of pages in the dataset before pagination, of which
32
+ # this paginated dataset is one.
33
+ attr_accessor :page_count
34
+
35
+ # The current page of the dataset, starting at 1 and not 0.
36
+ attr_accessor :current_page
37
+
38
+ # The total number of records in the dataset before pagination.
39
+ attr_accessor :pagination_record_count
49
40
 
50
- # Returns the next page number or nil if the current page is the last page
51
- def next_page
52
- current_page < page_count ? (current_page + 1) : nil
53
- end
54
-
55
- # Returns the page range
56
- def page_range
57
- 1..page_count
58
- end
59
-
60
41
  # Returns the record range for the current page
61
42
  def current_page_record_range
62
43
  return (0..0) if @current_page > @page_count
@@ -76,6 +57,40 @@ module Sequel
76
57
  b = @pagination_record_count if b > @pagination_record_count
77
58
  b - a + 1
78
59
  end
60
+
61
+ # Returns true if the current page is the first page
62
+ def first_page?
63
+ @current_page == 1
64
+ end
65
+
66
+ # Returns true if the current page is the last page
67
+ def last_page?
68
+ @current_page == @page_count
69
+ end
70
+
71
+ # Returns the next page number or nil if the current page is the last page
72
+ def next_page
73
+ current_page < page_count ? (current_page + 1) : nil
74
+ end
75
+
76
+ # Returns the page range
77
+ def page_range
78
+ 1..page_count
79
+ end
80
+
81
+ # Returns the previous page number or nil if the current page is the first
82
+ def prev_page
83
+ current_page > 1 ? (current_page - 1) : nil
84
+ end
85
+
86
+ # Sets the pagination info for this paginated dataset, and returns self.
87
+ def set_pagination_info(page_no, page_size, record_count)
88
+ @current_page = page_no
89
+ @page_size = page_size
90
+ @pagination_record_count = record_count
91
+ @page_count = (record_count / page_size.to_f).ceil
92
+ self
93
+ end
79
94
  end
80
95
  end
81
96
  end