sequel 0.3.0.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -28,7 +28,9 @@ module Sequel
28
28
  # <i>You can even set it to nil!</i>
29
29
  def self.set_primary_key(*key)
30
30
  # if k is nil, we go to no_primary_key
31
- return no_primary_key unless key
31
+ if key.empty? || (key.size == 1 && key.first == nil)
32
+ return no_primary_key
33
+ end
32
34
 
33
35
  # backwards compat
34
36
  key = (key.length == 1) ? key[0] : key.flatten
@@ -40,6 +42,12 @@ module Sequel
40
42
  class_def(:this) do
41
43
  @this ||= dataset.filter(key => @values[key]).limit(1).naked
42
44
  end
45
+ class_def(:pk) do
46
+ @pk ||= @values[key]
47
+ end
48
+ class_def(:pk_hash) do
49
+ @pk ||= {key => @values[key]}
50
+ end
43
51
  class_def(:cache_key) do
44
52
  pk = @values[key] || (raise SequelError, 'no primary key for this record')
45
53
  @cache_key ||= "#{self.class}:#{pk}"
@@ -52,6 +60,14 @@ module Sequel
52
60
  block = eval("proc {@this ||= self.class.dataset.filter(#{exp_list.join(',')}).limit(1).naked}")
53
61
  class_def(:this, &block)
54
62
 
63
+ exp_list = key.map {|k| "@values[#{k.inspect}]"}
64
+ block = eval("proc {@pk ||= [#{exp_list.join(',')}]}")
65
+ class_def(:pk, &block)
66
+
67
+ exp_list = key.map {|k| "#{k.inspect} => @values[#{k.inspect}]"}
68
+ block = eval("proc {@this ||= {#{exp_list.join(',')}}}")
69
+ class_def(:pk_hash, &block)
70
+
55
71
  exp_list = key.map {|k| '#{@values[%s]}' % k.inspect}.join(',')
56
72
  block = eval('proc {@cache_key ||= "#{self.class}:%s"}' % exp_list)
57
73
  class_def(:cache_key, &block)
@@ -65,7 +81,9 @@ module Sequel
65
81
  def self.no_primary_key #:nodoc:
66
82
  meta_def(:primary_key) {nil}
67
83
  meta_def(:primary_key_hash) {|v| raise SequelError, "#{self} does not have a primary key"}
68
- class_def(:this) {raise SequelError, "No primary key is associated with this model"}
84
+ class_def(:this) {raise SequelError, "No primary key is associated with this model"}
85
+ class_def(:pk) {raise SequelError, "No primary key is associated with this model"}
86
+ class_def(:pk_hash) {raise SequelError, "No primary key is associated with this model"}
69
87
  class_def(:cache_key) {raise SequelError, "No primary key is associated with this model"}
70
88
  end
71
89
 
@@ -97,7 +115,18 @@ module Sequel
97
115
 
98
116
  # Returns value for primary key.
99
117
  def pkey
100
- @pkey ||= @values[primary_key]
118
+ warn "Model#pkey is deprecated. Please use Model#pk instead."
119
+ @pkey ||= @values[self.class.primary_key]
120
+ end
121
+
122
+ # Returns the primary key value identifying the model instance. Stock implementation.
123
+ def pk
124
+ @pk ||= @values[:id]
125
+ end
126
+
127
+ # Returns a hash identifying the model instance. Stock implementation.
128
+ def pk_hash
129
+ @pk_hash ||= {:id => @values[:id]}
101
130
  end
102
131
 
103
132
  # Creates new instance with values set to passed-in Hash.
@@ -109,11 +138,11 @@ module Sequel
109
138
 
110
139
  @new = new_record
111
140
  unless @new # determine if it's a new record
112
- pk = primary_key
141
+ k = self.class.primary_key
113
142
  # if there's no primary key for the model class, or
114
143
  # @values doesn't contain a primary key value, then
115
144
  # we regard this instance as new.
116
- @new = (pk == nil) || (!(Array === pk) && !@values[pk])
145
+ @new = (k == nil) || (!(Array === k) && !@values[k])
117
146
  end
118
147
  end
119
148
 
@@ -187,8 +216,14 @@ module Sequel
187
216
  att = $1.to_sym
188
217
  write = $2 == '='
189
218
 
190
- # check wether the column is legal
219
+ # check whether the column is legal
191
220
  unless columns.include?(att)
