sequel 0.3.0.1 → 0.3.1
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 +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
|