sequel 0.1.0 → 0.1.1

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.
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ *0.1.1*
2
+
3
+ * More documentation for Dataset.
4
+
5
+ * Added Dataset#size as alias to Dataset#count.
6
+
7
+ * Changed Database#<< to call execute (instead of being an alias). Thus it will work for descendants as well.
8
+
9
+ * Fixed Sequel.open to accept variable arity.
10
+
11
+ * Refactored Model#refresh, Model.create. Removed Model#reload.
12
+
13
+ * Refactored Model hooks.
14
+
15
+ * Cleaned up Dataset API.
16
+
1
17
  *0.1.0*
2
18
 
3
19
  * Changed Database#create_table to only accept a block. Nobody's gonna use the other way.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
6
6
  include FileUtils
7
7
 
8
8
  NAME = "sequel"
9
- VERS = "0.1.0"
9
+ VERS = "0.1.1"
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.rb CHANGED
@@ -20,8 +20,8 @@ module Sequel #:nodoc:
20
20
  # The specified scheme determines the database class used, and the rest
21
21
  # of the string specifies the connection options. For example:
22
22
  # DB = Sequel.open 'sqlite:///blog.db'
23
- def connect(url)
24
- Database.connect(url)
23
+ def connect(*args)
24
+ Database.connect(*args)
25
25
  end
26
26
 
27
27
  alias_method :open, :connect
@@ -36,7 +36,8 @@ module Sequel
36
36
  def execute(sql)
37
37
  raise NotImplementedError
38
38
  end
39
- alias_method :<<, :execute
39
+
40
+ def <<(sql); execute(sql); end
40
41
 
41
42
  # Acquires a database connection, yielding it to the passed block.
42
43
  def synchronize(&block)
@@ -147,11 +148,11 @@ module Sequel
147
148
  # The specified scheme determines the database class used, and the rest
148
149
  # of the string specifies the connection options. For example:
149
150
  # DB = Sequel.open 'sqlite:///blog.db'
150
- def self.connect(conn_string)
151
+ def self.connect(conn_string, more_opts = nil)
151
152
  uri = URI.parse(conn_string)
152
153
  c = @@adapters[uri.scheme.to_sym]
153
154
  raise SequelError, "Invalid database scheme" unless c
154
- c.new(c.uri_to_options(uri))
155
+ c.new(c.uri_to_options(uri).merge(more_opts || {}))
155
156
  end
156
157
  end
157
158
  end
@@ -26,7 +26,7 @@ module Sequel
26
26
  include Enumerable
27
27
 
28
28
  attr_reader :db, :opts
29
- attr_accessor :record_class
29
+ attr_accessor :model_class
30
30
 
31
31
  # Constructs a new instance of a dataset with a database instance, initial
32
32
  # options and an optional record class. Datasets are usually constructed by
@@ -37,29 +37,24 @@ module Sequel
37
37
  #
38
38
  # Sequel::Dataset is an abstract class that is not useful by itself. Each
39
39
  # database adaptor should provide a descendant class of Sequel::Dataset.
40
- def initialize(db, opts = nil, record_class = nil)
40
+ def initialize(db, opts = nil, model_class = nil)
41
41
  @db = db
42
42
  @opts = opts || {}
43
- @record_class = record_class
43
+ @model_class = model_class
44
44
  end
45
45
 
46
- # Returns a new instance of the dataset with its options
46
+ # Returns a new instance of the dataset with with the give options merged.
47
47
  def dup_merge(opts)
48
- self.class.new(@db, @opts.merge(opts), @record_class)
48
+ self.class.new(@db, @opts.merge(opts), @model_class)
49
49
  end
50
50
 
51
51
  # Returns a dataset that fetches records as hashes (instead of model
52
52
  # objects). If no record class is defined for the dataset, self is
53
53
  # returned.
54
54
  def naked
55
- @record_class ? self.class.new(@db, @opts.dup) : self
55
+ @model_class ? self.class.new(@db, opts || @opts.dup) : self
56
56
  end
57
57
 
58
- AS_REGEXP = /(.*)___(.*)/.freeze
59
- AS_FORMAT = "%s AS %s".freeze
60
- DOUBLE_UNDERSCORE = '__'.freeze
61
- PERIOD = '.'.freeze
62
-
63
58
  # Returns a valid SQL fieldname as a string. Field names specified as
64
59
  # symbols can include double underscores to denote a dot separator, e.g.
65
60
  # :posts__id will be converted into posts.id.
@@ -79,7 +74,8 @@ module Sequel
79
74
  WILDCARD = '*'.freeze
80
75
  COMMA_SEPARATOR = ", ".freeze
81
76
 
