sequel 0.1.6 → 0.1.7

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,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