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.
@@ -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
+
@@ -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