82
- # Converts a field list into a comma seperated string of field names.
77
+ # Converts an array of field names into a comma seperated string of
78
+ # field names. If the array is empty, a wildcard (*) is returned.
83
79
  def field_list(fields)
84
80
  if fields.empty?
85
81
  WILDCARD
@@ -88,9 +84,11 @@ module Sequel
88
84
  end
89
85
  end
90
86
 
91
- # Converts an array of sources into a comma separated list.
87
+ # Converts an array of sources names into into a comma separated list.
92
88
  def source_list(source)
93
- raise SequelError, 'No source specified for query' unless source
89
+ if source.nil? || source.empty?
90
+ raise SequelError, 'No source specified for query'
91
+ end
94
92
  source.map {|i| i.is_a?(Dataset) ? i.to_table_reference : i}.
95
93
  join(COMMA_SEPARATOR)
96
94
  end
@@ -100,10 +98,21 @@ module Sequel
100
98
  DATE_FORMAT = "DATE '%Y-%m-%d'".freeze
101
99
 
102
100
  # Returns a literal representation of a value to be used as part
103
- # of an SQL expression. This method is overriden in descendants.
101
+ # of an SQL expression. The stock implementation supports literalization
102
+ # of String (with proper escaping to prevent SQL injections), numbers,
103
+ # Symbol (as field references), Array (as a list of literalized values),
104
+ # Time (as an SQL TIMESTAMP), Date (as an SQL DATE), Dataset (as a
105
+ # subquery) and nil (AS NULL).
106
+ #
107
+ # dataset.literal("abc'def") #=> "'abc''def'"
108
+ # dataset.literal(:items__id) #=> "items.id"
109
+ # dataset.literal([1, 2, 3]) => "(1, 2, 3)"
110
+ # dataset.literal(DB[:items]) => "(SELECT * FROM items)"
111
+ #
112
+ # If an unsupported object is given, an exception is raised.
104
113
  def literal(v)
105
114
  case v
