sequel 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,25 @@
1
+ *0.1.7*
2
+
3
+ * Removed db.synchronize wrapping calls in sqlite adapter.
4
+
5
+ * Implemented Model.join method to restrict returned columns to the model table (thanks Pedro Gutierrez).
6
+
7
+ * Implemented Dataset#paginate method.
8
+
9
+ * Fixed after_destroy hook.
10
+
11
+ * Improved Dataset#first and #last to accept a filter hash.
12
+
13
+ * Added Dataset#[]= method.
14
+
15
+ * Added Sequel() convenience method.
16
+
17
+ * Fixed Dataset#first to include a LIMIT clause for a single record.
18
+
19
+ * Small fix to Postgres driver to return a primary_key value for the inserted record if it is specified in the insertion values (thanks Florian Aßmann and Pedro Gutierrez).
20
+
21
+ * Fixed Symbol#DESC to support qualified notation (thanks Pedro Gutierrez).
22
+
1
23
  *0.1.6*
2
24
 
3
25
  * Fixed Model#method_missing to raise for an invalid attribute.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
6
6
  include FileUtils
7
7
 
8
8
  NAME = "sequel"
9
- VERS = "0.1.6"
9
+ VERS = "0.1.7"
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",
@@ -27,3 +27,9 @@ module Sequel #:nodoc:
27
27
  alias_method :open, :connect
28
28
  end
29
29
  end
30
+
31
+ class Object
32
+ def Sequel(uri)
33
+ Sequel.connect(uri)
34
+ end
35
+ end
@@ -39,3 +39,35 @@ class String
39
39
  Sequel::ExpressionString.new(self)
40
40
  end
41
41
  end
42
+
43
+ # Symbol extensions
44
+ class Symbol
45
+ def DESC
46
+ "#{to_field_name} DESC"
47
+ end
48
+
49
+ def AS(target)
50
+ "#{to_field_name} AS #{target}"
51
+ end
52
+
53
+ AS_REGEXP = /(.*)___(.*)/.freeze
54
+ DOUBLE_UNDERSCORE = '__'.freeze
55
+ PERIOD = '.'.freeze
56
+
57
+ def to_field_name
58
+ s = to_s
59
+ if s =~ AS_REGEXP
60
+ s = "#{$1} AS #{$2}"
61
+ end
62
+ s.split(DOUBLE_UNDERSCORE).join(PERIOD)
63
+ end
64
+
65
+ def ALL
66
+ "#{to_s}.*"
67
+ end
68
+
69
+ def MIN; "min(#{to_field_name})"; end
70
+ def MAX; "max(#{to_field_name})"; end
71
+ def SUM; "sum(#{to_field_name})"; end
72
+ def AVG; "avg(#{to_field_name})"; end
73
+ end
@@ -294,12 +294,17 @@ module Sequel
294
294
  clause = (@opts[:group] ? :having : :where)
295
295
  cond = cond.first if cond.size == 1
296
296
  parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
297
+ filter = cond.is_a?(Hash) && cond
297
298
  if @opts[clause]
299
+ if filter && cond.is_a?(Hash)
300
+ filter
301
+ end
302
+ filter =
298
303
  l = expression_list(@opts[clause])
299
304
  r = expression_list(block || cond, parenthesize)
300
305
  dup_merge(clause => "#{l} AND #{r}")
301
306
  else
302
- dup_merge(clause => expression_list(block || cond))
307
+ dup_merge(:filter => cond, clause => expression_list(block || cond))
303
308
  end
304
309
  end
305
310
 
@@ -606,6 +611,30 @@ module Sequel
606
611
  end
607
612
  alias size count
608
613
 
614
+ # returns a paginated dataset. The resulting dataset also provides the
615
+ # total number of pages (Dataset#page_count) and the current page number
616
+ # (Dataset#current_page), as well as Dataset#prev_page and Dataset#next_page
617
+ # for implementing pagination controls.
618
+ def paginate(page_no, page_size)
619
+ total_pages = (count / page_size.to_f).ceil
620
+ paginated = limit(page_size, (page_no - 1) * page_size)
621
+ paginated.current_page = page_no
622
+ paginated.page_count = total_pages
623
+ paginated
624
+ end
625
+
626
+ attr_accessor :page_count, :current_page
627
+
628
+ # Returns the previous page number or nil if the current page is the first
629
+ def prev_page
630
+ current_page > 1 ? (current_page - 1) : nil
631
+ end
632
+
633
+ # Returns the next page number or nil if the current page is the last page
634
+ def next_page
635
+ current_page < page_count ? (current_page + 1) : nil
636
+ end
637
+
609
638
  # Returns a table reference for use in the FROM clause. If the dataset has
610
639
  # only a :from option refering to a single table, only the table name is
611
640
  # returned. Otherwise a subquery is returned.
@@ -660,35 +689,47 @@ module Sequel
660
689
 
661
690
  # Returns the first record in the dataset. If the num argument is specified,
662
691
  # an array is returned with the first <i>num</i> records.
