sequel 0.0.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 +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
|