106
- when String: "'%s'" % v.gsub(/'/, "''")
115
+ when String: "'#{v.gsub(/'/, "''")}'"
107
116
  when Integer, Float: v.to_s
108
117
  when NilClass: NULL
109
118
  when Symbol: v.to_field_name
@@ -112,12 +121,28 @@ module Sequel
112
121
  when Date: v.strftime(DATE_FORMAT)
113
122
  when Dataset: "(#{v.sql})"
114
123
  else
115
- raise SequelError, "can't express #{v.inspect}:#{v.class} as a SQL literal"
124
+ raise SequelError, "can't express #{v.inspect} as a SQL literal"
116
125
  end
117
126
  end
118
127
 
119
128
  AND_SEPARATOR = " AND ".freeze
120
129
 
130
+ # Formats an equality expression involving a left value and a right value.
131
+ # Equality expressions differ according to the class of the right value.
132
+ # The stock implementation supports Range (inclusive and exclusive), Array
133
+ # (as a list of values to compare against), Dataset (as a subquery to
134
+ # compare against), or a regular value.
135
+ #
136
+ # dataset.format_eq_expression('id', 1..20) #=>
137
+ # "(id >= 1 AND id <= 20)"
138
+ # dataset.format_eq_expression('id', [3,6,10]) #=>
139
+ # "(id IN (3, 6, 10))"
140
+ # dataset.format_eq_expression('id', DB[:items].select(:id)) #=>
141
+ # "(id IN (SELECT id FROM items))"
142
+ # dataset.format_eq_expression('id', nil) #=>
143
+ # "(id IS NULL)"
144
+ # dataset.format_eq_expression('id', 3) #=>
145
+ # "(id = 3)"
121
146
  def format_eq_expression(left, right)
122
147
  case right
123
148
  when Range:
@@ -127,7 +152,7 @@ module Sequel
127
152
  when Array:
128
153
  "(#{left} IN (#{literal(right)}))"
129
154
  when Dataset:
130
- "(#{left} IN #{literal(right)})"
155
+ "(#{left} IN (#{right.sql}))"
131
156
  when NilClass:
132
157
  "(#{left} IS NULL)"
133
158
  else
@@ -135,6 +160,16 @@ module Sequel
135
160
  end
136
161
  end
137
162
 
163
+ # Formats an expression comprising a left value, a binary operator and a
164
+ # right value. The supported operators are :eql (=), :not (!=), :lt (<),
165
+ # :lte (<=), :gt (>), :gte (>=) and :like (LIKE operator). Examples:
166
+ #
167
+ # dataset.format_expression('price', :gte, 100) #=> "(price >= 100)"
168
+ # dataset.format_expression('id', :not, 30) #=> "NOT (id = 30)"
169
+ # dataset.format_expression('name', :like, 'abc%') #=>
170
+ # "(name LIKE 'abc%')"
171
+ #
172
+ # If an unsupported operator is given, an exception is raised.
138
173
  def format_expression(left, op, right)
139
174
  left = field_name(left)
140
175
  case op
@@ -157,6 +192,8 @@ module Sequel
157
192
  end
158
193
  end
159
194
 
195
+ QUESTION_MARK = '?'.freeze
196
+
160
197
  # Formats a where clause. If parenthesize is true, then the whole
161
198
  # generated clause will be enclosed in a set of parentheses.
162
199
  def expression_list(where, parenthesize = false)
@@ -166,8 +203,7 @@ module Sequel
166
203
  fmt = where.map {|i| format_expression(i[0], :eql, i[1])}.
167
204
  join(AND_SEPARATOR)
168
205
  when Array:
169
- fmt = where.shift
170
- fmt.gsub!('?') {|i| literal(where.shift)}
206
+ fmt = where.shift.gsub(QUESTION_MARK) {literal(where.shift)}
171
207
  when Proc:
172
208
  fmt = where.to_expressions.map {|e| format_expression(e.left, e.op, e.right)}.
173
209
  join(AND_SEPARATOR)
@@ -215,6 +251,12 @@ module Sequel
215
251
 
216
252
  DESC_ORDER_REGEXP = /(.*)\sDESC/.freeze
217
253
 
254
+ # Inverts the given order by breaking it into a list of field references
255
+ # and inverting them.
256
+ #
257
+ # dataset.invert_order('id DESC') #=> "id"
258
+ # dataset.invert_order('category, price DESC') #=>
259
+ # "category DESC, price"
218
260
  def invert_order(order)
219
261
  new_order = []
220
262
  order.each do |f|
@@ -234,7 +276,25 @@ module Sequel
234
276
 
235
277
  # Returns a copy of the dataset with the given conditions imposed upon it.
236
278
  # If the query has been grouped, then the conditions are imposed in the
237
- # HAVING clause. If not, then they are imposed in the WHERE clause.
279
+ # HAVING clause. If not, then they are imposed in the WHERE clause. Filter
280
+ # accepts a Hash (formated into a list of equality expressions), an Array
281
+ # (formatted ala ActiveRecord conditions), a String (taken literally), or
282
+ # a block that is converted into expressions.
283
+ #
284
+ # dataset.filter(:id => 3).sql #=>
285
+ # "SELECT * FROM items WHERE (id = 3)"
286
+ # dataset.filter('price < ?', 100).sql #=>
287
+ # "SELECT * FROM items WHERE price < 100"
288
+ # dataset.filter('price < 100').sql #=>
289
+ # "SELECT * FROM items WHERE price < 100"
290
+ # dataset.filter {price < 100}.sql #=>
291
+ # "SELECT * FROM items WHERE (price < 100)"
292
+ #
293
+ # Multiple filter calls can be chained for scoping:
294
+ #
295
+ # software = dataset.filter(:category => 'software')
296
+ # software.filter {price < 100}.sql #=>
297
+ # "SELECT * FROM items WHERE (category = 'software') AND (price < 100)"
238
298
  def filter(*cond, &block)
239
299
  clause = (@opts[:group] ? :having : :where)
240
300
  cond = cond.first if cond.size == 1
@@ -248,6 +308,10 @@ module Sequel
248
308
  end
249
309
  end
250
310
 
311
+ # Performs the inverse of Dataset#filter.
312
+ #
313
+ # dataset.exclude(:category => 'software').sql #=>
314
+ # "SELECT * FROM items WHERE NOT (category = 'software')"
251
315
  def exclude(*cond, &block)
252
316
  clause = (@opts[:group] ? :having : :where)
253
317
  cond = cond.first if cond.size == 1
@@ -263,7 +327,7 @@ module Sequel
263
327
  end
264
328
 
265
329
  # Returns a copy of the dataset with the where conditions changed. Raises
266
- # if the dataset has been grouped. See also #filter
330
+ # if the dataset has been grouped. See also #filter.
267
331
  def where(*cond, &block)
268
332
  if @opts[:group]
269
333
  raise SequelError, "Can't specify a WHERE clause once the dataset has been grouped"
@@ -286,26 +350,27 @@ module Sequel
286
350
  INNER_JOIN = 'INNER JOIN'.freeze
287
351
  RIGHT_OUTER_JOIN = 'RIGHT OUTER JOIN'.freeze
288
352
  FULL_OUTER_JOIN = 'FULL OUTER JOIN'.freeze
289
-
353
+
354
+ # Returns a joined dataset.
290
355
  def join(table, expr)
291
356
  expr = {expr => :id} unless expr.is_a?(Hash)
292
357
  dup_merge(:join_type => LEFT_OUTER_JOIN, :join_table => table,
293
358
  :join_cond => expr)
294
359
  end
295
360
 
296
- alias_method :all, :to_a
361
+ alias all to_a
297
362
 
298
- alias_method :enum_map, :map
363
+ # Maps field values for each record in the dataset (if a field name is
364
+ # given), or performs the stock mapping functionality of Enumerable.
299
365
  def map(field_name = nil, &block)
300
- if block
301
- enum_map(&block)
302
- elsif field_name
303
- enum_map {|r| r[field_name]}
366
+ if field_name
367
+ super() {|r| r[field_name]}
304
368
  else
305
- []
369
+ super(&block)
306
370
  end
307
371
  end
308
372
 
373
+ # Returns a hash with one column used as key and another used as value.
309
374
  def hash_column(key_column, value_column)
310
375
  inject({}) do |m, r|
311
376
  m[r[key_column]] = r[value_column]
@@ -313,10 +378,13 @@ module Sequel
313
378
  end
314
379
  end
315
380
 
381
+ # Inserts the given values into the table.
316
382
  def <<(values)
317
383
  insert(values)
318
384
  end
319
385
 
386
+ # Inserts multiple values. If a block is given it is invoked for each
387
+ # item in the given array before inserting it.
320
388
  def insert_multiple(array, &block)
321
389
  if block
322
390
  array.each {|i| insert(block[i])}
@@ -326,9 +394,10 @@ module Sequel
326
394
  end
327
395
 
328
396
  EMPTY = ''.freeze
329
-
330
397
  SPACE = ' '.freeze
331
398
 
399
+ # Formats a SELECT statement using the given options and the dataset
400
+ # options.
332
401
  def select_sql(opts = nil)
333
402
  opts = opts ? @opts.merge(opts) : @opts
334
403
 
@@ -367,12 +436,19 @@ module Sequel
367
436
  sql << " OFFSET #{offset}"
368
437
  end
369
438
  end
370
-
439
+
371
440
  sql
372
441
  end
373
-
374
- alias_method :sql, :select_sql
375
-
442
+ alias sql select_sql
443
+
444
+ # Formats an INSERT statement using the given values. If a hash is given,
445
+ # the resulting statement includes field names. If no values are given,
446
+ # the resulting statement includes a DEFAULT VALUES clause.
447
+ #
448
+ # dataset.insert_sql() #=> 'INSERT INTO items DEFAULT VALUES'
449
+ # dataset.insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
450
+ # dataset.insert_sql(:a => 1, :b => 2) #=>
451
+ # 'INSERT INTO items (a, b) VALUES (1, 2)'
376
452
  def insert_sql(*values)
377
453
  if values.empty?
378
454
  "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
@@ -391,6 +467,10 @@ module Sequel
391
467
  end
392
468
  end
393
469
 
470
+ # Formats an UPDATE statement using the given values.
471
+ #
472
+ # dataset.update_sql(:price => 100, :category => 'software') #=>
473
+ # "UPDATE items SET price = 100, category = 'software'"
394
474
  def update_sql(values, opts = nil)
395
475
  opts = opts ? @opts.merge(opts) : @opts
396
476
 
@@ -400,7 +480,7 @@ module Sequel
400
480
  raise SequelError, "Can't update a joined dataset"
401
481
  end
402
482
 
403
- set_list = values.map {|kv| "#{kv[0]} = #{literal(kv[1])}"}.
483
+ set_list = values.map {|k, v| "#{k} = #{literal(v)}"}.
404
484
  join(COMMA_SEPARATOR)
405
485
  sql = "UPDATE #{@opts[:from]} SET #{set_list}"
406
486
 
@@ -411,11 +491,15 @@ module Sequel
411
491
  sql
412
492
  end
413
493
 
494
+ # Formats a DELETE statement using the given options and dataset options.
495
+ #
496
+ # dataset.filter {price >= 100}.delete_sql #=>
497
+ # "DELETE FROM items WHERE (price >= 100)"
414
498
  def delete_sql(opts = nil)
415
499
  opts = opts ? @opts.merge(opts) : @opts
416
500
 
417
501
  if opts[:group]
418
- raise SequelError, "Can't delete from a grouped dataset"
502
+ raise SequelError, "Can't delete from a grouped dataset"
419
503
  elsif opts[:from].is_a?(Array) && opts[:from].size > 1
420
504
  raise SequelError, "Can't delete from a joined dataset"
421
505
  end
@@ -429,13 +513,27 @@ module Sequel
429
513
  sql
430
514
  end
431
515
 
432
- COUNT = "COUNT(*)".freeze
433
- SELECT_COUNT = {:select => COUNT, :order => nil}.freeze
516
+ # Returns the first record in the dataset.
517
+ def single_record(opts = nil)
518
+ each(opts) {|r| return r}
519
+ end
520
+
521
+ # Returns the first value of the first reecord in the dataset.
522
+ def single_value(opts = nil)
523
+ naked.each(opts) {|r| return r.values.first}
524
+ end
525
+
526
+ SELECT_COUNT = {:select => ["COUNT(*)"], :order => nil}.freeze
434
527
 
435
- def count_sql(opts = nil)
436
- select_sql(opts ? opts.merge(SELECT_COUNT) : SELECT_COUNT)
528
+ # Returns the number of records in the dataset.
529
+ def count
530
+ single_value(SELECT_COUNT).to_i
437
531
  end
438
-
532
+ alias size count
533
+
534
+ # Returns a table reference for use in the FROM clause. If the dataset has
535
+ # only a :from option refering to a single table, only the table name is
536
+ # returned. Otherwise a subquery is returned.
439
537
  def to_table_reference
440
538
  if opts.keys == [:from] && opts[:from].size == 1
441
539
  opts[:from].first.to_s
@@ -444,29 +542,33 @@ module Sequel
444
542
  end
445
543
  end
446
544
 
447
- # aggregates
545
+ # Returns the minimum value for the given field.
448
546
  def min(field)
449
- select(field.MIN).naked.first.values.first
547
+ single_value(:select => [field.MIN])
450
548
  end
451
549
 
550
+ # Returns the maximum value for the given field.
452
551
  def max(field)
453
- select(field.MAX).naked.first.values.first
552
+ single_value(:select => [field.MAX])
454
553
  end
455
554
 
555
+ # Returns the sum for the given field.
456
556
  def sum(field)
457
- select(field.SUM).naked.first.values.first
557
+ single_value(:select => [field.SUM])
458
558
  end
459
559
 
560
+ # Returns the average value for the given field.
460
561
  def avg(field)
461
- select(field.AVG).naked.first.values.first
562
+ single_value(:select => [field.AVG])
462
563
  end
463
564
 
565
+ # Returns an EXISTS clause for the dataset.
566
+ #
567
+ # dataset.exists #=> "EXISTS (SELECT 1 FROM items)"
464
568
  def exists(opts = nil)
465
569
  "EXISTS (#{sql({:select => [1]}.merge(opts || {}))})"
466
570
  end
467
571
 
468
- LIMIT_1 = {:limit => 1}.freeze
469
-
470
572
  # If given an integer, the dataset will contain only the first l results.
471
573
  # If given a range, it will contain only those at offsets within that
472
574
  # range. If a second argument is given, it is used as an offset.
@@ -485,7 +587,7 @@ module Sequel
485
587
  # an array is returned with the first <i>num</i> records.
486
588
  def first(num = 1)
487
589
  if num == 1
488
- first_record
590
+ single_record
489
591
  else
490
592
  limit(num).all
491
593
  end
@@ -496,6 +598,10 @@ module Sequel
496
598
  where(*conditions).first
497
599
  end
498
600
 
601
+ # Returns the last records in the dataset by inverting the order. If no
602
+ # order is given, an exception is raised. If num is not given, the last
603
+ # record is returned. Otherwise an array is returned with the last
604
+ # <i>num</i> records.
499
605
  def last(num = 1)
500
606
  raise SequelError, 'No order specified' unless
501
607
  @opts[:order] || (opts && opts[:order])
@@ -505,7 +611,7 @@ module Sequel
505
611
  merge(opts ? opts.merge(l) : l)
506
612
 
507
613
  if num == 1
508
- first_record(opts)
614
+ single_record(opts)
509
615
  else
510
616
  dup_merge(opts).all
511
617
  end
@@ -514,7 +620,7 @@ module Sequel
514
620
  # Deletes all records in the dataset one at a time by invoking the destroy
515
621
  # method of the associated model class.
516
622
  def destroy
517
- raise SequelError, 'Dataset not associated with model' unless @record_class
623
+ raise SequelError, 'Dataset not associated with model' unless @model_class
518
624
 
519
625
  count = 0
520
626
  @db.transaction {each {|r| count += 1; r.destroy}}
@@ -543,14 +649,13 @@ class Symbol
543
649
  def AVG; "avg(#{to_field_name})"; end
544
650
 
545
651
  AS_REGEXP = /(.*)___(.*)/.freeze
546
- AS_FORMAT = "%s AS %s".freeze
547
652
  DOUBLE_UNDERSCORE = '__'.freeze
548
653
  PERIOD = '.'.freeze
549
654
 
550
655
  def to_field_name
551
656
  s = to_s
552
657
  if s =~ AS_REGEXP
553
- s = AS_FORMAT % [$1, $2]
658
+ s = "#{$1} AS #{$2}"
554
659
  end
555
660
  s.split(DOUBLE_UNDERSCORE).join(PERIOD)
556
661
  end
data/lib/sequel/model.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'rubygems'
1
2
  require 'metaid'
2
3
 
3
4
  module Sequel
@@ -22,14 +23,14 @@ module Sequel
22
23
  raise RuntimeError, "No database connected."
23
24
  end
24
25
  @dataset = db[table_name]
25
- @dataset.record_class = self
26
+ @dataset.model_class = self
26
27
  @dataset
27
28
  end
28
29
 
29
30
  def self.set_dataset(ds)
30
31
  @db = ds.db
31
32
  @dataset = ds
32
- @dataset.record_class = self
33
+ @dataset.model_class = self
33
34
  end
34
35
 
35
36
  def self.cache_by(column, expiration)
@@ -124,11 +125,19 @@ module Sequel
124
125
  def run_hooks(key)
125
126
  self.class.get_hooks(key).each {|h| instance_eval(&h)}
126
127
  end
127
-
128
+
129
+ def self.before_create(&block)
130
+ get_hooks(:before_create).unshift(block)
131
+ end
132
+
128
133
  def self.before_destroy(&block)
129
134
  get_hooks(:before_destroy).unshift(block)
130
135
  end
131
136
 
137
+ def self.after_destroy(&block)
138
+ get_hooks(:after_destroy).unshift(block)
139
+ end
140
+
132
141
  def self.after_create(&block)
133
142
  get_hooks(:after_create) << block
134
143
  end
@@ -161,8 +170,7 @@ module Sequel
161
170
  end
162
171
 
163
172
  def refresh
164
- record = self.class.find(primary_key => @pkey)
165
- record ? (@values = record.values) :
173
+ @values = self.class.dataset.naked[primary_key => @pkey] ||
166
174
  (raise RuntimeError, "Record not found")
167
175
  self
168
176
  end
@@ -185,8 +193,8 @@ module Sequel
185
193
 
186
194
  def self.create(values = nil)
187
195
  db.transaction do
188
- obj = find(primary_key => dataset.insert(values))
189
- obj.run_hooks(:after_create)
196
+ obj = new(values || {})
197
+ obj.save
190
198
  obj
191
199
  end
192
200
  end
@@ -225,11 +233,6 @@ module Sequel
225
233
 
226
234
  def db; self.class.db; end
227
235
 
228
- def reload
229
- temp = self.class[@pkey]
230
- @values = self.class.dataset.naked[primary_key => @pkey]
231
- end
232
-
233
236
  def [](field); @values[field]; end
234
237
 
235
238
  def []=(field, value); @values[field] = value; end
@@ -247,12 +250,18 @@ module Sequel
247
250
  def id; @values[:id]; end
248
251
 
249
252
  def save
253
+ run_hooks(:before_save)
250
254
  if @pkey
255
+ # run_hooks(:before_update)
251
256
  model.dataset.filter(primary_key => @pkey).update(@values)
257
+ # run_hooks(:after_update)
252
258
  else
259
+ # run_hooks(:before_create)
253
260
  @pkey = model.dataset.insert(@values)
254
- reload
261
+ refresh
262
+ # run_hooks(:after_create)
255
263
  end
264
+ run_hooks(:after_save)
256
265
  end
257
266
 
258
267
  def ==(obj)
data/lib/sequel/mysql.rb CHANGED
@@ -56,10 +56,6 @@ module Sequel
56
56
  self
57
57
  end
58
58
 
59
- def first_record(opts = nil)
60
- query_first(select_sql(opts), true)
61
- end
62
-
63
59
  def count(opts = nil)
64
60
  query_single_value(count_sql(opts)).to_i
65
61
  end
@@ -76,12 +72,12 @@ module Sequel
76
72
  @db.execute_affected(delete_sql(opts))
77
73
  end
78
74
 
79
- def query_each(sql, use_record_class = false)
75
+ def query_each(sql, use_model_class = false)
80
76
  @db.synchronize do
81
77
  result = @db.execute(sql)
82
78
  begin
83
- if use_record_class && @record_class
84
- result.each_hash {|r| yield @record_class.new(r)}
79
+ if use_model_class && @model_class
80
+ result.each_hash {|r| yield @model_class.new(r)}
85
81
  else
86
82
  result.each_hash {|r| yield r}
87
83
  end
@@ -92,12 +88,20 @@ module Sequel
92
88
  self
93
89
  end
94
90
 
95
- def query_first(sql, use_record_class = false)
91
+ def single_record(opts = nil)
92
+ query_single(select_sql(opts), true)
93
+ end
94
+
95
+ def single_value(opts = nil)
96
+ query_single_value(select_sql(opts))
97
+ end
98
+
99
+ def query_single(sql, use_model_class = false)
96
100
  @db.synchronize do
97
101
  result = @db.execute(sql)
98
102
  begin
99
- if use_record_class && @record_class
100
- @record_class.new(result.fetch_hash)
103
+ if use_model_class && @model_class
104
+ @model_class.new(result.fetch_hash)
101
105
  else
102
106
  result.fetch_hash
103
107
  end
@@ -129,11 +129,12 @@ class String
129
129
  TIME_REGEXP = /(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})/
