sequel 0.2.0.2 → 0.2.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 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.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
@@ -106,9 +106,9 @@ class Symbol
106
106
  include FieldCompositionMethods
107
107
 
108
108
 
109
- FIELD_REF_RE1 = /^([a-z_]+)__([a-z_]+)___([a-z_]+)/.freeze
110
- FIELD_REF_RE2 = /^([a-z_]+)___([a-z_]+)$/.freeze
111
- FIELD_REF_RE3 = /^([a-z_]+)__([a-z_]+)$/.freeze
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
@@ -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)
@@ -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
- extend_with_stock_each
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
- extend_with_model(key, *args)
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
- extend_with_polymorphic_model(key, hash, *args)
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
- private
242
- # Overrides the each method to convert records to model instances.
243
- def extend_with_model(c, *args)
244
- meta_def(:make_model_instance) {|r| c.new(r, *args)}
245
- m = Module.new do
246
- def each(opts = nil, &block)
247
- if opts && opts[:naked]
248
- fetch_rows(select_sql(opts), &block)
249
- else
250
- fetch_rows(select_sql(opts)) {|r| block.call(make_model_instance(r))}
251
- end
252
- end
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
- extend(m)
293
+ #
294
+ # @transform.each do |k, tt|
295
+ # r[k] = tt[0][r[k]]
296
+ # end
297
+ # r
255
298
  end
256
299
 
257
- # Overrides the each method to convert records to polymorphic model
258
- # instances. The model class is determined according to the value in the
259
- # key column.
260
- def extend_with_polymorphic_model(key, hash, *args)
261
- meta_def(:make_model_instance) do |r|
262
- c = hash[r[key]] || hash[nil] || \
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
- m = Module.new do
267
- def each(opts = nil, &block)
268
- if opts && opts[:naked]
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