sequel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +13 -0
- data/COPYING +18 -0
- data/README +151 -0
- data/Rakefile +96 -0
- data/lib/sequel.rb +13 -0
- data/lib/sequel/connection_pool.rb +65 -0
- data/lib/sequel/core_ext.rb +9 -0
- data/lib/sequel/database.rb +119 -0
- data/lib/sequel/dataset.rb +357 -0
- data/lib/sequel/model.rb +237 -0
- data/lib/sequel/postgres.rb +396 -0
- data/lib/sequel/schema.rb +163 -0
- data/lib/sequel/sqlite.rb +112 -0
- data/spec/database_spec.rb +18 -0
- data/spec/dataset_spec.rb +124 -0
- data/spec/postgres_spec.rb +6 -0
- metadata +85 -0
@@ -0,0 +1,357 @@
|
|
1
|
+
module Sequel
|
2
|
+
# A Dataset represents a view of a the data in a database, constrained by
|
3
|
+
# specific parameters such as filtering conditions, order, etc. Datasets
|
4
|
+
# can be used to create, retrieve, update and delete records.
|
5
|
+
#
|
6
|
+
# Query results are always retrieved on demand, so a dataset can be kept
|
7
|
+
# around and reused indefinitely:
|
8
|
+
# my_posts = DB[:posts].filter(:author => 'david') # no records are retrieved
|
9
|
+
# p my_posts.all # records are now retrieved
|
10
|
+
# ...
|
11
|
+
# p my_posts.all # records are retrieved again
|
12
|
+
#
|
13
|
+
# In order to provide this functionality, dataset methods such as where,
|
14
|
+
# select, order, etc. return modified copies of the dataset, so you can
|
15
|
+
# use different datasets to access data:
|
16
|
+
# posts = DB[:posts]
|
17
|
+
# davids_posts = posts.filter(:author => 'david')
|
18
|
+
# old_posts = posts.filter('stamp < ?', 1.week.ago)
|
19
|
+
#
|
20
|
+
# Datasets are Enumerable objects, so they can be manipulated using any
|
21
|
+
# of the Enumerable methods, such as map, inject, etc.
|
22
|
+
class Dataset
|
23
|
+
include Enumerable
|
24
|
+
|
25
|
+
attr_reader :db
|
26
|
+
attr_accessor :record_class
|
27
|
+
|
28
|
+
# Constructs a new instance of a dataset with a database instance, initial
|
29
|
+
# options and an optional record class. Datasets are usually constructed by
|
30
|
+
# invoking Database methods:
|
31
|
+
# DB[:posts]
|
32
|
+
# Or:
|
33
|
+
# DB.dataset # the returned dataset is blank
|
34
|
+
#
|
35
|
+
# Sequel::Dataset is an abstract class that is not useful by itself. Each
|
36
|
+
# database adaptor should provide a descendant class of Sequel::Dataset.
|
37
|
+
def initialize(db, opts = {}, record_class = nil)
|
38
|
+
@db = db
|
39
|
+
@opts = opts || {}
|
40
|
+
@record_class = record_class
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a new instance of the dataset with its options
|
44
|
+
def dup_merge(opts)
|
45
|
+
self.class.new(@db, @opts.merge(opts), @record_class)
|
46
|
+
end
|
47
|
+
|
48
|
+
AS_REGEXP = /(.*)___(.*)/.freeze
|
49
|
+
AS_FORMAT = "%s AS %s".freeze
|
50
|
+
DOUBLE_UNDERSCORE = '__'.freeze
|
51
|
+
PERIOD = '.'.freeze
|
52
|
+
|
53
|
+
# Returns a valid SQL fieldname as a string. Field names specified as
|
54
|
+
# symbols can include double underscores to denote a dot separator, e.g.
|
55
|
+
# :posts__id will be converted into posts.id.
|
56
|
+
def field_name(field)
|
57
|
+
field.is_a?(Symbol) ? field.to_field_name : field
|
58
|
+
end
|
59
|
+
|
60
|
+
QUALIFIED_REGEXP = /(.*)\.(.*)/.freeze
|
61
|
+
QUALIFIED_FORMAT = "%s.%s".freeze
|
62
|
+
|
63
|
+
# Returns a qualified field name (including a table name) if the field
|
64
|
+
# name isn't already qualified.
|
65
|
+
def qualified_field_name(field, table)
|
66
|
+
fn = field_name(field)
|
67
|
+
fn = QUALIFIED_FORMAT % [table, fn] unless fn =~ QUALIFIED_REGEXP
|
68
|
+
end
|
69
|
+
|
70
|
+
WILDCARD = '*'.freeze
|
71
|
+
COMMA_SEPARATOR = ", ".freeze
|
72
|
+
|
73
|
+
# Converts a field list into a comma seperated string of field names.
|
74
|
+
def field_list(fields)
|
75
|
+
case fields
|
76
|
+
when Array:
|
77
|
+
if fields.empty?
|
78
|
+
WILDCARD
|
79
|
+
else
|
80
|
+
fields.map {|i| field_name(i)}.join(COMMA_SEPARATOR)
|
81
|
+
end
|
82
|
+
when Symbol:
|
83
|
+
fields.to_field_name
|
84
|
+
else
|
85
|
+
fields
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Converts an array of sources into a comma separated list.
|
90
|
+
def source_list(source)
|
91
|
+
case source
|
92
|
+
when Array: source.join(COMMA_SEPARATOR)
|
93
|
+
else source
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns a literal representation of a value to be used as part
|
98
|
+
# of an SQL expression. This method is overriden in descendants.
|
99
|
+
def literal(v)
|
100
|
+
case v
|
101
|
+
when String: "'%s'" % v
|
102
|
+
else v.to_s
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
AND_SEPARATOR = " AND ".freeze
|
107
|
+
EQUAL_COND = "(%s = %s)".freeze
|
108
|
+
|
109
|
+
# Formats an equality condition SQL expression.
|
110
|
+
def where_equal_condition(left, right)
|
111
|
+
EQUAL_COND % [field_name(left), literal(right)]
|
112
|
+
end
|
113
|
+
|
114
|
+
# Formats a where clause.
|
115
|
+
def where_list(where)
|
116
|
+
case where
|
117
|
+
when Hash:
|
118
|
+
where.map {|kv| where_equal_condition(kv[0], kv[1])}.join(AND_SEPARATOR)
|
119
|
+
when Array:
|
120
|
+
fmt = where.shift
|
121
|
+
fmt.gsub('?') {|i| literal(where.shift)}
|
122
|
+
else
|
123
|
+
where
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Formats a join condition.
|
128
|
+
def join_cond_list(cond, join_table)
|
129
|
+
cond.map do |kv|
|
130
|
+
EQUAL_COND % [
|
131
|
+
qualified_field_name(kv[0], join_table),
|
132
|
+
qualified_field_name(kv[1], @opts[:from])]
|
133
|
+
end.join(AND_SEPARATOR)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns a copy of the dataset with the source changed.
|
137
|
+
def from(source)
|
138
|
+
dup_merge(:from => source)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns a copy of the dataset with the selected fields changed.
|
142
|
+
def select(*fields)
|
143
|
+
fields = fields.first if fields.size == 1
|
144
|
+
dup_merge(:select => fields)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns a copy of the dataset with the order changed.
|
148
|
+
def order(*order)
|
149
|
+
dup_merge(:order => order)
|
150
|
+
end
|
151
|
+
|
152
|
+
DESC_ORDER_REGEXP = /(.*)\sDESC/.freeze
|
153
|
+
|
154
|
+
def reverse_order(order)
|
155
|
+
order.map do |f|
|
156
|
+
if f.to_s =~ DESC_ORDER_REGEXP
|
157
|
+
$1
|
158
|
+
else
|
159
|
+
f.DESC
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns a copy of the dataset with the where conditions changed.
|
165
|
+
def where(*where)
|
166
|
+
if where.size == 1
|
167
|
+
where = where.first
|
168
|
+
if @opts[:where] && @opts[:where].is_a?(Hash) && where.is_a?(Hash)
|
169
|
+
where = @opts[:where].merge(where)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
dup_merge(:where => where)
|
173
|
+
end
|
174
|
+
|
175
|
+
LEFT_OUTER_JOIN = 'LEFT OUTER JOIN'.freeze
|
176
|
+
INNER_JOIN = 'INNER JOIN'.freeze
|
177
|
+
RIGHT_OUTER_JOIN = 'RIGHT OUTER JOIN'.freeze
|
178
|
+
FULL_OUTER_JOIN = 'FULL OUTER JOIN'.freeze
|
179
|
+
|
180
|
+
def join(table, cond)
|
181
|
+
dup_merge(:join_type => LEFT_OUTER_JOIN, :join_table => table,
|
182
|
+
:join_cond => cond)
|
183
|
+
end
|
184
|
+
|
185
|
+
alias_method :filter, :where
|
186
|
+
alias_method :all, :to_a
|
187
|
+
alias_method :enum_map, :map
|
188
|
+
|
189
|
+
#
|
190
|
+
def map(field_name = nil, &block)
|
191
|
+
if block
|
192
|
+
enum_map(&block)
|
193
|
+
elsif field_name
|
194
|
+
enum_map {|r| r[field_name]}
|
195
|
+
else
|
196
|
+
[]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def hash_column(key_column, value_column)
|
201
|
+
inject({}) do |m, r|
|
202
|
+
m[r[key_column]] = r[value_column]
|
203
|
+
m
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def <<(values)
|
208
|
+
insert(values)
|
209
|
+
end
|
210
|
+
|
211
|
+
def insert_multiple(array, &block)
|
212
|
+
if block
|
213
|
+
array.each {|i| insert(block[i])}
|
214
|
+
else
|
215
|
+
array.each {|i| insert(i)}
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
SELECT = "SELECT %s FROM %s".freeze
|
220
|
+
LIMIT = " LIMIT %s".freeze
|
221
|
+
ORDER = " ORDER BY %s".freeze
|
222
|
+
WHERE = " WHERE %s".freeze
|
223
|
+
JOIN_CLAUSE = " %s %s ON %s".freeze
|
224
|
+
|
225
|
+
EMPTY = ''.freeze
|
226
|
+
|
227
|
+
SPACE = ' '.freeze
|
228
|
+
|
229
|
+
def select_sql(opts = nil)
|
230
|
+
opts = opts ? @opts.merge(opts) : @opts
|
231
|
+
|
232
|
+
fields = opts[:select]
|
233
|
+
select_fields = fields ? field_list(fields) : WILDCARD
|
234
|
+
select_source = source_list(opts[:from])
|
235
|
+
sql = SELECT % [select_fields, select_source]
|
236
|
+
|
237
|
+
if join_type = opts[:join_type]
|
238
|
+
join_table = opts[:join_table]
|
239
|
+
join_cond = join_cond_list(opts[:join_cond], join_table)
|
240
|
+
sql << (JOIN_CLAUSE % [join_type, join_table, join_cond])
|
241
|
+
end
|
242
|
+
|
243
|
+
if where = opts[:where]
|
244
|
+
sql << (WHERE % where_list(where))
|
245
|
+
end
|
246
|
+
|
247
|
+
if order = opts[:order]
|
248
|
+
sql << (ORDER % order.join(COMMA_SEPARATOR))
|
249
|
+
end
|
250
|
+
|
251
|
+
if limit = opts[:limit]
|
252
|
+
sql << (LIMIT % limit)
|
253
|
+
end
|
254
|
+
|
255
|
+
sql
|
256
|
+
end
|
257
|
+
|
258
|
+
INSERT = "INSERT INTO %s (%s) VALUES (%s)".freeze
|
259
|
+
INSERT_EMPTY = "INSERT INTO %s DEFAULT VALUES".freeze
|
260
|
+
|
261
|
+
def insert_sql(values, opts = nil)
|
262
|
+
opts = opts ? @opts.merge(opts) : @opts
|
263
|
+
|
264
|
+
if values.nil? || values.empty?
|
265
|
+
INSERT_EMPTY % opts[:from]
|
266
|
+
else
|
267
|
+
field_list = []
|
268
|
+
value_list = []
|
269
|
+
values.each do |k, v|
|
270
|
+
field_list << k
|
271
|
+
value_list << literal(v)
|
272
|
+
end
|
273
|
+
|
274
|
+
INSERT % [
|
275
|
+
opts[:from],
|
276
|
+
field_list.join(COMMA_SEPARATOR),
|
277
|
+
value_list.join(COMMA_SEPARATOR)]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
UPDATE = "UPDATE %s SET %s".freeze
|
282
|
+
SET_FORMAT = "%s = %s".freeze
|
283
|
+
|
284
|
+
def update_sql(values, opts = nil)
|
285
|
+
opts = opts ? @opts.merge(opts) : @opts
|
286
|
+
|
287
|
+
set_list = values.map {|kv| SET_FORMAT % [kv[0], literal(kv[1])]}.
|
288
|
+
join(COMMA_SEPARATOR)
|
289
|
+
update_clause = UPDATE % [opts[:from], set_list]
|
290
|
+
|
291
|
+
where = opts[:where]
|
292
|
+
where_clause = where ? WHERE % where_list(where) : EMPTY
|
293
|
+
|
294
|
+
[update_clause, where_clause].join(SPACE)
|
295
|
+
end
|
296
|
+
|
297
|
+
DELETE = "DELETE FROM %s".freeze
|
298
|
+
|
299
|
+
def delete_sql(opts = nil)
|
300
|
+
opts = opts ? @opts.merge(opts) : @opts
|
301
|
+
|
302
|
+
delete_source = opts[:from]
|
303
|
+
|
304
|
+
where = opts[:where]
|
305
|
+
where_clause = where ? WHERE % where_list(where) : EMPTY
|
306
|
+
|
307
|
+
[DELETE % delete_source, where_clause].join(SPACE)
|
308
|
+
end
|
309
|
+
|
310
|
+
COUNT = "COUNT(*)".freeze
|
311
|
+
SELECT_COUNT = {:select => COUNT, :order => nil}.freeze
|
312
|
+
|
313
|
+
def count_sql(opts = nil)
|
314
|
+
select_sql(opts ? opts.merge(SELECT_COUNT) : SELECT_COUNT)
|
315
|
+
end
|
316
|
+
|
317
|
+
# aggregates
|
318
|
+
def min(field)
|
319
|
+
select(field.MIN).first[:min]
|
320
|
+
end
|
321
|
+
|
322
|
+
def max(field)
|
323
|
+
select(field.MAX).first[:max]
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
class Symbol
|
329
|
+
def DESC
|
330
|
+
"#{to_s} DESC"
|
331
|
+
end
|
332
|
+
|
333
|
+
def AS(target)
|
334
|
+
"#{field_name} AS #{target}"
|
335
|
+
end
|
336
|
+
|
337
|
+
def MIN; "MIN(#{to_field_name})"; end
|
338
|
+
def MAX; "MAX(#{to_field_name})"; end
|
339
|
+
|
340
|
+
AS_REGEXP = /(.*)___(.*)/.freeze
|
341
|
+
AS_FORMAT = "%s AS %s".freeze
|
342
|
+
DOUBLE_UNDERSCORE = '__'.freeze
|
343
|
+
PERIOD = '.'.freeze
|
344
|
+
|
345
|
+
def to_field_name
|
346
|
+
s = to_s
|
347
|
+
if s =~ AS_REGEXP
|
348
|
+
s = AS_FORMAT % [$1, $2]
|
349
|
+
end
|
350
|
+
s.split(DOUBLE_UNDERSCORE).join(PERIOD)
|
351
|
+
end
|
352
|
+
|
353
|
+
def ALL
|
354
|
+
"#{to_s}.*"
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
data/lib/sequel/model.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
#require 'rubygems'
|
2
|
+
require 'metaid'
|
3
|
+
|
4
|
+
module Sequel
|
5
|
+
class Model
|
6
|
+
@@db = nil
|
7
|
+
|
8
|
+
def self.db; @@db; end
|
9
|
+
def self.db=(db); @@db = db; end
|
10
|
+
|
11
|
+
def self.table_name; @table_name; end
|
12
|
+
def self.set_table_name(t); @table_name = t; end
|
13
|
+
|
14
|
+
def self.dataset
|
15
|
+
return @dataset if @dataset
|
16
|
+
if !table_name
|
17
|
+
raise RuntimeError, "Table name not specified for class #{self}."
|
18
|
+
elsif !db
|
19
|
+
raise RuntimeError, "No database connected."
|
20
|
+
end
|
21
|
+
@dataset = db[table_name]
|
22
|
+
@dataset.record_class = self
|
23
|
+
@dataset
|
24
|
+
end
|
25
|
+
def self.set_dataset(ds); @dataset = ds; @dataset.record_class = self; end
|
26
|
+
|
27
|
+
def self.cache_by(column, expiration)
|
28
|
+
@cache_column = column
|
29
|
+
|
30
|
+
prefix = "#{name}.#{column}."
|
31
|
+
define_method(:cache_key) do
|
32
|
+
prefix + @values[column].to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
define_method("find_by_#{column}".to_sym) do |arg|
|
36
|
+
key = cache_key
|
37
|
+
rec = CACHE[key]
|
38
|
+
if !rec
|
39
|
+
rec = find(column => arg)
|
40
|
+
CACHE.set(key, rec, expiration)
|
41
|
+
end
|
42
|
+
rec
|
43
|
+
end
|
44
|
+
|
45
|
+
alias_method :delete, :delete_and_invalidate_cache
|
46
|
+
alias_method :set, :set_and_update_cache
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.cache_column
|
50
|
+
@cache_column
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.primary_key; @primary_key ||= :id; end
|
54
|
+
def self.set_primary_key(k); @primary_key = k; end
|
55
|
+
|
56
|
+
def self.schema(name = nil, &block)
|
57
|
+
name ||= table_name
|
58
|
+
@schema = Schema::Generator.new(name, &block)
|
59
|
+
set_table_name name
|
60
|
+
if @schema.primary_key_name
|
61
|
+
set_primary_key @schema.primary_key_name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.table_exists?
|
66
|
+
db.table_exists?(table_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.create_table
|
70
|
+
db.execute get_schema.create_sql
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.drop_table
|
74
|
+
db.execute get_schema.drop_sql
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.recreate_table
|
78
|
+
drop_table if table_exists?
|
79
|
+
create_table
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.get_schema
|
83
|
+
@schema
|
84
|
+
end
|
85
|
+
|
86
|
+
ONE_TO_ONE_PROC = "proc {i = @values[:%s]; %s[i] if i}".freeze
|
87
|
+
ID_POSTFIX = "_id".freeze
|
88
|
+
FROM_DATASET = "db[%s]".freeze
|
89
|
+
|
90
|
+
def self.one_to_one(name, opts)
|
91
|
+
klass = opts[:class] ? opts[:class] : (FROM_DATASET % name.inspect)
|
92
|
+
key = opts[:key] || (name.to_s + ID_POSTFIX)
|
93
|
+
define_method name, &eval(ONE_TO_ONE_PROC % [key, klass])
|
94
|
+
end
|
95
|
+
|
96
|
+
ONE_TO_MANY_PROC = "proc {%s.filter(:%s => @pkey)}".freeze
|
97
|
+
ONE_TO_MANY_ORDER_PROC = "proc {%s.filter(:%s => @pkey).order(%s)}".freeze
|
98
|
+
def self.one_to_many(name, opts)
|
99
|
+
klass = opts[:class] ? opts[:class] :
|
100
|
+
(FROM_DATASET % (opts[:table] || name.inspect))
|
101
|
+
key = opts[:on]
|
102
|
+
order = opts[:order]
|
103
|
+
define_method name, &eval(
|
104
|
+
(order ? ONE_TO_MANY_ORDER_PROC : ONE_TO_MANY_PROC) %
|
105
|
+
[klass, key, order.inspect]
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.get_hooks(key)
|
110
|
+
@hooks ||= {}
|
111
|
+
@hooks[key] ||= []
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.has_hooks?(key)
|
115
|
+
!get_hooks(key).empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
def run_hooks(key)
|
119
|
+
self.class.get_hooks(key).each {|h| instance_eval(&h)}
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.before_delete(&block)
|
123
|
+
get_hooks(:before_delete).unshift(block)
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.after_create(&block)
|
127
|
+
get_hooks(:after_create) << block
|
128
|
+
end
|
129
|
+
|
130
|
+
############################################################################
|
131
|
+
|
132
|
+
attr_reader :values, :pkey
|
133
|
+
|
134
|
+
def model
|
135
|
+
self.class
|
136
|
+
end
|
137
|
+
|
138
|
+
def primary_key
|
139
|
+
model.primary_key
|
140
|
+
end
|
141
|
+
|
142
|
+
def initialize(values)
|
143
|
+
@values = values
|
144
|
+
@pkey = values[self.class.primary_key]
|
145
|
+
end
|
146
|
+
|
147
|
+
def exists?
|
148
|
+
model.filter(primary_key => @pkey).count == 1
|
149
|
+
end
|
150
|
+
|
151
|
+
def refresh
|
152
|
+
record = self.class.find(primary_key => @pkey)
|
153
|
+
record ? (@values = record.values) :
|
154
|
+
(raise RuntimeError, "Record not found")
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.find(cond)
|
159
|
+
dataset.filter(cond).first # || (raise RuntimeError, "Record not found.")
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.each(&block); dataset.each(&block); end
|
163
|
+
def self.all; dataset.all; end
|
164
|
+
def self.filter(*arg); dataset.filter(*arg); end
|
165
|
+
def self.first; dataset.first; end
|
166
|
+
def self.count; dataset.count; end
|
167
|
+
def self.map(column); dataset.map(column); end
|
168
|
+
def self.hash_column(column); dataset.hash_column(primary_key, column); end
|
169
|
+
def self.join(*args); dataset.join(*args); end
|
170
|
+
def self.lock(mode, &block); dataset.lock(mode, &block); end
|
171
|
+
def self.delete_all
|
172
|
+
if has_hooks?(:before_delete)
|
173
|
+
db.transaction {dataset.all.each {|r| r.delete}}
|
174
|
+
else
|
175
|
+
dataset.delete
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.[](key)
|
180
|
+
find key.is_a?(Hash) ? key : {primary_key => key}
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.create(values = nil)
|
184
|
+
db.transaction do
|
185
|
+
obj = find(primary_key => dataset.insert(values))
|
186
|
+
obj.run_hooks(:after_create)
|
187
|
+
obj
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def delete
|
192
|
+
db.transaction do
|
193
|
+
run_hooks(:before_delete)
|
194
|
+
model.dataset.filter(primary_key => @pkey).delete
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
FIND_BY_REGEXP = /^find_by_(.*)/.freeze
|
199
|
+
FILTER_BY_REGEXP = /^filter_by_(.*)/.freeze
|
200
|
+
|
201
|
+
def self.method_missing(m, *args)
|
202
|
+
Thread.exclusive do
|
203
|
+
method_name = m.to_s
|
204
|
+
if method_name =~ FIND_BY_REGEXP
|
205
|
+
c = $1
|
206
|
+
meta_def(method_name) {|arg| find(c => arg)}
|
207
|
+
send(m, *args) if respond_to?(m)
|
208
|
+
elsif method_name =~ FILTER_BY_REGEXP
|
209
|
+
c = $1
|
210
|
+
meta_def(method_name) {|arg| filter(c => arg)}
|
211
|
+
send(m, *args) if respond_to?(m)
|
212
|
+
else
|
213
|
+
super
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def db; @@db; end
|
219
|
+
|
220
|
+
def [](field); @values[field]; end
|
221
|
+
|
222
|
+
def ==(obj)
|
223
|
+
(obj.class == model) && (obj.pkey == @pkey)
|
224
|
+
end
|
225
|
+
|
226
|
+
def set(values)
|
227
|
+
model.dataset.filter(primary_key => @pkey).update(values)
|
228
|
+
@values.merge!(values)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.Model(table_name)
|
233
|
+
Class.new(Sequel::Model) do
|
234
|
+
meta_def(:inherited) {|c| c.set_table_name(table_name)}
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|