130
130
 
131
131
  def postgres_to_time
132
- if self =~ TIME_REGEXP
133
- Time.local($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i)
134
- else
135
- nil
136
- end
132
+ Time.parse(self)
133
+ # if self =~ TIME_REGEXP
134
+ # Time.local($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i)
135
+ # else
136
+ # nil
137
+ # end
137
138
  end
138
139
  end
139
140
 
@@ -215,8 +216,24 @@ module Sequel
215
216
  end
216
217
 
217
218
  class Dataset < Sequel::Dataset
219
+ TRUE = "'t'".freeze
220
+ FALSE = "'f'".freeze
221
+
218
222
  def literal(v)
219
223
  case v
224
+ # when String: "'%s'" % v.gsub(/'/, "''")
225
+ # when Integer, Float: v.to_s
226
+ # when NilClass: NULL
227
+ # when Symbol: v.to_field_name
228
+ # when Array: v.empty? ? NULL : v.map {|i| literal(i)}.join(COMMA_SEPARATOR)
229
+ # when Time: v.strftime(TIMESTAMP_FORMAT)
230
+ # when Date: v.strftime(DATE_FORMAT)
231
+ # when Dataset: "(#{v.sql})"
232
+ # when true: TRUE
233
+ # when false: FALSE
234
+ # else
235
+ # raise SequelError, "can't express #{v.inspect}:#{v.class} as a SQL literal"
236
+ # end
220
237
  when String, Fixnum, Float, TrueClass, FalseClass: PGconn.quote(v)