221
+ # if read accessor and a value exists for the column, we return it
222
+ if !write && @values.has_key?(att)
223
+ return @values[att]
224
+ end
225
+
226
+ # otherwise, raise an error
192
227
  raise SequelError, "Invalid column (#{att.inspect}) for #{self}"
193
228
  end
194
229
 
@@ -1,31 +1,107 @@
1
- # TODO: refactoring...
2
1
  module Sequel
3
2
  class Model
4
-
5
- ONE_TO_ONE_PROC = "proc {i = @values[:%s]; %s[i] if i}".freeze
6
- ID_POSTFIX = "_id".freeze
7
- FROM_DATASET = "db[%s]".freeze
3
+ ID_POSTFIX = '_id'.freeze
8
4
 
9
- # Comprehensive description goes here!
5
+ # Creates a 1-1 relationship by defining an association method, e.g.:
6
+ #
7
+ # class Session < Sequel::Model(:sessions)
8
+ # end
9
+ #
10
+ # class Node < Sequel::Model(:nodes)
11
+ # one_to_one :producer, :from => Session
12
+ # # which is equivalent to
13
+ # def producer
14
+ # Session[producer_id] if producer_id
15
+ # end
16
+ # end
17
+ #
18
+ # You can also set the foreign key explicitly by including a :key option:
19
+ #
20
+ # one_to_one :producer, :from => Session, :key => :producer_id
21
+ #
22
+ # The one_to_one macro also creates a setter, which accepts nil, a hash or
23
+ # a model instance, e.g.:
24
+ #
25
+ # p = Producer[1234]
26
+ # node = Node[:path => '/']
27
+ # node.producer = p
28
+ # node.producer_id #=> 1234
29
+ #
10
30
  def self.one_to_one(name, opts)
11
- klass = opts[:class] ? opts[:class] : (FROM_DATASET % name.inspect)
12
- key = opts[:key] || (name.to_s + ID_POSTFIX)
13
- define_method name, &eval(ONE_TO_ONE_PROC % [key, klass])
31
+ # deprecation
32
+ if opts[:class]
33
+ warn "The :class option has been deprecated. Please use :from instead."
34
+ opts[:from] = opts[:class]
35
+ end
36
+
37
+ from = opts[:from]
38
+ from || (raise SequelError, "No association source defined (use :from option)")
39
+ key = opts[:key] || (name.to_s + ID_POSTFIX).to_sym
40
+
41
+ setter_name = "#{name}=".to_sym
42
+
43
+ case from
44
+ when Symbol
45
+ class_def(name) {(k = @values[key]) ? db[from][:id => k] : nil}
46
+ when Sequel::Dataset
47
+ class_def(name) {(k = @values[key]) ? from[:id => k] : nil}
48
+ else
49
+ class_def(name) {(k = @values[key]) ? from[k] : nil}
50
+ end
51
+ class_def(setter_name) do |v|
52
+ case v
53
+ when nil
54
+ set(key => nil)
55
+ when Sequel::Model
56
+ set(key => v.pk)
57
+ when Hash
58
+ set(key => v[:id])
59
+ end
60
+ end
61
+
62
+ # define_method name, &eval(ONE_TO_ONE_PROC % [key, from])
14
63
  end
15
64
 
16
- ONE_TO_MANY_PROC = "proc {%s.filter(:%s => pkey)}".freeze
17
- ONE_TO_MANY_ORDER_PROC = "proc {%s.filter(:%s => pkey).order(%s)}".freeze
18
-
19
- # Comprehensive description goes here!
65
+ # Creates a 1-N relationship by defining an association method, e.g.:
66
+ #
67
+ # class Book < Sequel::Model(:books)
68
+ # end
69
+ #
70
+ # class Author < Sequel::Model(:authors)
71
+ # one_to_many :books, :from => Book
72
+ # # which is equivalent to
73
+ # def books
74
+ # Book.filter(:author_id => id)
75
+ # end
76
+ # end
77
+ #
78
+ # You can also set the foreign key explicitly by including a :key option:
79
+ #
80
+ # one_to_many :books, :from => Book, :key => :author_id
81
+ #
20
82
  def self.one_to_many(name, opts)
