sequel 0.2.0.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +64 -0
- data/Rakefile +41 -1
- data/lib/sequel.rb +37 -37
- data/lib/sequel/core_ext.rb +3 -3
- data/lib/sequel/database.rb +8 -2
- data/lib/sequel/dataset.rb +103 -42
- data/lib/sequel/dataset/convenience.rb +46 -0
- data/lib/sequel/dataset/sequelizer.rb +34 -8
- data/lib/sequel/dataset/sql.rb +12 -8
- data/lib/sequel/model.rb +13 -237
- data/lib/sequel/model/base.rb +84 -0
- data/lib/sequel/model/hooks.rb +40 -0
- data/lib/sequel/model/record.rb +154 -0
- data/lib/sequel/model/relations.rb +26 -0
- data/lib/sequel/model/schema.rb +36 -0
- data/lib/sequel/mysql.rb +9 -7
- data/lib/sequel/postgres.rb +5 -4
- data/lib/sequel/schema/schema_generator.rb +1 -0
- data/lib/sequel/sqlite.rb +2 -2
- data/spec/core_ext_spec.rb +14 -0
- data/spec/database_spec.rb +12 -1
- data/spec/dataset_spec.rb +274 -0
- data/spec/model_spec.rb +286 -3
- data/spec/sequelizer_spec.rb +49 -9
- data/spec/spec_helper.rb +27 -5
- metadata +8 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,67 @@
|
|
1
|
+
=== 0.2.1 (2007-09-24)
|
2
|
+
|
3
|
+
* Added default implementation of Model.primary_key_hash.
|
4
|
+
|
5
|
+
* Fixed Sequel::Model() to set dataset for inherited classes.
|
6
|
+
|
7
|
+
* Rewrote Model.serialize to use Dataset#transform.
|
8
|
+
|
9
|
+
* Implemented Dataset#transform.
|
10
|
+
|
11
|
+
* Added gem spec for Windows (without ParseTree dependency).
|
12
|
+
|
13
|
+
* Added support for dynamic strings in Sequelizer (#49).
|
14
|
+
|
15
|
+
* Query branch merged into trunk.
|
16
|
+
|
17
|
+
* Implemented self-changing methods.
|
18
|
+
|
19
|
+
* Add support for ternary operator to Sequelizer.
|
20
|
+
|
21
|
+
* Fixed sequelizer to evaluate expressions if they don't involve symbols or literal strings.
|
22
|
+
|
23
|
+
* Added protection against using #each, #delete, #insert, #update inside query blocks.
|
24
|
+
|
25
|
+
* Improved Model#method_missing to deal with invalid attributes.
|
26
|
+
|
27
|
+
* Implemented Dataset#query.
|
28
|
+
|
29
|
+
* Added Dataset#group_by as alias for Dataset#group.
|
30
|
+
|
31
|
+
* Added Dataset#order_by as alias for Dataset#order.
|
32
|
+
|
33
|
+
* More model refactoring. Added support for composite keys.
|
34
|
+
|
35
|
+
* Added Dataset#empty? method (#46).
|
36
|
+
|
37
|
+
* Fixed Symbol#to_field_name to support names with numbers and upper-case characters (#45).
|
38
|
+
|
39
|
+
* Added install_no_doc rake task.
|
40
|
+
|
41
|
+
* Partial refactoring of model code.
|
42
|
+
|
43
|
+
* Refactored dataset-model association and added Dataset#set_row_filter method.
|
44
|
+
|
45
|
+
* Added support for case-sensitive regexps to mysql adapter.
|
46
|
+
|
47
|
+
* Changed mysql adapter to support encoding option as well.
|
48
|
+
|
49
|
+
* Added charset/encoding option to postgres adapter.
|
50
|
+
|
51
|
+
* Implemented Model.serialize (thanks Aman Gupta.)
|
52
|
+
|
53
|
+
* Changed Model.create to INSERT DEFAULT VALUES instead of (id) VALUES (null) (brings back #41.)
|
54
|
+
|
55
|
+
* Fixed Model.new to work without arguments.
|
56
|
+
|
57
|
+
* Added Model.no_primary_key method to allow models without primary keys.
|
58
|
+
|
59
|
+
* Added Model#this method (#42 thanks Duane Johnson).
|
60
|
+
|
61
|
+
* Fixed Dataset#insert_sql to use DEFAULT VALUES clause if argument is an empty hash.
|
62
|
+
|
63
|
+
* Fixed Model.create to work correctly when no argument is passed (#41).
|
64
|
+
|
1
65
|
=== 0.2.0.2 (2007-09-07)
|
2
66
|
|
3
67
|
* Dataset#insert can now accept subqueries.
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
|
|
6
6
|
include FileUtils
|
7
7
|
|
8
8
|
NAME = "sequel"
|
9
|
-
VERS = "0.2.
|
9
|
+
VERS = "0.2.1"
|
10
10
|
CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
|
11
11
|
RDOC_OPTS = ['--quiet', '--title', "Sequel: Concise ORM for Ruby",
|
12
12
|
"--opname", "index.html",
|
@@ -55,16 +55,51 @@ spec = Gem::Specification.new do |s|
|
|
55
55
|
s.bindir = "bin"
|
56
56
|
end
|
57
57
|
|
58
|
+
win_spec = Gem::Specification.new do |s|
|
59
|
+
s.name = NAME
|
60
|
+
s.version = VERS
|
61
|
+
s.platform = Gem::Platform::WIN32
|
62
|
+
s.has_rdoc = true
|
63
|
+
s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
|
64
|
+
s.rdoc_options += RDOC_OPTS +
|
65
|
+
['--exclude', '^(examples|extras)\/', '--exclude', 'lib/sequel.rb']
|
66
|
+
s.summary = "Lightweight ORM library for Ruby"
|
67
|
+
s.description = s.summary
|
68
|
+
s.author = "Sharon Rosner"
|
69
|
+
s.email = 'ciconia@gmail.com'
|
70
|
+
s.homepage = 'http://sequel.rubyforge.org'
|
71
|
+
s.executables = ['sequel']
|
72
|
+
|
73
|
+
s.add_dependency('metaid')
|
74
|
+
|
75
|
+
s.required_ruby_version = '>= 1.8.4'
|
76
|
+
|
77
|
+
s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib}/**/*")
|
78
|
+
|
79
|
+
s.require_path = "lib"
|
80
|
+
s.bindir = "bin"
|
81
|
+
end
|
82
|
+
|
58
83
|
Rake::GemPackageTask.new(spec) do |p|
|
59
84
|
p.need_tar = true
|
60
85
|
p.gem_spec = spec
|
61
86
|
end
|
62
87
|
|
88
|
+
Rake::GemPackageTask.new(win_spec) do |p|
|
89
|
+
p.need_tar = true
|
90
|
+
p.gem_spec = win_spec
|
91
|
+
end
|
92
|
+
|
63
93
|
task :install do
|
64
94
|
sh %{rake package}
|
65
95
|
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
66
96
|
end
|
67
97
|
|
98
|
+
task :install_no_docs do
|
99
|
+
sh %{rake package}
|
100
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
|
101
|
+
end
|
102
|
+
|
68
103
|
task :uninstall => [:clean] do
|
69
104
|
sh %{sudo gem uninstall #{NAME}}
|
70
105
|
end
|
@@ -83,6 +118,11 @@ Spec::Rake::SpecTask.new('spec') do |t|
|
|
83
118
|
t.rcov = true
|
84
119
|
end
|
85
120
|
|
121
|
+
desc "Run specs without coverage"
|
122
|
+
Spec::Rake::SpecTask.new('spec_no_cov') do |t|
|
123
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
124
|
+
end
|
125
|
+
|
86
126
|
desc "Run adapter specs without coverage"
|
87
127
|
Spec::Rake::SpecTask.new('spec_adapters') do |t|
|
88
128
|
t.spec_files = FileList['spec/adapters/*_spec.rb']
|
data/lib/sequel.rb
CHANGED
@@ -1,37 +1,37 @@
|
|
1
|
-
require 'metaid'
|
2
|
-
|
3
|
-
files = %w[
|
4
|
-
core_ext error connection_pool pretty_table
|
5
|
-
dataset migration model schema database
|
6
|
-
]
|
7
|
-
dir = File.join(File.dirname(__FILE__), 'sequel')
|
8
|
-
files.each {|f| require(File.join(dir, f))}
|
9
|
-
|
10
|
-
module Sequel #:nodoc:
|
11
|
-
class << self
|
12
|
-
# call-seq:
|
13
|
-
# Sequel::Database.connect(conn_string)
|
14
|
-
# Sequel.connect(conn_string)
|
15
|
-
# Sequel.open(conn_string)
|
16
|
-
#
|
17
|
-
# Creates a new database object based on the supplied connection string.
|
18
|
-
# The specified scheme determines the database class used, and the rest
|
19
|
-
# of the string specifies the connection options. For example:
|
20
|
-
# DB = Sequel.open 'sqlite:///blog.db'
|
21
|
-
def connect(*args)
|
22
|
-
Database.connect(*args)
|
23
|
-
end
|
24
|
-
|
25
|
-
alias_method :open, :connect
|
26
|
-
|
27
|
-
def single_threaded=(value)
|
28
|
-
Database.single_threaded = value
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class Object
|
34
|
-
def Sequel(*args)
|
35
|
-
Sequel.connect(*args)
|
36
|
-
end
|
37
|
-
end
|
1
|
+
require 'metaid'
|
2
|
+
|
3
|
+
files = %w[
|
4
|
+
core_ext error connection_pool pretty_table
|
5
|
+
dataset migration model schema database
|
6
|
+
]
|
7
|
+
dir = File.join(File.dirname(__FILE__), 'sequel')
|
8
|
+
files.each {|f| require(File.join(dir, f))}
|
9
|
+
|
10
|
+
module Sequel #:nodoc:
|
11
|
+
class << self
|
12
|
+
# call-seq:
|
13
|
+
# Sequel::Database.connect(conn_string)
|
14
|
+
# Sequel.connect(conn_string)
|
15
|
+
# Sequel.open(conn_string)
|
16
|
+
#
|
17
|
+
# Creates a new database object based on the supplied connection string.
|
18
|
+
# The specified scheme determines the database class used, and the rest
|
19
|
+
# of the string specifies the connection options. For example:
|
20
|
+
# DB = Sequel.open 'sqlite:///blog.db'
|
21
|
+
def connect(*args)
|
22
|
+
Database.connect(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :open, :connect
|
26
|
+
|
27
|
+
def single_threaded=(value)
|
28
|
+
Database.single_threaded = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Object
|
34
|
+
def Sequel(*args)
|
35
|
+
Sequel.connect(*args)
|
36
|
+
end
|
37
|
+
end
|
data/lib/sequel/core_ext.rb
CHANGED
@@ -106,9 +106,9 @@ class Symbol
|
|
106
106
|
include FieldCompositionMethods
|
107
107
|
|
108
108
|
|
109
|
-
FIELD_REF_RE1 = /^(
|
110
|
-
FIELD_REF_RE2 = /^(
|
111
|
-
FIELD_REF_RE3 = /^(
|
109
|
+
FIELD_REF_RE1 = /^(\w+)__(\w+)___(\w+)/.freeze
|
110
|
+
FIELD_REF_RE2 = /^(\w+)___(\w+)$/.freeze
|
111
|
+
FIELD_REF_RE3 = /^(\w+)__(\w+)$/.freeze
|
112
112
|
|
113
113
|
# Converts a symbol into a field name. This method supports underscore
|
114
114
|
# notation in order to express qualified (two underscores) and aliased
|
data/lib/sequel/database.rb
CHANGED
@@ -60,9 +60,15 @@ module Sequel
|
|
60
60
|
|
61
61
|
# Returns a blank dataset
|
62
62
|
def dataset
|
63
|
-
Sequel::Dataset.new(self)
|
63
|
+
ds = Sequel::Dataset.new(self)
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
|
+
# Converts a query block into a dataset. For more information see
|
67
|
+
# Dataset#query.
|
68
|
+
def query(&block)
|
69
|
+
dataset.query(&block)
|
70
|
+
end
|
71
|
+
|
66
72
|
# Returns a new dataset with the from method invoked. If a block is given,
|
67
73
|
# it is used as a filter on the dataset.
|
68
74
|
def from(*args, &block)
|
data/lib/sequel/dataset.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'time'
|
2
2
|
require 'date'
|
3
3
|
|
4
|
-
require File.join(File.dirname(__FILE__), 'dataset/sequelizer')
|
5
4
|
require File.join(File.dirname(__FILE__), 'dataset/sql')
|
5
|
+
require File.join(File.dirname(__FILE__), 'dataset/sequelizer')
|
6
6
|
require File.join(File.dirname(__FILE__), 'dataset/convenience')
|
7
7
|
|
8
8
|
module Sequel
|
@@ -86,6 +86,7 @@ module Sequel
|
|
86
86
|
def initialize(db, opts = nil)
|
87
87
|
@db = db
|
88
88
|
@opts = opts || {}
|
89
|
+
@row_proc = nil
|
89
90
|
end
|
90
91
|
|
91
92
|
# Returns a new instance of the dataset with with the give options merged.
|
@@ -154,7 +155,7 @@ module Sequel
|
|
154
155
|
def each(opts = nil, &block)
|
155
156
|
fetch_rows(select_sql(opts), &block)
|
156
157
|
end
|
157
|
-
|
158
|
+
|
158
159
|
# Returns the the model classes associated with the dataset as a hash.
|
159
160
|
def model_classes
|
160
161
|
@opts[:models]
|
@@ -220,17 +221,22 @@ module Sequel
|
|
220
221
|
when nil: # set_model(nil) => no
|
221
222
|
# no argument provided, so the dataset is denuded
|
222
223
|
@opts.merge!(:naked => true, :models => nil, :polymorphic_key => nil)
|
223
|
-
|
224
|
+
remove_row_proc
|
225
|
+
# extend_with_stock_each
|
224
226
|
when Class:
|
225
227
|
# isomorphic model
|
226
228
|
@opts.merge!(:naked => nil, :models => {nil => key}, :polymorphic_key => nil)
|
227
|
-
|
229
|
+
set_row_proc {|h| key.new(h, *args)}
|
228
230
|
extend_with_destroy
|
229
231
|
when Symbol:
|
230
232
|
# polymorphic model
|
231
233
|
hash = args.shift || raise(SequelError, "No class hash supplied for polymorphic model")
|
232
234
|
@opts.merge!(:naked => true, :models => hash, :polymorphic_key => key)
|
233
|
-
|
235
|
+
set_row_proc do |h|
|
236
|
+
c = hash[h[key]] || hash[nil] || \
|
237
|
+
raise(SequelError, "No matching model class for record (#{polymorphic_key} => #{h[polymorphic_key].inspect})")
|
238
|
+
c.new(h, *args)
|
239
|
+
end
|
234
240
|
extend_with_destroy
|
235
241
|
else
|
236
242
|
raise SequelError, "Invalid parameters specified"
|
@@ -238,41 +244,106 @@ module Sequel
|
|
238
244
|
self
|
239
245
|
end
|
240
246
|
|
241
|
-
|
242
|
-
#
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
247
|
+
# Overrides the each method to pass the values through a filter. The filter
|
248
|
+
# receives as argument a hash containing the column values for the current
|
249
|
+
# record. The filter should return a value which is then passed to the
|
250
|
+
# iterating block. In order to elucidate, here's a contrived example:
|
251
|
+
#
|
252
|
+
# dataset.set_row_proc {|h| h.merge(:xxx => 'yyy')}
|
253
|
+
# dataset.first[:xxx] #=> "yyy" # always!
|
254
|
+
#
|
255
|
+
def set_row_proc(&filter)
|
256
|
+
@row_proc = filter
|
257
|
+
update_each_method
|
258
|
+
end
|
259
|
+
|
260
|
+
# Removes the row making proc.
|
261
|
+
def remove_row_proc
|
262
|
+
@row_proc = nil
|
263
|
+
update_each_method
|
264
|
+
end
|
265
|
+
|
266
|
+
# Sets a value transform which is used to convert values loaded and saved
|
267
|
+
# to/from the database. The transform should be supplied as a hash. Each
|
268
|
+
# value in the hash should be an array containing two proc objects - one
|
269
|
+
# for transforming loaded values, and one for transforming saved values.
|
270
|
+
# The following example demonstrates how to store Ruby objects in a dataset
|
271
|
+
# using Marshal serialization:
|
272
|
+
#
|
273
|
+
# dataset.transform(:obj => [
|
274
|
+
# proc {|v| Marshal.load(v)},
|
275
|
+
# proc {|v| Marshal.dump(v)}
|
276
|
+
# ])
|
277
|
+
#
|
278
|
+
# dataset.insert_sql(:obj => 1234) #=>
|
279
|
+
# "INSERT INTO items (obj) VALUES ('\004\bi\002\322\004')"
|
280
|
+
#
|
281
|
+
def transform(t)
|
282
|
+
@transform = t
|
283
|
+
update_each_method
|
284
|
+
end
|
285
|
+
|
286
|
+
# Applies the value transform for data loaded from the database.
|
287
|
+
def transform_load(r)
|
288
|
+
r.inject({}) do |m, kv|
|
289
|
+
k, v = kv[0], kv[1]
|
290
|
+
m[k] = (tt = @transform[k]) ? tt[0][v] : v
|
291
|
+
m
|
253
292
|
end
|
254
|
-
|
293
|
+
#
|
294
|
+
# @transform.each do |k, tt|
|
295
|
+
# r[k] = tt[0][r[k]]
|
296
|
+
# end
|
297
|
+
# r
|
255
298
|
end
|
256
299
|
|
257
|
-
#
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
raise(SequelError, "No matching model class for record (#{polymorphic_key} => #{r[polymorphic_key].inspect})")
|
264
|
-
c.new(r, *args)
|
300
|
+
# Applies the value transform for data saved to the database.
|
301
|
+
def transform_save(r)
|
302
|
+
r.inject({}) do |m, kv|
|
303
|
+
k, v = kv[0], kv[1]
|
304
|
+
m[k] = (tt = @transform[k]) ? tt[1][v] : v
|
305
|
+
m
|
265
306
|
end
|
266
|
-
|
267
|
-
|
268
|
-
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
# Updates the each method according to whether @row_proc and @transform are
|
311
|
+
# set or not.
|
312
|
+
def update_each_method
|
313
|
+
# warning: ugly code generation ahead
|
314
|
+
if @row_proc && @transform
|
315
|
+
class << self
|
316
|
+
def each(opts = nil, &block)
|
317
|
+
if opts && opts[:naked]
|
318
|
+
fetch_rows(select_sql(opts)) {|r| block[transform_load(r)]}
|
319
|
+
else
|
320
|
+
fetch_rows(select_sql(opts)) {|r| block[@row_proc[transform_load(r)]]}
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
elsif @row_proc
|
325
|
+
class << self
|
326
|
+
def each(opts = nil, &block)
|
327
|
+
if opts && opts[:naked]
|
328
|
+
fetch_rows(select_sql(opts), &block)
|
329
|
+
else
|
330
|
+
fetch_rows(select_sql(opts)) {|r| block[@row_proc[r]]}
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
elsif @transform
|
335
|
+
class << self
|
336
|
+
def each(opts = nil, &block)
|
337
|
+
fetch_rows(select_sql(opts)) {|r| block[transform_load(r)]}
|
338
|
+
end
|
339
|
+
end
|
340
|
+
else
|
341
|
+
class << self
|
342
|
+
def each(opts = nil, &block)
|
269
343
|
fetch_rows(select_sql(opts), &block)
|
270
|
-
else
|
271
|
-
fetch_rows(select_sql(opts)) {|r| block.call(make_model_instance(r))}
|
272
344
|
end
|
273
345
|
end
|
274
346
|
end
|
275
|
-
extend(m)
|
276
347
|
end
|
277
348
|
|
278
349
|
# Extends the dataset with a destroy method, that calls destroy for each
|
@@ -287,16 +358,6 @@ module Sequel
|
|
287
358
|
end
|
288
359
|
end
|
289
360
|
end
|
290
|
-
|
291
|
-
# Restores the stock #each implementation.
|
292
|
-
def extend_with_stock_each
|
293
|
-
m = Module.new do
|
294
|
-
def each(opts = nil, &block)
|
295
|
-
fetch_rows(select_sql(opts), &block)
|
296
|
-
end
|
297
|
-
end
|
298
|
-
extend(m)
|
299
|
-
end
|
300
361
|
end
|
301
362
|
end
|
302
363
|
|