221
238
  else
222
239
  super
@@ -241,10 +258,6 @@ module Sequel
241
258
  self
242
259
  end
243
260
 
244
- def first_record(opts = nil)
245
- query_first(select_sql(opts), true)
246
- end
247
-
248
261
  FOR_UPDATE = ' FOR UPDATE'.freeze
249
262
  FOR_SHARE = ' FOR SHARE'.freeze
250
263
 
@@ -301,10 +314,6 @@ module Sequel
301
314
  end
302
315
  end
303
316
 
304
- def count(opts = nil)
305
- query_single_value(count_sql(opts)).to_i
306
- end
307
-
308
317
  def insert(*values)
309
318
  @db.execute_insert(insert_sql(*values), @opts[:from])
310
319
  end
@@ -333,25 +342,20 @@ module Sequel
333
342
  end
334
343
  end
335
344
 
336
- def query_all(sql, use_record_class = false)
337
- @db.synchronize do
338
- result = @db.execute(sql)
339
- begin
340
- conv = row_converter(result, use_record_class)
341
- all = []
342
- result.each {|r| all << conv[r]}
343
- ensure
344
- result.clear
345
- end
346
- all
347
- end
345
+ def single_record(opts = nil)
346
+ query_single(select_sql(opts), true)
348
347
  end
