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.
- data/CHANGELOG +26 -0
- data/Rakefile +2 -2
- data/lib/sequel/dataset.rb +1 -0
- data/lib/sequel/dataset/sql.rb +8 -8
- data/lib/sequel/model.rb +205 -141
- data/lib/sequel/model/record.rb +41 -6
- data/lib/sequel/model/relations.rb +97 -21
- data/lib/sequel/mysql.rb +1 -0
- data/lib/sequel/oracle.rb +109 -0
- data/lib/sequel/postgres.rb +9 -2
- data/lib/sequel/schema/schema_sql.rb +4 -1
- data/spec/adapters/mysql_spec.rb +7 -0
- data/spec/adapters/sqlite_spec.rb +3 -3
- data/spec/array_keys_spec.rb +2 -2
- data/spec/connection_pool_spec.rb +1 -1
- data/spec/database_spec.rb +1 -1
- data/spec/dataset_spec.rb +20 -4
- data/spec/migration_spec.rb +1 -1
- data/spec/model_spec.rb +240 -32
- data/spec/schema_spec.rb +13 -0
- data/spec/spec_helper.rb +1 -0
- metadata +20 -20
data/lib/sequel/model/record.rb
CHANGED
@@ -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
|
-
|
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)
|
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
|
-
|
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
|
-
|
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 = (
|
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
|
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
|
-
#
|
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
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
@@ -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
|
data/lib/sequel/postgres.rb
CHANGED
@@ -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
|
-
|
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
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -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
|
-
|
268
|
-
|
269
|
-
|
267
|
+
['abc', 1.23],
|
268
|
+
['abc', 4.56],
|
269
|
+
['def', 7.89]
|
270
270
|
]
|
271
271
|
end
|
272
272
|
end
|
data/spec/array_keys_spec.rb
CHANGED
@@ -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
|
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
|
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
|
208
|
+
specify "should let five threads simultaneously access separate connections" do
|
209
209
|
cc = {}
|
210
210
|
threads = []
|
211
211
|
stop = nil
|