663
- def first(num = 1)
664
- if num == 1
665
- single_record
692
+ def first(*args)
693
+ args = args.empty? ? 1 : (args.size == 1) ? args.first : args
694
+ case args
695
+ when 1: single_record(:limit => 1)
696
+ when Fixnum: limit(args).all
666
697
  else
667
- limit(num).all
698
+ filter(args).single_record(:limit => 1)
668
699
  end
669
700
  end
670
701
 
671
702
  # Returns the first record matching the condition.
672
703
  def [](*conditions)
673
- where(*conditions).first
704
+ first(*conditions)
705
+ end
706
+
707
+ def []=(conditions, values)
708
+ filter(conditions).update(values)
674
709
  end
675
710
 
676
711
  # Returns the last records in the dataset by inverting the order. If no
677
712
  # order is given, an exception is raised. If num is not given, the last
678
713
  # record is returned. Otherwise an array is returned with the last
679
714
  # <i>num</i> records.
680
- def last(num = 1)
715
+ def last(*args)
681
716
  raise SequelError, 'No order specified' unless
682
717
  @opts[:order] || (opts && opts[:order])
683
718
 
684
- l = {:limit => num}
685
- opts = {:order => invert_order(@opts[:order])}.
686
- merge(opts ? opts.merge(l) : l)
687
-
688
- if num == 1
689
- single_record(opts)
719
+ args = args.empty? ? 1 : (args.size == 1) ? args.first : args
720
+
721
+ case args
722
+ when Fixnum:
723
+ l = {:limit => args}
724
+ opts = {:order => invert_order(@opts[:order])}. \
725
+ merge(opts ? opts.merge(l) : l)
726
+ if args == 1
727
+ single_record(opts)
728
+ else
729
+ dup_merge(opts).all
730
+ end
690
731
  else
691
- dup_merge(opts).all
732
+ filter(args).last(1)
692
733
  end
693
734
  end
694
735
 
@@ -709,33 +750,3 @@ module Sequel
709
750
  end
710
751
  end
711
752
 
712
- class Symbol
713
- def DESC
714
- "#{to_s} DESC"
715
- end
716
-
717
- def AS(target)
718
- "#{to_field_name} AS #{target}"
719
- end
720
-
721
- def MIN; "min(#{to_field_name})"; end
722
- def MAX; "max(#{to_field_name})"; end
723
- def SUM; "sum(#{to_field_name})"; end
724
- def AVG; "avg(#{to_field_name})"; end
725
-
726
- AS_REGEXP = /(.*)___(.*)/.freeze
727
- DOUBLE_UNDERSCORE = '__'.freeze
728
- PERIOD = '.'.freeze
729
-
730
- def to_field_name
731
- s = to_s
732
- if s =~ AS_REGEXP
733
- s = "#{$1} AS #{$2}"
734
- end
735
- s.split(DOUBLE_UNDERSCORE).join(PERIOD)
736
- end
737
-
738
- def ALL
739
- "#{to_s}.*"
740
- end
741
- end
@@ -154,7 +154,7 @@ module Sequel
154
154
  end
155
155
 
156
156
  def self.after_destroy(&block)
157
- get_hooks(:after_destroy).unshift(block)
157
+ get_hooks(:after_destroy) << block
158
158
  end
159
159
 
160
160
  def self.find(cond)
@@ -194,17 +194,6 @@ module Sequel
194
194
  self
195
195
  end
196
196
 
197
- # def self.each(&block); dataset.each(&block); end
198
- # def self.all; dataset.all; end
199
- # def self.filter(*arg, &block); dataset.filter(*arg, &block); end
200
- # def self.exclude(*arg, &block); dataset.exclude(*arg, &block); end
201
- # def self.order(*arg); dataset.order(*arg); end
202
- # def self.first(*arg); dataset.first(*arg); end
203
- # def self.count; dataset.count; end
204
- # def self.map(*arg, &block); dataset.map(*arg, &block); end
205
- # def self.hash_column(column); dataset.hash_column(primary_key, column); end
206
- # def self.join(*args); dataset.join(*args); end
207
- # def self.lock(mode, &block); dataset.lock(mode, &block); end
208
197
  def self.destroy_all
209
198
  has_hooks?(:before_destroy) ? dataset.destroy : dataset.delete
210
199
  end
@@ -222,6 +211,7 @@ module Sequel
222
211
  db.transaction do
223
212
  run_hooks(:before_destroy)
224
213
  delete
214
+ run_hooks(:after_destroy)
225
215
  end
226
216
  end
227
217
 
@@ -252,6 +242,11 @@ module Sequel
252
242
  respond_to?(m) ? send(m, *args, &block) : super(m, *args)
253
243
  end
254
244
 
245
+ def self.join(*args)
246
+ table_name = dataset.opts[:from].first
247
+ dataset.join(*args).select(table_name.to_sym.ALL)
248
+ end
249
+
255
250
  def db; self.class.db; end
256
251
 
257
252
  def [](field); @values[field]; end
@@ -41,9 +41,17 @@ class PGconn
41
41
 
42
42
  def last_insert_id(table)
43
43
  @table_sequences ||= {}