349
-
350
- def query_each(sql, use_record_class = false)
348
+
349
+ def single_value(opts = nil)
350
+ query_single_value(select_sql(opts))
351
+ end
352
+
353
+ def query_each(sql, use_model_class = false, &block)
351
354
  @db.synchronize do
352
355
  result = @db.execute(sql)
353
356
  begin
354
- conv = row_converter(result, use_record_class)
357
+ # each_row(result, use_model_class, &block)
358
+ conv = row_converter(result, use_model_class)
355
359
  result.each {|r| yield conv[r]}
356
360
  ensure
357
361
  result.clear
@@ -359,12 +363,13 @@ module Sequel
359
363
  end
360
364
  end
361
365
 
362
- def query_first(sql, use_record_class = false)
366
+ def query_single(sql, use_model_class = false)
363
367
  @db.synchronize do
364
368
  result = @db.execute(sql)
365
369
  begin
366
370
  row = nil
367
- conv = row_converter(result, use_record_class)
371
+ # each_row(result, use_model_class) {|r| row = r}
372
+ conv = row_converter(result, use_model_class)
368
373
  result.each {|r| row = conv.call(r)}
369
374
  ensure
370
375
  result.clear
@@ -378,22 +383,42 @@ module Sequel
378
383
  result = @db.execute(sql)
