sequel 0.0.16 → 0.0.17
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +16 -0
- data/README +41 -12
- data/Rakefile +1 -1
- data/lib/sequel/dataset.rb +113 -32
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,5 +1,21 @@
|
|
1
|
+
*0.0.17*
|
2
|
+
|
3
|
+
* Fixed Dataset#from to have variable arity, like Dataset#select and Dataset#where (patch by Alex Bradbury.)
|
4
|
+
|
5
|
+
* Added support for GROUP BY and HAVING clauses (patches by Alex Bradbury.) Refactored Dataset#filter.
|
6
|
+
|
7
|
+
* More specs.
|
8
|
+
|
9
|
+
* Refactored Dataset#where for better composability.
|
10
|
+
|
11
|
+
* Added Dataset#[]= method.
|
12
|
+
|
13
|
+
* Added support for DISTINCT and OFFSET clauses (patches by Alex Bradbury.) Dataset#limit now accepts ranges. Added Dataset#uniq and distinct methods.
|
14
|
+
|
1
15
|
*0.0.16*
|
2
16
|
|
17
|
+
* More documentation.
|
18
|
+
|
3
19
|
* Added support for subqueries in Dataset#literal.
|
4
20
|
|
5
21
|
* Added support for Model.all_by_XXX methods through Model.method_missing.
|
data/README
CHANGED
@@ -39,7 +39,36 @@ Sequel includes an IRB console for quick'n'dirty access to databases. You can us
|
|
39
39
|
|
40
40
|
You get an IRB session with the database object stored in DB.
|
41
41
|
|
42
|
-
==
|
42
|
+
== An Introduction
|
43
|
+
|
44
|
+
Sequel was designed to take the hassle away from connecting to databases and manipulating them. Sequel deals with all the boring stuff like maintaining connections, formatting SQL correctly and fetching records so you can concentrate on your application.
|
45
|
+
|
46
|
+
Sequel uses the concept of datasets to retrieve data. A Dataset object encapsulates an SQL query and supports chainability, letting you fetch data using a convenient Ruby DSL that is both concise and infinitely flexible.
|
47
|
+
|
48
|
+
For example, the following one-liner returns the average GDP for the five biggest countries in the middle east region:
|
49
|
+
|
50
|
+
DB[:countries].filter(:region => 'Middle East').reverse_order(:area).limit(5).avg(:GDP)
|
51
|
+
|
52
|
+
Which is equivalent to:
|
53
|
+
|
54
|
+
SELECT avg(GDP) FROM countries WHERE region = 'Middle East' ORDER BY area DESC LIMIT 5
|
55
|
+
|
56
|
+
Since datasets retrieve records only when needed, they can be stored and later reused. Records are fetched as hashes (they can also be fetched as custom model objects), and are accessed using an Enumerable interface:
|
57
|
+
|
58
|
+
middle_east = DB[:countries].filter(:region => 'Middle East')
|
59
|
+
middle_east.order(:name).each {|r| puts r[:name]}
|
60
|
+
|
61
|
+
Sequel also offers convenience methods for extracting data from Datasets, such as an extended map method:
|
62
|
+
|
63
|
+
middle_east.map(:name) #=> ['Egypt', 'Greece', 'Israel', ...]
|
64
|
+
|
65
|
+
Or getting results as a transposed hash, with one column as key and another as value:
|
66
|
+
|
67
|
+
middle_east.hash_column(:name, :area) #=> {'Israel' => 20000, 'Greece' => 120000, ...}
|
68
|
+
|
69
|
+
Much of Sequel is still undocumented (especially the part relating to model classes). The following section provides examples of common usage. Feel free to explore...
|
70
|
+
|
71
|
+
== Getting Started
|
43
72
|
|
44
73
|
=== Connecting to a database
|
45
74
|
|
@@ -49,18 +78,18 @@ Before connecting to a database, you should require the corresponding adaptor, f
|
|
49
78
|
|
50
79
|
Note: you don't need to require 'sequel' separately before that, as each adapter requires 'sequel' if it hasn't yet been required.
|
51
80
|
|
52
|
-
|
81
|
+
To connect to a database you simply provide Sequel with a URL:
|
53
82
|
|
54
|
-
DB = Sequel.open
|
55
|
-
|
56
|
-
|
83
|
+
DB = Sequel.open 'sqlite:///blog.db'
|
84
|
+
|
85
|
+
The connection URL can also include such stuff as the user name and password:
|
57
86
|
|
58
|
-
DB = Sequel.open
|
59
|
-
:max_connections => 10)
|
87
|
+
DB = Sequel.open 'postgres://cico:12345@localhost:5432/mydb'
|
60
88
|
|
61
|
-
|
89
|
+
You can also specify optional parameters, such as the connection pool size, or a logger for logging SQL queries:
|
62
90
|
|
63
|
-
DB = Sequel
|
91
|
+
DB = Sequel.open("postgres://postgres:postgres@localhost/my_db",
|
92
|
+
:max_connections => 10, :logger => Logger.new('log/db.log'))
|
64
93
|
|
65
94
|
=== Arbitrary SQL queries
|
66
95
|
|
@@ -72,7 +101,7 @@ Or more succinctly:
|
|
72
101
|
DB << "create table t (a text, b text)"
|
73
102
|
DB << "insert into t values ('a', 'b')"
|
74
103
|
|
75
|
-
===
|
104
|
+
=== Getting Dataset Instances
|
76
105
|
|
77
106
|
Dataset is the primary means through which records are retrieved and manipulated. You can create an blank dataset by using the dataset method:
|
78
107
|
|
@@ -147,9 +176,9 @@ You can also specify a custom WHERE clause:
|
|
147
176
|
|
148
177
|
posts.filter('(stamp < ?) AND (author <> ?)', 3.days.ago, author_name)
|
149
178
|
|
150
|
-
Datasets can also be used as subqueries
|
179
|
+
Datasets can also be used as subqueries:
|
151
180
|
|
152
|
-
|
181
|
+
DB[:items].filter('price > ?', DB[:items].select('AVG(price) + 100'))
|
153
182
|
|
154
183
|
=== Summarizing Records
|
155
184
|
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
|
|
6
6
|
include FileUtils
|
7
7
|
|
8
8
|
NAME = "sequel"
|
9
|
-
VERS = "0.0.
|
9
|
+
VERS = "0.0.17"
|
10
10
|
CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
|
11
11
|
RDOC_OPTS = ['--quiet', '--title', "Sequel: Concise ORM for Ruby",
|
12
12
|
"--opname", "index.html",
|
data/lib/sequel/dataset.rb
CHANGED
@@ -36,7 +36,7 @@ module Sequel
|
|
36
36
|
#
|
37
37
|
# Sequel::Dataset is an abstract class that is not useful by itself. Each
|
38
38
|
# database adaptor should provide a descendant class of Sequel::Dataset.
|
39
|
-
def initialize(db, opts =
|
39
|
+
def initialize(db, opts = nil, record_class = nil)
|
40
40
|
@db = db
|
41
41
|
@opts = opts || {}
|
42
42
|
@record_class = record_class
|
@@ -100,7 +100,7 @@ module Sequel
|
|
100
100
|
case source
|
101
101
|
when Array: source.join(COMMA_SEPARATOR)
|
102
102
|
else source
|
103
|
-
end
|
103
|
+
end
|
104
104
|
end
|
105
105
|
|
106
106
|
NULL = "NULL".freeze
|
@@ -149,7 +149,7 @@ module Sequel
|
|
149
149
|
end
|
150
150
|
|
151
151
|
# Formats a where clause.
|
152
|
-
def where_list(where)
|
152
|
+
def where_list(where, parenthesize = false)
|
153
153
|
case where
|
154
154
|
when Hash:
|
155
155
|
where.map {|kv| where_condition(*kv)}.join(AND_SEPARATOR)
|
@@ -157,7 +157,7 @@ module Sequel
|
|
157
157
|
fmt = where.shift
|
158
158
|
fmt.gsub('?') {|i| literal(where.shift)}
|
159
159
|
else
|
160
|
-
where
|
160
|
+
parenthesize ? "(#{where})" : where
|
161
161
|
end
|
162
162
|
end
|
163
163
|
|
@@ -171,7 +171,8 @@ module Sequel
|
|
171
171
|
end
|
172
172
|
|
173
173
|
# Returns a copy of the dataset with the source changed.
|
174
|
-
def from(source)
|
174
|
+
def from(*source)
|
175
|
+
source = source.first if source.size == 1
|
175
176
|
dup_merge(:from => source)
|
176
177
|
end
|
177
178
|
|
@@ -181,6 +182,12 @@ module Sequel
|
|
181
182
|
dup_merge(:select => fields)
|
182
183
|
end
|
183
184
|
|
185
|
+
# Returns a copy of the dataset with the distinct option.
|
186
|
+
def uniq
|
187
|
+
dup_merge(:distinct => true)
|
188
|
+
end
|
189
|
+
alias distinct uniq
|
190
|
+
|
184
191
|
# Returns a copy of the dataset with the order changed.
|
185
192
|
def order(*order)
|
186
193
|
dup_merge(:order => order)
|
@@ -200,26 +207,51 @@ module Sequel
|
|
200
207
|
end
|
201
208
|
end
|
202
209
|
|
210
|
+
# Returns a copy of the dataset with the results grouped by the value of
|
211
|
+
# the given fields
|
212
|
+
def group(*fields)
|
213
|
+
dup_merge(:group => fields)
|
214
|
+
end
|
215
|
+
|
203
216
|
AND_WHERE = "%s AND %s".freeze
|
204
217
|
|
205
|
-
# Returns a copy of the dataset with the
|
206
|
-
|
218
|
+
# Returns a copy of the dataset with the given conditions imposed upon it.
|
219
|
+
# If the query has been grouped, then the conditions are imposed in the
|
220
|
+
# HAVING clause. If not, then they are imposed in the WHERE clause.
|
221
|
+
def filter(*cond)
|
222
|
+
clause = (@opts[:group] ? :having : :where)
|
207
223
|
cond = cond.first if cond.size == 1
|
208
|
-
if @opts[
|
209
|
-
|
210
|
-
cond = @opts[:where].merge(cond)
|
211
|
-
else
|
212
|
-
cond = AND_WHERE % [where_list(@opts[:where]), where_list(cond)]
|
213
|
-
end
|
224
|
+
if @opts[clause]
|
225
|
+
cond = AND_WHERE % [where_list(@opts[clause]), where_list(cond, true)]
|
214
226
|
end
|
215
|
-
dup_merge(
|
227
|
+
dup_merge(clause => where_list(cond))
|
216
228
|
end
|
229
|
+
|
230
|
+
# Returns a copy of the dataset with the where conditions changed. Raises
|
231
|
+
# if the dataset has been grouped. See also #filter
|
232
|
+
def where(*cond)
|
233
|
+
if @opts[:group]
|
234
|
+
raise "Can't specify a WHERE clause once the dataset has been grouped"
|
235
|
+
else
|
236
|
+
filter(*cond)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns a copy of the dataset with the having conditions changed. Raises
|
241
|
+
# if the dataset has not been grouped. See also #filter
|
242
|
+
def having(*cond)
|
243
|
+
unless @opts[:group]
|
244
|
+
raise "Can only specify a HAVING clause on a grouped dataset"
|
245
|
+
else
|
246
|
+
filter(*cond)
|
247
|
+
end
|
248
|
+
end
|
217
249
|
|
218
250
|
NOT_WHERE = "NOT %s".freeze
|
219
251
|
|
220
252
|
def exclude(*cond)
|
221
253
|
cond = cond.first if cond.size == 1
|
222
|
-
|
254
|
+
filter(NOT_WHERE % where_list(cond))
|
223
255
|
end
|
224
256
|
|
225
257
|
LEFT_OUTER_JOIN = 'LEFT OUTER JOIN'.freeze
|
@@ -232,7 +264,6 @@ module Sequel
|
|
232
264
|
:join_cond => cond)
|
233
265
|
end
|
234
266
|
|
235
|
-
alias_method :filter, :where
|
236
267
|
alias_method :all, :to_a
|
237
268
|
|
238
269
|
alias_method :enum_map, :map
|
@@ -266,9 +297,13 @@ module Sequel
|
|
266
297
|
end
|
267
298
|
|
268
299
|
SELECT = "SELECT %s FROM %s".freeze
|
300
|
+
SELECT_DISTINCT = "SELECT DISTINCT %s FROM %s".freeze
|
269
301
|
LIMIT = " LIMIT %s".freeze
|
302
|
+
OFFSET = " OFFSET %s".freeze
|
270
303
|
ORDER = " ORDER BY %s".freeze
|
271
304
|
WHERE = " WHERE %s".freeze
|
305
|
+
GROUP = " GROUP BY %s".freeze
|
306
|
+
HAVING = " HAVING %s".freeze
|
272
307
|
JOIN_CLAUSE = " %s %s ON %s".freeze
|
273
308
|
|
274
309
|
EMPTY = ''.freeze
|
@@ -281,7 +316,7 @@ module Sequel
|
|
281
316
|
fields = opts[:select]
|
282
317
|
select_fields = fields ? field_list(fields) : WILDCARD
|
283
318
|
select_source = source_list(opts[:from])
|
284
|
-
sql = SELECT % [select_fields, select_source]
|
319
|
+
sql = (opts[:distinct] ? SELECT_DISTINCT : SELECT) % [select_fields, select_source]
|
285
320
|
|
286
321
|
if join_type = opts[:join_type]
|
287
322
|
join_table = opts[:join_table]
|
@@ -290,17 +325,28 @@ module Sequel
|
|
290
325
|
end
|
291
326
|
|
292
327
|
if where = opts[:where]
|
293
|
-
sql << (WHERE %
|
328
|
+
sql << (WHERE % where)
|
294
329
|
end
|
295
330
|
|
331
|
+
if group = opts[:group]
|
332
|
+
sql << (GROUP % group.join(COMMA_SEPARATOR))
|
333
|
+
end
|
334
|
+
|
296
335
|
if order = opts[:order]
|
297
336
|
sql << (ORDER % order.join(COMMA_SEPARATOR))
|
298
337
|
end
|
299
338
|
|
339
|
+
if having = opts[:having]
|
340
|
+
sql << (HAVING % having)
|
341
|
+
end
|
342
|
+
|
300
343
|
if limit = opts[:limit]
|
301
344
|
sql << (LIMIT % limit)
|
345
|
+
if offset = opts[:offset]
|
346
|
+
sql << (OFFSET % offset)
|
347
|
+
end
|
302
348
|
end
|
303
|
-
|
349
|
+
|
304
350
|
sql
|
305
351
|
end
|
306
352
|
|
@@ -335,14 +381,21 @@ module Sequel
|
|
335
381
|
def update_sql(values, opts = nil)
|
336
382
|
opts = opts ? @opts.merge(opts) : @opts
|
337
383
|
|
384
|
+
if opts[:group]
|
385
|
+
raise "Can't update a grouped dataset"
|
386
|
+
elsif opts[:from].is_a?(Array) && opts[:from].size > 1
|
387
|
+
raise "Can't update in a joined dataset"
|
388
|
+
end
|
389
|
+
|
338
390
|
set_list = values.map {|kv| SET_FORMAT % [kv[0], literal(kv[1])]}.
|
339
391
|
join(COMMA_SEPARATOR)
|
340
|
-
|
392
|
+
sql = UPDATE % [opts[:from], set_list]
|
341
393
|
|
342
|
-
where = opts[:where]
|
343
|
-
|
394
|
+
if where = opts[:where]
|
395
|
+
sql << WHERE % where_list(where)
|
396
|
+
end
|
344
397
|
|
345
|
-
|
398
|
+
sql
|
346
399
|
end
|
347
400
|
|
348
401
|
DELETE = "DELETE FROM %s".freeze
|
@@ -350,12 +403,19 @@ module Sequel
|
|
350
403
|
def delete_sql(opts = nil)
|
351
404
|
opts = opts ? @opts.merge(opts) : @opts
|
352
405
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
406
|
+
if opts[:group]
|
407
|
+
raise "Can't delete from a grouped dataset"
|
408
|
+
elsif opts[:from].is_a?(Array) && opts[:from].size > 1
|
409
|
+
raise "Can't delete from a joined dataset"
|
410
|
+
end
|
411
|
+
|
412
|
+
sql = DELETE % opts[:from]
|
413
|
+
|
414
|
+
if where = opts[:where]
|
415
|
+
sql << WHERE % where_list(where)
|
416
|
+
end
|
417
|
+
|
418
|
+
sql
|
359
419
|
end
|
360
420
|
|
361
421
|
COUNT = "COUNT(*)".freeze
|
@@ -390,10 +450,22 @@ module Sequel
|
|
390
450
|
|
391
451
|
LIMIT_1 = {:limit => 1}.freeze
|
392
452
|
|
393
|
-
|
394
|
-
|
453
|
+
# If given an integer, the dataset will contain only the first l results.
|
454
|
+
# If given a range, it will contain only those at offsets within that
|
455
|
+
# range. If a second argument is given, it is used as an offset.
|
456
|
+
def limit(l, o = nil)
|
457
|
+
if l.is_a? Range
|
458
|
+
lim = (l.exclude_end? ? l.last - l.first : l.last + 1 - l.first)
|
459
|
+
dup_merge(:limit => lim, :offset=>l.first)
|
460
|
+
elsif o
|
461
|
+
dup_merge(:limit => l, :offset => o)
|
462
|
+
else
|
463
|
+
dup_merge(:limit => l)
|
464
|
+
end
|
395
465
|
end
|
396
|
-
|
466
|
+
|
467
|
+
# Returns the first record in the dataset. If the num argument is specified,
|
468
|
+
# an array is returned with the first <i>num</i> records.
|
397
469
|
def first(num = 1)
|
398
470
|
if num == 1
|
399
471
|
first_record
|
@@ -402,9 +474,15 @@ module Sequel
|
|
402
474
|
end
|
403
475
|
end
|
404
476
|
|
477
|
+
# Returns the first record matching the condition.
|
405
478
|
def [](condition)
|
406
479
|
where(condition).first
|
407
480
|
end
|
481
|
+
|
482
|
+
# Updates all records matching the condition with the values specified.
|
483
|
+
def []=(condition, values)
|
484
|
+
where(condition).update(values)
|
485
|
+
end
|
408
486
|
|
409
487
|
def last(num = 1)
|
410
488
|
raise 'No order specified' unless @opts[:order] || (opts && opts[:order])
|
@@ -420,6 +498,8 @@ module Sequel
|
|
420
498
|
end
|
421
499
|
end
|
422
500
|
|
501
|
+
# Deletes all records in the dataset one at a time by invoking the destroy
|
502
|
+
# method of the associated model class.
|
423
503
|
def destroy
|
424
504
|
raise 'Dataset not associated with model' unless @record_class
|
425
505
|
|
@@ -428,6 +508,7 @@ module Sequel
|
|
428
508
|
count
|
429
509
|
end
|
430
510
|
|
511
|
+
# Pretty prints the records in the dataset as plain-text table.
|
431
512
|
def print(*columns)
|
432
513
|
Sequel::PrettyTable.print(naked.all, columns.empty? ? nil : columns)
|
433
514
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: sequel
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-04-
|
6
|
+
version: 0.0.17
|
7
|
+
date: 2007-04-10 00:00:00 +03:00
|
8
8
|
summary: Concise ORM for Ruby.
|
9
9
|
require_paths:
|
10
10
|
- lib
|