44
- seq = @table_sequences[table] ||= pkey_and_sequence(table)[1]
45
- r = async_query(SELECT_CURRVAL % seq)
46
- r[0][0].to_i unless r.nil? || r.empty?
44
+ if !@table_sequences.include?(table)
45
+ pkey_and_seq = pkey_and_sequence(table)
46
+ if pkey_and_seq
47
+ @table_sequences[table] = pkey_and_seq[1]
48
+ end
49
+ end
50
+ if seq = @table_sequences[table]
51
+ r = async_query(SELECT_CURRVAL % seq)
52
+ return r[0][0].to_i unless r.nil? || r.empty?
53
+ end
54
+ nil # primary key sequence not found
47
55
  end
48
56
 
49
57
  # Shamelessly appropriated from ActiveRecord's Postgresql adapter.
@@ -74,13 +82,31 @@ class PGconn
74
82
  AND cons.contype = 'p'
75
83
  AND def.adsrc ~* 'nextval'
76
84
  end_sql
85
+
86
+ SELECT_PK = <<-end_sql
87
+ SELECT pg_attribute.attname
88
+ FROM pg_class, pg_attribute, pg_index
89
+ WHERE pg_class.oid = pg_attribute.attrelid AND
90
+ pg_class.oid = pg_index.indrelid AND
91
+ pg_index.indkey[0] = pg_attribute.attnum AND
92
+ pg_index.indisprimary = 't' AND
93
+ pg_class.relname = '%s'
94
+ end_sql
77
95
 
78
96
  def pkey_and_sequence(table)
79
97
  r = async_query(SELECT_PK_AND_SERIAL_SEQUENCE % table)
80
98
  return [r[0].first, r[0].last] unless r.nil? or r.empty?
81
99
 
82
100
  r = async_query(SELECT_PK_AND_CUSTOM_SEQUENCE % table)
83
- return [r.first, r.last] unless r.nil? or r.empty?
101
+ return [r[0].first, r[0].last] unless r.nil? or r.empty?
102
+ rescue
103
+ nil
104
+ end
105
+
106
+ def primary_key(table)
107
+ r = async_query(SELECT_PK % table)
108
+ pkey = r[0].first unless r.nil? or r.empty?
109
+ return pkey.to_sym if pkey
84
110
  rescue
85
111
  nil
86
112
  end
@@ -164,11 +190,42 @@ module Sequel
164
190
  raise e
165
191
  end
166
192
 
167
- def execute_insert(sql, table)
193
+ def primary_key_for_table(conn, table)
194
+ @primary_keys ||= {}
195
+ @primary_keys[table] ||= conn.primary_key(table)
196
+ end
197
+
198
+ RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session/.freeze
199
+
200
+ def insert_result(conn, table, values)
201
+ begin
202
+ result = conn.last_insert_id(table)
203
+ return result if result
204
+ rescue PGError => e
205
+ # An error could occur if the inserted values include a primary key
206
+ # value, while the primary key is serial.
207
+ if e.message =~ RE_CURRVAL_ERROR
208
+ raise SequelError, "Could not return primary key value for the inserted record. Are you specifying a primary key value for a serial primary key?"
209
+ else
210
+ raise e
211
+ end
212
+ end
213
+
214
+ case values
215
+ when Hash:
216
+ values[primary_key_for_table(conn, table)]
217
+ when Array:
218
+ values.first
219
+ else
220
+ nil
221
+ end
222
+ end
223
+
224
+ def execute_insert(sql, table, values)
168
225
  @logger.info(sql) if @logger
169
226
  @pool.hold do |conn|
170
227
  conn.execute(sql).clear
171
- conn.last_insert_id(table)
228
+ insert_result(conn, table, values)
172
229
  end
173
230
  rescue => e
174
231
  @logger.error(e.message) if @logger
@@ -314,7 +371,8 @@ module Sequel
314
371
  end
315
372
 
316
373
  def insert(*values)
317
- @db.execute_insert(insert_sql(*values), @opts[:from])
374
+ @db.execute_insert(insert_sql(*values), @opts[:from],
375
+ values.size == 1 ? values.first : values)
318
376
  end
319
377
 
320
378
  def update(values, opts = nil)
@@ -75,22 +75,16 @@ module Sequel
75
75
  end
76
76
 
77
77
  def insert(*values)
78
- @db.synchronize do
79
- @db.execute_insert insert_sql(*values)
80
- end
78
+ @db.execute_insert insert_sql(*values)
81
79
  end
82
80
 
83
81
  def update(values, opts = nil)
84
- @db.synchronize do
85
- @db.execute update_sql(values, opts)
86
- end
82
+ @db.execute update_sql(values, opts)
87
83
  self
88
84
  end
89
85
 
90
86
  def delete(opts = nil)
91
- @db.synchronize do
92
- @db.execute delete_sql(opts)
93
- end
87
+ @db.execute delete_sql(opts)
94
88
  self
95
89
  end
96
90
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: sequel
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.6
7
- date: 2007-06-01 00:00:00 +03:00
6
+ version: 0.1.7
7
+ date: 2007-06-30 00:00:00 +03:00
8
8
  summary: Concise ORM for Ruby.
9
9
  require_paths:
10
10
  - lib