379
384
  begin
380
385
  value = result.getvalue(0, 0)
386
+ if value
387
+ value = value.send(PG_TYPES[result.type(0)])
388
+ end
381
389
  ensure
382
390
  result.clear
383
391
  end
384
392
  value
385
393
  end
386
394
  end
395
+
396
+ def each_row(result, use_model_class)
397
+ fields = result.fields.map {|s| s.to_sym}
398
+ types = (0..(result.num_fields - 1)).map {|idx| PG_TYPES[result.type(idx)]}
399
+ m_klass = use_model_class && @model_class
400
+ result.each do |row|
401
+ hashed_row = {}
402
+ row.each_index do |cel_index|
403
+ column = row[cel_index]
404
+ if column && types[cel_index]
405
+ column = column.send(types[cel_index])
406
+ end
407
+ hashed_row[fields[cel_index]] = column
408
+ end
409
+ yield m_klass ? m_klass.new(hashed_row) : hashed_row
410
+ end
411
+ end
387
412
 
388
413
  COMMA = ','.freeze
389
414
 
390
415
  @@converters_mutex = Mutex.new
391
416
  @@converters = {}
392
417
 
393
- def row_converter(result, use_record_class)
418
+ def row_converter(result, use_model_class)
394
419
  fields = result.fields.map {|s| s.to_sym}