21
- klass = opts[:class] ? opts[:class] :
22
- (FROM_DATASET % (opts[:table] || name.inspect))
23
- key = opts[:on]
24
- order = opts[:order]
25
- define_method name, &eval(
26
- (order ? ONE_TO_MANY_ORDER_PROC : ONE_TO_MANY_PROC) %
27
- [klass, key, order.inspect]
28
- )
83
+ # deprecation
84
+ if opts[:class]
85
+ warn "The :class option has been deprecated. Please use :from instead."
86
+ opts[:from] = opts[:class]
87
+ end
88
+ # deprecation
89
+ if opts[:on]
90
+ warn "The :on option has been deprecated. Please use :key instead."
91
+ opts[:key] = opts[:on]
92
+ end
93
+
94
+
95
+ from = opts[:from]
96
+ from || (raise SequelError, "No association source defined (use :from option)")
97
+ key = opts[:key] || (self.to_s + ID_POSTFIX).to_sym
98
+
99
+ case from
100
+ when Symbol
101
+ class_def(name) {db[from].filter(key => pk)}
102
+ else
103
+ class_def(name) {from.filter(key => pk)}
104
+ end
29
105
  end
30
106
  end
31
107
  end
data/lib/sequel/mysql.rb CHANGED
@@ -197,6 +197,7 @@ module Sequel
197
197
  def literal(v)
198
198
  case v
199
199
  when LiteralString: quoted_field_name(v)
200
+ when String: "'#{v.gsub(/'|\\/, '\&\&')}'"
200
201
  when true: TRUE
201
202
  when false: FALSE
202
203
  else
@@ -0,0 +1,109 @@
1
+ if !Object.const_defined?('Sequel')
2
+ require File.join(File.dirname(__FILE__), '../sequel')
3
+ end
4
+
5
+ require 'oci8'
6
+
7
+ module Sequel
8
+ module Oracle
9
+ class Database < Sequel::Database
10
+ set_adapter_scheme :oracle
11
+
12
+ # AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
13
+ #
14
+ # def auto_increment_sql
15
+ # AUTO_INCREMENT
16
+ # end
17
+ #
18
+ def connect
19
+ if @opts[:database]
20
+ dbname = @opts[:host] ? \
21
+ "//#{@opts[:host]}/#{@opts[:database]}" : @opts[:database]
22
+ else
23
+ dbname = @opts[:host]
24
+ end
25
+ conn = OCI8.new(@opts[:user], @opts[:password], dbname, @opts[:privilege])
26
+ conn.autocommit = true
27
+ conn.non_blocking = true
28
+ conn
29
+ end
30
+
31
+ def disconnect
32
+ @pool.disconnect {|c| c.logoff}
33
+ end
34
+
35
+ def dataset(opts = nil)
36
+ Oracle::Dataset.new(self, opts)
37
+ end
38
+
39
+ def execute(sql)
40
+ @logger.info(sql) if @logger
41
+ @pool.hold {|conn| conn.Execute(sql)}
42
+ end
43
+
44
+ alias_method :do, :execute
45
+ end
46
+
47
+ class Dataset < Sequel::Dataset
48
+ def literal(v)
49
+ case v
50
+ when Time: literal(v.iso8601)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def fetch_rows(sql, &block)
57
+ @db.synchronize do
58
+ cursor = @db.execute sql
59
+ begin
60
+ @columns = cursor.get_col_names.map {|c| c.to_sym}
61
+ while r = cursor.fetch
62
+ row = {}
63
+ r.each_with_index {|v, i| row[columns[i]] = v}
64
+ yield row
65
+ end
66
+ ensure
67
+ cursor.close
68
+ end
69
+ end
70
+ self
71
+ end
72
+
73
+ def hash_row(row)
74
+ @columns.inject({}) do |m, c|
75
+ m[c] = row.shift
76
+ m
77
+ end
78
+ end
79
+
80
+ def array_tuples_fetch_rows(sql, &block)
81
+ @db.synchronize do
82
+ cursor = @db.execute sql
83
+ begin
84
+ @columns = cursor.get_col_names.map {|c| c.to_sym}
85
+ while r = cursor.fetch
86
+ r.keys = columns
87
+ yield r
88
+ end
89
+ ensure
90
+ cursor.close
91
+ end
92
+ end
93
+ self
94
+ end
95
+
96
+ def insert(*values)
97
+ @db.do insert_sql(*values)
98
+ end
99
+
100
+ def update(values, opts = nil)
101
+ @db.do update_sql(values, opts)
102
+ end
103
+
104
+ def delete(opts = nil)
105
+ @db.do delete_sql(opts)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -137,6 +137,13 @@ module Sequel
137
137
  1114 => :to_time
