sequel 0.2.1.1 → 0.3.0
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 +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 == '='
|