395
420
  types = (0..(result.num_fields - 1)).map {|idx| result.type(idx)}
396
- klass = use_record_class ? @record_class : nil
421
+ klass = use_model_class ? @model_class : nil
397
422
 
398
423
  # create result signature and memoize the converter
399
424
  sig = fields.join(COMMA) + types.join(COMMA) + klass.to_s
@@ -403,7 +428,7 @@ module Sequel
403
428
  end
404
429
 
405
430
  CONVERT = "lambda {|r| {%s}}".freeze
406
- CONVERT_RECORD_CLASS = "lambda {|r| %2$s.new(%1$s)}".freeze
431
+ CONVERT_MODEL_CLASS = "lambda {|r| %2$s.new(%1$s)}".freeze
407
432
 
408
433
  CONVERT_FIELD = '%s => r[%d]'.freeze
409
434
  CONVERT_FIELD_TRANSLATE = '%s => ((t = r[%d]) ? t.%s : nil)'.freeze
@@ -419,7 +444,7 @@ module Sequel
419
444
  kvs << (translate_fn ? CONVERT_FIELD_TRANSLATE : CONVERT_FIELD) %
420
445
  [field.inspect, idx, translate_fn]
421
446
  end
422
- s = (klass ? CONVERT_RECORD_CLASS : CONVERT) %
447
+ s = (klass ? CONVERT_MODEL_CLASS : CONVERT) %
423
448
  [kvs.join(COMMA), klass]
424
449
  eval(s)
425
450
  end
data/lib/sequel/sqlite.rb CHANGED
@@ -43,7 +43,7 @@ module Sequel
43
43
  @pool.hold {|conn| conn.get_first_value(sql)}
44
44
  end
45
45
 
46
- def result_set(sql, record_class, &block)
46
+ def result_set(sql, model_class, &block)
47
47
  @pool.hold do |conn|
48
48
  conn.query(sql) do |result|
49
49
  columns = result.columns
@@ -51,7 +51,7 @@ module Sequel
51
51
  result.each do |values|
52
52
  row = {}
53
53
  column_count.times {|i| row[columns[i].to_sym] = values[i]}
54
- block.call(record_class ? record_class.new(row) : row)
54
+ block.call(model_class ? model_class.new(row) : row)
55
55
  end
56
56
  end
57
57
  end
@@ -68,20 +68,10 @@ module Sequel
68
68
  end
69
69
 
70
70
  def each(opts = nil, &block)
71
- @db.result_set(select_sql(opts), @record_class, &block)
71
+ @db.result_set(select_sql(opts), @model_class, &block)
72
72
  self
73
73
  end
74
74
 
75
- LIMIT_1 = {:limit => 1}.freeze
76
-
77
- def first_record(opts = nil)
78
- @db.result_set(select_sql(opts), @record_class) {|r| return r}
79
- end
80
-
81
- def count(opts = nil)
82
- @db.single_value(count_sql(opts)).to_i
83
- end
84
-
85
75
  def insert(*values)
86
76
  @db.synchronize do
87
77
  @db.execute_insert insert_sql(*values)
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.1.0
7
- date: 2007-04-22 00:00:00 +03:00
6
+ version: 0.1.1
7
+ date: 2007-04-28 00:00:00 +03:00
8
8
  summary: Concise ORM for Ruby.
9
9
  require_paths:
10
10
  - lib