138
138
  }
139
139
 
140
+ if PGconn.respond_to?(:translate_results=)
141
+ PGconn.translate_results = true
142
+ AUTO_TRANSLATE = true
143
+ else
144
+ AUTO_TRANSLATE = false
145
+ end
146
+
140
147
  class Database < Sequel::Database
141
148
  set_adapter_scheme :postgres
142
149
 
@@ -433,7 +440,7 @@ module Sequel
433
440
  next if used_fields.include?(field)
434
441
  used_fields << field
435
442
 
436
- if translator = translators[idx]
443
+ if !AUTO_TRANSLATE and translator = translators[idx]
437
444
  kvs << ":\"#{field}\" => ((t = r[#{idx}]) ? t.#{translator} : nil)"
438
445
  else
439
446
  kvs << ":\"#{field}\" => r[#{idx}]"
@@ -475,7 +482,7 @@ module Sequel
475
482
  def array_tuples_compile_converter(fields, translators)
476
483
  tr = []
477
484
  fields.each_with_index do |field, idx|
478
- if t = translators[idx]
485
+ if !AUTO_TRANSLATE and t = translators[idx]
479
486
  tr << "if (v = r[#{idx}]); r[#{idx}] = v.#{t}; end"
480
487
  end
481
488
  end
@@ -40,7 +40,10 @@ module Sequel
40
40
  sql << NOT_NULL if column[:null] == false
41
41
  sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
42
42
  sql << PRIMARY_KEY if column[:primary_key]
43
- sql << " REFERENCES #{column[:table]}" if column[:table]
43
+ if column[:table]
44
+ sql << " REFERENCES #{column[:table]}"
45
+ sql << "(#{column[:key]})" if column[:key]
46
+ end
44
47
  sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
45
48
  sql << " #{auto_increment_sql}" if column[:auto_increment]
46
49
  sql
@@ -134,6 +134,13 @@ context "A MySQL dataset" do
134
134
  @d.filter(:name => /bc/).count.should == 2
135
135
  @d.filter(:name => /^bc/).count.should == 1
136
136
  end
137
+
138
+ specify "should correctly literalize strings with comment backslashes in them" do
139
+ @d.delete
140
+ proc {@d << {:name => ':\\'}}.should_not raise_error
141
+
142
+ @d.first[:name].should == ':\\'
143
+ end
137
144
  end
138
145
 
139
146
  context "A MySQL dataset in array tuples mode" do
@@ -264,9 +264,9 @@ context "An SQLite dataset in array tuples mode" do
264
264
  @d << {:name => 'abc', :value => 4.56}
265
265
  @d << {:name => 'def', :value => 7.89}
266
266
  @d.select(:name, :value).to_a.sort_by {|h| h[:value]}.should == [
267
- Array.from_hash({:name => 'abc', :value => 1.23}),
268
- Array.from_hash({:name => 'abc', :value => 4.56}),
269
- Array.from_hash({:name => 'def', :value => 7.89})
267
+ ['abc', 1.23],
268
+ ['abc', 4.56],
269
+ ['def', 7.89]
270
270
  ]
271
271
  end
272
272
  end
@@ -28,7 +28,7 @@ context "An array with symbol keys" do
28
28
  @a.keys.should == [:a, :b, :c, :d]
29
29
  end
30
30
 
31
- specify "should provide key acess using strings" do
31
+ specify "should provide key access using strings" do
32
32
  @a['a'].should == 1
33
33
  @a['A'].should be_nil
34
34
 
@@ -280,7 +280,7 @@ context "An array with string keys" do
280
280
  @a.keys.should == ['a', 'b', 'c', :d]
281
281
  end
282
282
 
283
- specify "should provide key acess using strings" do
283
+ specify "should provide key access using strings" do
284
284
  @a['a'].should == 1
285
285
  @a['A'].should be_nil
286
286
 
@@ -205,7 +205,7 @@ context "A connection pool with a max size of 5" do
205
205
  @pool = Sequel::ConnectionPool.new(5) {@invoked_count += 1}
206
206
  end
207
207
 
208
- specify "should let five threads simulatneously access separate connections" do
208
+ specify "should let five threads simultaneously access separate connections" do
209
209
  cc = {}
210
210
  threads = []
211
211
  stop = nil