sequel 0.2.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +76 -0
- data/Rakefile +1 -1
- data/lib/sequel.rb +1 -1
- data/lib/sequel/ado.rb +17 -0
- data/lib/sequel/array_keys.rb +233 -0
- data/lib/sequel/connection_pool.rb +14 -0
- data/lib/sequel/core_ext.rb +3 -3
- data/lib/sequel/database.rb +25 -7
- data/lib/sequel/dataset.rb +46 -15
- data/lib/sequel/dataset/convenience.rb +27 -2
- data/lib/sequel/dataset/sequelizer.rb +2 -2
- data/lib/sequel/dataset/sql.rb +49 -18
- data/lib/sequel/dbi.rb +17 -0
- data/lib/sequel/model.rb +276 -82
- data/lib/sequel/model/base.rb +41 -30
- data/lib/sequel/model/caching.rb +42 -0
- data/lib/sequel/model/hooks.rb +113 -27
- data/lib/sequel/model/record.rb +78 -21
- data/lib/sequel/model/relations.rb +5 -0
- data/lib/sequel/model/schema.rb +11 -1
- data/lib/sequel/mysql.rb +61 -17
- data/lib/sequel/odbc.rb +42 -1
- data/lib/sequel/postgres.rb +45 -0
- data/lib/sequel/pretty_table.rb +14 -11
- data/lib/sequel/schema/schema_generator.rb +9 -3
- data/lib/sequel/sqlite.rb +33 -1
- data/spec/adapters/mysql_spec.rb +69 -15
- data/spec/adapters/postgres_spec.rb +66 -12
- data/spec/adapters/sqlite_spec.rb +113 -1
- data/spec/array_keys_spec.rb +544 -0
- data/spec/connection_pool_spec.rb +83 -0
- data/spec/database_spec.rb +81 -2
- data/spec/dataset_spec.rb +227 -9
- data/spec/model_spec.rb +392 -68
- data/spec/schema_spec.rb +7 -0
- metadata +5 -2
data/lib/sequel/model/base.rb
CHANGED
@@ -1,36 +1,41 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Model
|
3
|
-
#
|
3
|
+
# Returns the database associated with the Model class.
|
4
4
|
def self.db
|
5
|
-
@db ||= (
|
6
|
-
|
5
|
+
@db ||= (superclass != Object) && superclass.db or
|
6
|
+
raise SequelError, "No database associated with #{self}"
|
7
7
|
end
|
8
8
|
|
9
|
-
#
|
10
|
-
def self.db=(db)
|
9
|
+
# Sets the database associated with the Model class.
|
10
|
+
def self.db=(db)
|
11
|
+
@db = db
|
12
|
+
end
|
11
13
|
|
12
|
-
#
|
14
|
+
# Called when a database is opened in order to automatically associate the
|
13
15
|
# first opened database with model classes.
|
14
16
|
def self.database_opened(db)
|
15
17
|
@db = db if self == Model && !@db
|
16
18
|
end
|
17
19
|
|
18
|
-
#
|
20
|
+
# Returns the dataset associated with the Model class.
|
19
21
|
def self.dataset
|
20
|
-
@dataset || super_dataset
|
22
|
+
@dataset || super_dataset or
|
23
|
+
raise SequelError, "No dataset associated with #{self}"
|
21
24
|
end
|
22
25
|
|
23
|
-
def self.super_dataset
|
24
|
-
if superclass
|
25
|
-
ds
|
26
|
-
end
|
26
|
+
def self.super_dataset # :nodoc:
|
27
|
+
superclass.dataset if superclass and superclass.respond_to? :dataset
|
27
28
|
end
|
28
29
|
|
30
|
+
# Returns the columns in the result set in their original order.
|
31
|
+
#
|
32
|
+
# See Dataset#columns for more information.
|
29
33
|
def self.columns
|
30
|
-
@columns ||= @dataset.columns
|
34
|
+
@columns ||= @dataset.columns or
|
35
|
+
raise SequelError, "Could not fetch columns for #{self}"
|
31
36
|
end
|
32
37
|
|
33
|
-
# Sets the dataset associated with the
|
38
|
+
# Sets the dataset associated with the Model class.
|
34
39
|
def self.set_dataset(ds)
|
35
40
|
@db = ds.db
|
36
41
|
@dataset = ds
|
@@ -38,42 +43,48 @@ module Sequel
|
|
38
43
|
@dataset.transform(@transform) if @transform
|
39
44
|
end
|
40
45
|
|
41
|
-
|
42
|
-
@model ||= self.class
|
43
|
-
end
|
44
|
-
|
45
|
-
# Returns the dataset assoiated with the object's model class.
|
46
|
+
# Returns the database assoiated with the object's Model class.
|
46
47
|
def db
|
47
48
|
@db ||= model.db
|
48
49
|
end
|
49
50
|
|
50
|
-
# Returns the dataset assoiated with the object's
|
51
|
+
# Returns the dataset assoiated with the object's Model class.
|
52
|
+
#
|
53
|
+
# See Dataset for more information.
|
51
54
|
def dataset
|
52
|
-
|
55
|
+
model.dataset
|
53
56
|
end
|
54
57
|
|
58
|
+
# Returns the columns associated with the object's Model class.
|
55
59
|
def columns
|
56
|
-
|
60
|
+
model.columns
|
57
61
|
end
|
58
62
|
|
59
|
-
|
60
|
-
:yaml => [proc {|v| YAML.load v if v}, proc {|v| v.to_yaml}],
|
61
|
-
:marshal => [proc {|v| Marshal.load(v) if v}, proc {|v| Marshal.dump(v)}]
|
62
|
-
}
|
63
|
-
|
63
|
+
# Serializes column with YAML or through marshalling.
|
64
64
|
def self.serialize(*columns)
|
65
65
|
format = columns.pop[:format] if Hash === columns.last
|
66
|
-
|
67
|
-
# add error handling here
|
66
|
+
format ||= :yaml
|
68
67
|
|
69
68
|
@transform = columns.inject({}) do |m, c|
|
70
|
-
m[c] =
|
69
|
+
m[c] = format
|
71
70
|
m
|
72
71
|
end
|
73
72
|
@dataset.transform(@transform) if @dataset
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
76
|
+
# Lets you create a Model class with its table name already set or reopen
|
77
|
+
# an existing Model.
|
78
|
+
#
|
79
|
+
# Makes given dataset inherited.
|
80
|
+
#
|
81
|
+
# === Example:
|
82
|
+
# class Comment < Sequel::Model(:comments)
|
83
|
+
# table_name # => :comments
|
84
|
+
#
|
85
|
+
# # ...
|
86
|
+
#
|
87
|
+
# end
|
77
88
|
def self.Model(source)
|
78
89
|
@models ||= {}
|
79
90
|
@models[source] ||= Class.new(Sequel::Model) do
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Sequel
|
2
|
+
class Model
|
3
|
+
def self.set_cache(store, opts = {})
|
4
|
+
@cache_store = store
|
5
|
+
if (ttl = opts[:ttl])
|
6
|
+
set_cache_ttl(ttl)
|
7
|
+
end
|
8
|
+
|
9
|
+
meta_def(:[]) do |*args|
|
10
|
+
if (args.size == 1) && (Hash === (h = args.first))
|
11
|
+
return dataset[h]
|
12
|
+
end
|
13
|
+
|
14
|
+
unless obj = @cache_store.get(cache_key_from_values(args))
|
15
|
+
obj = dataset[primary_key_hash((args.size == 1) ? args.first : args)]
|
16
|
+
@cache_store.set(cache_key_from_values(args), obj, cache_ttl)
|
17
|
+
end
|
18
|
+
obj
|
19
|
+
end
|
20
|
+
|
21
|
+
class_def(:set) {|v| store.delete(cache_key); super}
|
22
|
+
class_def(:save) {store.delete(cache_key); super}
|
23
|
+
class_def(:delete) {store.delete(cache_key); super}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.set_cache_ttl(ttl)
|
27
|
+
@cache_ttl = ttl
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.cache_store
|
31
|
+
@cache_store
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.cache_ttl
|
35
|
+
@cache_ttl ||= 3600
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.cache_key_from_values(values)
|
39
|
+
"#{self}:#{values.join(',')}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/sequel/model/hooks.rb
CHANGED
@@ -1,40 +1,126 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Model
|
3
|
-
|
4
|
-
|
5
|
-
@hooks[key] ||= []
|
3
|
+
|
4
|
+
class ChainBroken < RuntimeError # :nodoc:
|
6
5
|
end
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
|
7
|
+
# This Hash translates verbs to methodnames used in chain manipulation
|
8
|
+
# methods.
|
9
|
+
VERB_TO_METHOD = {:prepend => :unshift, :append => :push}
|
10
|
+
|
11
|
+
# Returns @hooks which is an instance of Hash with its hook identifier
|
12
|
+
# (Symbol) as key and the chain of hooks (Array) as value.
|
13
|
+
#
|
14
|
+
# If it is not already set it'll be with an empty set of hooks.
|
15
|
+
# This behaviour will change in the future to allow inheritance.
|
16
|
+
#
|
17
|
+
# For the time being, you should be able to do:
|
18
|
+
#
|
19
|
+
# class A < Sequel::Model(:a)
|
20
|
+
# before_save { 'Do something...' }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class B < A
|
24
|
+
# @hooks = superclass.hooks.clone
|
25
|
+
# before_save # => [#<Proc:0x0000c6e8@(example.rb):123>]
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# In this case you should remember that the clone doesn't create any new
|
29
|
+
# instances of your chains, so if you change the chain here it changes in
|
30
|
+
# its superclass, too.
|
31
|
+
def self.hooks
|
32
|
+
@hooks ||= Hash.new { |h, k| h[k] = [] }
|
10
33
|
end
|
11
|
-
|
12
|
-
|
13
|
-
|
34
|
+
|
35
|
+
# Adds block to chain of Hooks for <tt>:before_save</tt>.
|
36
|
+
# It can either be prepended (default) or appended.
|
37
|
+
#
|
38
|
+
# Returns the chain itself.
|
39
|
+
#
|
40
|
+
# Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
|
41
|
+
def self.before_save(verb = :prepend, &block)
|
42
|
+
hooks[:before_save].send VERB_TO_METHOD.fetch(verb), block if block
|
43
|
+
hooks[:before_save]
|
14
44
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
45
|
+
# Adds block to chain of Hooks for <tt>:before_create</tt>.
|
46
|
+
# It can either be prepended (default) or appended.
|
47
|
+
#
|
48
|
+
# Returns the chain itself.
|
49
|
+
#
|
50
|
+
# Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
|
51
|
+
def self.before_create(verb = :prepend, &block)
|
52
|
+
hooks[:before_create].send VERB_TO_METHOD.fetch(verb), block if block
|
53
|
+
hooks[:before_create]
|
18
54
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
55
|
+
# Adds block to chain of Hooks for <tt>:before_update</tt>.
|
56
|
+
# It can either be prepended (default) or appended.
|
57
|
+
#
|
58
|
+
# Returns the chain itself.
|
59
|
+
#
|
60
|
+
# Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
|
61
|
+
def self.before_update(verb = :prepend, &block)
|
62
|
+
hooks[:before_update].send VERB_TO_METHOD.fetch(verb), block if block
|
63
|
+
hooks[:before_update]
|
22
64
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
65
|
+
# Adds block to chain of Hooks for <tt>:before_destroy</tt>.
|
66
|
+
# It can either be prepended (default) or appended.
|
67
|
+
#
|
68
|
+
# Returns the chain itself.
|
69
|
+
#
|
70
|
+
# Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
|
71
|
+
def self.before_destroy(verb = :prepend, &block)
|
72
|
+
hooks[:before_destroy].send VERB_TO_METHOD.fetch(verb), block if block
|
73
|
+
hooks[:before_destroy]
|
26
74
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
75
|
+
|
76
|
+
# Adds block to chain of Hooks for <tt>:after_save</tt>.
|
77
|
+
# It can either be prepended or appended (default).
|
78
|
+
#
|
79
|
+
# Returns the chain itself.
|
80
|
+
#
|
81
|
+
# Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
|
82
|
+
def self.after_save(verb = :append, &block)
|
83
|
+
hooks[:after_save].send VERB_TO_METHOD.fetch(verb), block if block
|
84
|
+
hooks[:after_save]
|
30
85
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
86
|
+
# Adds block to chain of Hooks for <tt>:after_create</tt>.
|
87
|
+
# It can either be prepended or appended (default).
|
88
|
+
#
|
89
|
+
# Returns the chain itself.
|
90
|
+
#
|
91
|
+
# Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
|
92
|
+
def self.after_create(verb = :append, &block)
|
93
|
+
hooks[:after_create].send VERB_TO_METHOD.fetch(verb), block if block
|
94
|
+
hooks[:after_create]
|
95
|
+
end
|
96
|
+
# Adds block to chain of Hooks for <tt>:after_update</tt>.
|
97
|
+
# It can either be prepended or appended (default).
|
98
|
+
#
|
99
|
+
# Returns the chain itself.
|
100
|
+
#
|
101
|
+
# Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
|
102
|
+
def self.after_update(verb = :append, &block)
|
103
|
+
hooks[:after_update].send VERB_TO_METHOD.fetch(verb), block if block
|
104
|
+
hooks[:after_update]
|
105
|
+
end
|
106
|
+
# Adds block to chain of Hooks for <tt>:after_destroy</tt>.
|
107
|
+
# It can either be prepended or appended (default).
|
108
|
+
#
|
109
|
+
# Returns the chain itself.
|
110
|
+
#
|
111
|
+
# Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
|
112
|
+
def self.after_destroy(verb = :append, &block)
|
113
|
+
hooks[:after_destroy].send VERB_TO_METHOD.fetch(verb), block if block
|
114
|
+
hooks[:after_destroy]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Evaluates specified chain of Hooks through <tt>instance_eval</tt>.
|
118
|
+
def run_hooks(key)
|
119
|
+
model.hooks[key].each { |h| instance_eval &h }
|
34
120
|
end
|
35
121
|
|
36
|
-
def self.
|
37
|
-
|
122
|
+
def self.has_hooks?(key)
|
123
|
+
hooks[key] && !hooks[key].empty?
|
38
124
|
end
|
39
125
|
end
|
40
|
-
end
|
126
|
+
end
|
data/lib/sequel/model/record.rb
CHANGED
@@ -2,41 +2,75 @@ module Sequel
|
|
2
2
|
class Model
|
3
3
|
attr_reader :values
|
4
4
|
|
5
|
-
|
6
|
-
def self.
|
5
|
+
# Returns key for primary key.
|
6
|
+
def self.primary_key
|
7
|
+
:id
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns primary key attribute hash.
|
11
|
+
def self.primary_key_hash(value)
|
12
|
+
{:id => value}
|
13
|
+
end
|
7
14
|
|
8
|
-
|
15
|
+
# Sets primary key, regular and composite are possible.
|
16
|
+
#
|
17
|
+
# == Example:
|
18
|
+
# class Tagging < Sequel::Model(:taggins)
|
19
|
+
# # composite key
|
20
|
+
# set_primary_key :taggable_id, :tag_id
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class Person < Sequel::Model(:person)
|
24
|
+
# # regular key
|
25
|
+
# set_primary_key :person_id
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# <i>You can even set it to nil!</i>
|
29
|
+
def self.set_primary_key(*key)
|
9
30
|
# if k is nil, we go to no_primary_key
|
10
31
|
return no_primary_key unless key
|
11
32
|
|
33
|
+
# backwards compat
|
34
|
+
key = (key.length == 1) ? key[0] : key.flatten
|
35
|
+
|
12
36
|
# redefine primary_key
|
13
37
|
meta_def(:primary_key) {key}
|
14
38
|
|
15
|
-
|
39
|
+
unless key.is_a? Array # regular primary key
|
16
40
|
class_def(:this) do
|
17
|
-
@this ||=
|
18
|
-
@values.reject {|k, v| !key.include?(k)} \
|
19
|
-
).naked
|
41
|
+
@this ||= dataset.filter(key => @values[key]).limit(1).naked
|
20
42
|
end
|
21
|
-
|
22
|
-
key
|
23
|
-
|
24
|
-
else # regular key
|
25
|
-
class_def(:this) do
|
26
|
-
@this ||= self.class.dataset.filter(key => @values[key]).naked
|
43
|
+
class_def(:cache_key) do
|
44
|
+
pk = @values[key] || (raise SequelError, 'no primary key for this record')
|
45
|
+
@cache_key ||= "#{self.class}:#{pk}"
|
27
46
|
end
|
28
47
|
meta_def(:primary_key_hash) do |v|
|
29
48
|
{key => v}
|
30
49
|
end
|
50
|
+
else # composite key
|
51
|
+
exp_list = key.map {|k| "#{k.inspect} => @values[#{k.inspect}]"}
|
52
|
+
block = eval("proc {@this ||= self.class.dataset.filter(#{exp_list.join(',')}).limit(1).naked}")
|
53
|
+
class_def(:this, &block)
|
54
|
+
|
55
|
+
exp_list = key.map {|k| '#{@values[%s]}' % k.inspect}.join(',')
|
56
|
+
block = eval('proc {@cache_key ||= "#{self.class}:%s"}' % exp_list)
|
57
|
+
class_def(:cache_key, &block)
|
58
|
+
|
59
|
+
meta_def(:primary_key_hash) do |v|
|
60
|
+
key.inject({}) {|m, i| m[i] = v.shift; m}
|
61
|
+
end
|
31
62
|
end
|
32
63
|
end
|
33
64
|
|
34
|
-
def self.no_primary_key
|
65
|
+
def self.no_primary_key #:nodoc:
|
35
66
|
meta_def(:primary_key) {nil}
|
36
67
|
meta_def(:primary_key_hash) {|v| raise SequelError, "#{self} does not have a primary key"}
|
37
68
|
class_def(:this) {raise SequelError, "No primary key is associated with this model"}
|
69
|
+
class_def(:cache_key) {raise SequelError, "No primary key is associated with this model"}
|
38
70
|
end
|
39
71
|
|
72
|
+
# Creates new instance with values set to passed-in Hash ensuring that
|
73
|
+
# new? returns true.
|
40
74
|
def self.create(values = {})
|
41
75
|
db.transaction do
|
42
76
|
obj = new(values, true)
|
@@ -45,36 +79,55 @@ module Sequel
|
|
45
79
|
end
|
46
80
|
end
|
47
81
|
|
82
|
+
# Returns (naked) dataset bound to current instance.
|
48
83
|
def this
|
49
|
-
@this ||= self.class.dataset.filter(:id => @values[:id]).naked
|
84
|
+
@this ||= self.class.dataset.filter(:id => @values[:id]).limit(1).naked
|
50
85
|
end
|
51
86
|
|
52
|
-
#
|
87
|
+
# Returns a key unique to the underlying record for caching
|
88
|
+
def cache_key
|
89
|
+
pk = @values[:id] || (raise SequelError, 'no primary key for this record')
|
90
|
+
@cache_key ||= "#{self.class}:#{pk}"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns primary key column(s) for object's Model class.
|
53
94
|
def primary_key
|
54
95
|
@primary_key ||= self.class.primary_key
|
55
96
|
end
|
56
97
|
|
98
|
+
# Returns value for primary key.
|
57
99
|
def pkey
|
58
100
|
@pkey ||= @values[primary_key]
|
59
101
|
end
|
60
102
|
|
61
|
-
|
103
|
+
# Creates new instance with values set to passed-in Hash.
|
104
|
+
#
|
105
|
+
# This method guesses whether the record exists when
|
106
|
+
# <tt>new_record</tt> is set to false.
|
107
|
+
def initialize(values = {}, new_record = false)
|
62
108
|
@values = values
|
63
|
-
|
64
|
-
|
109
|
+
|
110
|
+
@new = new_record
|
111
|
+
unless @new # determine if it's a new record
|
65
112
|
pk = primary_key
|
113
|
+
# if there's no primary key for the model class, or
|
114
|
+
# @values doesn't contain a primary key value, then
|
115
|
+
# we regard this instance as new.
|
66
116
|
@new = (pk == nil) || (!(Array === pk) && !@values[pk])
|
67
117
|
end
|
68
118
|
end
|
69
119
|
|
120
|
+
# Returns true if the current instance represents a new record.
|
70
121
|
def new?
|
71
122
|
@new
|
72
123
|
end
|
73
124
|
|
125
|
+
# Returns true when current instance exists, false otherwise.
|
74
126
|
def exists?
|
75
127
|
this.count > 0
|
76
128
|
end
|
77
129
|
|
130
|
+
# Creates or updates dataset for Model and runs hooks.
|
78
131
|
def save
|
79
132
|
run_hooks(:before_save)
|
80
133
|
if @new
|
@@ -100,16 +153,19 @@ module Sequel
|
|
100
153
|
self
|
101
154
|
end
|
102
155
|
|
156
|
+
# Updates and saves values to database from the passed-in Hash.
|
103
157
|
def set(values)
|
104
158
|
this.update(values)
|
105
|
-
|
159
|
+
values.each {|k, v| @values[k] = v}
|
106
160
|
end
|
107
161
|
|
162
|
+
# Reloads values from database and returns self.
|
108
163
|
def refresh
|
109
164
|
@values = this.first || raise(SequelError, "Record not found")
|
110
165
|
self
|
111
166
|
end
|
112
167
|
|
168
|
+
# Like delete but runs hooks before and after delete.
|
113
169
|
def destroy
|
114
170
|
db.transaction do
|
115
171
|
run_hooks(:before_destroy)
|
@@ -118,6 +174,7 @@ module Sequel
|
|
118
174
|
end
|
119
175
|
end
|
120
176
|
|
177
|
+
# Deletes and returns self.
|
121
178
|
def delete
|
122
179
|
this.delete
|
123
180
|
self
|
@@ -125,7 +182,7 @@ module Sequel
|
|
125
182
|
|
126
183
|
ATTR_RE = /^([a-zA-Z_]\w*)(=)?$/.freeze
|
127
184
|
|
128
|
-
def method_missing(m, *args)
|
185
|
+
def method_missing(m, *args) #:nodoc:
|
129
186
|
if m.to_s =~ ATTR_RE
|
130
187
|
att = $1.to_sym
|
131
188
|
write = $2 == '='
|