sequel 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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