epugh-sequel 0.0.0
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/README.rdoc +652 -0
- data/VERSION.yml +4 -0
- data/bin/sequel +104 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +85 -0
- data/lib/sequel/adapters/db2.rb +132 -0
- data/lib/sequel/adapters/dbi.rb +101 -0
- data/lib/sequel/adapters/do.rb +197 -0
- data/lib/sequel/adapters/do/mysql.rb +38 -0
- data/lib/sequel/adapters/do/postgres.rb +92 -0
- data/lib/sequel/adapters/do/sqlite.rb +31 -0
- data/lib/sequel/adapters/firebird.rb +307 -0
- data/lib/sequel/adapters/informix.rb +75 -0
- data/lib/sequel/adapters/jdbc.rb +485 -0
- data/lib/sequel/adapters/jdbc/h2.rb +62 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
- data/lib/sequel/adapters/mysql.rb +370 -0
- data/lib/sequel/adapters/odbc.rb +184 -0
- data/lib/sequel/adapters/openbase.rb +57 -0
- data/lib/sequel/adapters/oracle.rb +140 -0
- data/lib/sequel/adapters/postgres.rb +453 -0
- data/lib/sequel/adapters/shared/mssql.rb +93 -0
- data/lib/sequel/adapters/shared/mysql.rb +341 -0
- data/lib/sequel/adapters/shared/oracle.rb +62 -0
- data/lib/sequel/adapters/shared/postgres.rb +743 -0
- data/lib/sequel/adapters/shared/progress.rb +34 -0
- data/lib/sequel/adapters/shared/sqlite.rb +263 -0
- data/lib/sequel/adapters/sqlite.rb +243 -0
- data/lib/sequel/adapters/utils/date_format.rb +21 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
- data/lib/sequel/adapters/utils/unsupported.rb +62 -0
- data/lib/sequel/connection_pool.rb +258 -0
- data/lib/sequel/core.rb +204 -0
- data/lib/sequel/core_sql.rb +185 -0
- data/lib/sequel/database.rb +687 -0
- data/lib/sequel/database/schema_generator.rb +324 -0
- data/lib/sequel/database/schema_methods.rb +164 -0
- data/lib/sequel/database/schema_sql.rb +324 -0
- data/lib/sequel/dataset.rb +422 -0
- data/lib/sequel/dataset/convenience.rb +237 -0
- data/lib/sequel/dataset/prepared_statements.rb +220 -0
- data/lib/sequel/dataset/sql.rb +1105 -0
- data/lib/sequel/deprecated.rb +529 -0
- data/lib/sequel/exceptions.rb +44 -0
- data/lib/sequel/extensions/blank.rb +42 -0
- data/lib/sequel/extensions/inflector.rb +288 -0
- data/lib/sequel/extensions/pagination.rb +96 -0
- data/lib/sequel/extensions/pretty_table.rb +78 -0
- data/lib/sequel/extensions/query.rb +48 -0
- data/lib/sequel/extensions/string_date_time.rb +47 -0
- data/lib/sequel/metaprogramming.rb +44 -0
- data/lib/sequel/migration.rb +212 -0
- data/lib/sequel/model.rb +142 -0
- data/lib/sequel/model/association_reflection.rb +263 -0
- data/lib/sequel/model/associations.rb +1024 -0
- data/lib/sequel/model/base.rb +911 -0
- data/lib/sequel/model/deprecated.rb +188 -0
- data/lib/sequel/model/deprecated_hooks.rb +103 -0
- data/lib/sequel/model/deprecated_inflector.rb +335 -0
- data/lib/sequel/model/deprecated_validations.rb +384 -0
- data/lib/sequel/model/errors.rb +37 -0
- data/lib/sequel/model/exceptions.rb +7 -0
- data/lib/sequel/model/inflections.rb +230 -0
- data/lib/sequel/model/plugins.rb +74 -0
- data/lib/sequel/object_graph.rb +230 -0
- data/lib/sequel/plugins/caching.rb +122 -0
- data/lib/sequel/plugins/hook_class_methods.rb +122 -0
- data/lib/sequel/plugins/schema.rb +53 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
- data/lib/sequel/plugins/validation_class_methods.rb +373 -0
- data/lib/sequel/sql.rb +854 -0
- data/lib/sequel/version.rb +11 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/ado_spec.rb +46 -0
- data/spec/adapters/firebird_spec.rb +376 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +875 -0
- data/spec/adapters/oracle_spec.rb +272 -0
- data/spec/adapters/postgres_spec.rb +692 -0
- data/spec/adapters/spec_helper.rb +10 -0
- data/spec/adapters/sqlite_spec.rb +550 -0
- data/spec/core/connection_pool_spec.rb +526 -0
- data/spec/core/core_ext_spec.rb +156 -0
- data/spec/core/core_sql_spec.rb +528 -0
- data/spec/core/database_spec.rb +1214 -0
- data/spec/core/dataset_spec.rb +3513 -0
- data/spec/core/expression_filters_spec.rb +363 -0
- data/spec/core/migration_spec.rb +261 -0
- data/spec/core/object_graph_spec.rb +280 -0
- data/spec/core/pretty_table_spec.rb +58 -0
- data/spec/core/schema_generator_spec.rb +167 -0
- data/spec/core/schema_spec.rb +778 -0
- data/spec/core/spec_helper.rb +82 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/caching_spec.rb +201 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/inflector_spec.rb +122 -0
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/extensions/schema_spec.rb +111 -0
- data/spec/extensions/single_table_inheritance_spec.rb +53 -0
- data/spec/extensions/spec_helper.rb +90 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/validation_class_methods_spec.rb +1054 -0
- data/spec/integration/dataset_test.rb +160 -0
- data/spec/integration/eager_loader_test.rb +683 -0
- data/spec/integration/prepared_statement_test.rb +130 -0
- data/spec/integration/schema_test.rb +183 -0
- data/spec/integration/spec_helper.rb +75 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +93 -0
- data/spec/model/associations_spec.rb +1780 -0
- data/spec/model/base_spec.rb +494 -0
- data/spec/model/caching_spec.rb +217 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1165 -0
- data/spec/model/hooks_spec.rb +472 -0
- data/spec/model/inflector_spec.rb +126 -0
- data/spec/model/model_spec.rb +588 -0
- data/spec/model/plugins_spec.rb +142 -0
- data/spec/model/record_spec.rb +1243 -0
- data/spec/model/schema_spec.rb +92 -0
- data/spec/model/spec_helper.rb +124 -0
- data/spec/model/validations_spec.rb +1080 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +202 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
class Dataset
|
|
3
|
+
# Pretty prints the records in the dataset as plain-text table.
|
|
4
|
+
def print(*cols)
|
|
5
|
+
Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module PrettyTable
|
|
10
|
+
# Prints nice-looking plain-text tables via puts
|
|
11
|
+
#
|
|
12
|
+
# +--+-------+
|
|
13
|
+
# |id|name |
|
|
14
|
+
# |--+-------|
|
|
15
|
+
# |1 |fasdfas|
|
|
16
|
+
# |2 |test |
|
|
17
|
+
# +--+-------+
|
|
18
|
+
def self.print(records, columns = nil) # records is an array of hashes
|
|
19
|
+
columns ||= records.first.keys.sort_by{|x|x.to_s}
|
|
20
|
+
sizes = column_sizes(records, columns)
|
|
21
|
+
sep_line = separator_line(columns, sizes)
|
|
22
|
+
|
|
23
|
+
puts sep_line
|
|
24
|
+
puts header_line(columns, sizes)
|
|
25
|
+
puts sep_line
|
|
26
|
+
records.each {|r| puts data_line(columns, sizes, r)}
|
|
27
|
+
puts sep_line
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
### Private Module Methods ###
|
|
31
|
+
|
|
32
|
+
# Hash of the maximum size of the value for each column
|
|
33
|
+
def self.column_sizes(records, columns) # :nodoc:
|
|
34
|
+
sizes = Hash.new {0}
|
|
35
|
+
columns.each do |c|
|
|
36
|
+
s = c.to_s.size
|
|
37
|
+
sizes[c.to_sym] = s if s > sizes[c.to_sym]
|
|
38
|
+
end
|
|
39
|
+
records.each do |r|
|
|
40
|
+
columns.each do |c|
|
|
41
|
+
s = r[c].to_s.size
|
|
42
|
+
sizes[c.to_sym] = s if s > sizes[c.to_sym]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
sizes
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# String for each data line
|
|
49
|
+
def self.data_line(columns, sizes, record) # :nodoc:
|
|
50
|
+
'|' << columns.map {|c| format_cell(sizes[c], record[c])}.join('|') << '|'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Format the value so it takes up exactly size characters
|
|
54
|
+
def self.format_cell(size, v) # :nodoc:
|
|
55
|
+
case v
|
|
56
|
+
when Bignum, Fixnum
|
|
57
|
+
"%#{size}d" % v
|
|
58
|
+
when Float
|
|
59
|
+
"%#{size}g" % v
|
|
60
|
+
else
|
|
61
|
+
"%-#{size}s" % v.to_s
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# String for header line
|
|
66
|
+
def self.header_line(columns, sizes) # :nodoc:
|
|
67
|
+
'|' << columns.map {|c| "%-#{sizes[c]}s" % c.to_s}.join('|') << '|'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# String for separtor line
|
|
71
|
+
def self.separator_line(columns, sizes) # :nodoc:
|
|
72
|
+
'+' << columns.map {|c| '-' * sizes[c]}.join('+') << '+'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private_class_method :column_sizes, :data_line, :format_cell, :header_line, :separator_line
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
class Database
|
|
3
|
+
# Return a dataset modified by the query block
|
|
4
|
+
def query(&block)
|
|
5
|
+
dataset.query(&block)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Dataset
|
|
10
|
+
# Translates a query block into a dataset. Query blocks can be useful
|
|
11
|
+
# when expressing complex SELECT statements, e.g.:
|
|
12
|
+
#
|
|
13
|
+
# dataset = DB[:items].query do
|
|
14
|
+
# select :x, :y, :z
|
|
15
|
+
# filter{|o| (o.x > 1) & (o.y > 2)}
|
|
16
|
+
# order :z.desc
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# Which is the same as:
|
|
20
|
+
#
|
|
21
|
+
# dataset = DB[:items].select(:x, :y, :z).filter{|o| (o.x > 1) & (o.y > 2)}.order(:z.desc)
|
|
22
|
+
#
|
|
23
|
+
# Note that inside a call to query, you cannot call each, insert, update,
|
|
24
|
+
# or delete (or any method that calls those), or Sequel will raise an
|
|
25
|
+
# error.
|
|
26
|
+
def query(&block)
|
|
27
|
+
copy = clone({})
|
|
28
|
+
copy.extend(QueryBlockCopy)
|
|
29
|
+
copy.instance_eval(&block)
|
|
30
|
+
clone(copy.opts)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Module used by Dataset#query that has the effect of making all
|
|
34
|
+
# dataset methods into !-style methods that modify the receiver.
|
|
35
|
+
module QueryBlockCopy
|
|
36
|
+
%w'each insert update delete'.each do |meth|
|
|
37
|
+
define_method(meth){|*args| raise Error, "##{meth} cannot be invoked inside a query block."}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Merge the given options into the receiver's options and return the receiver
|
|
41
|
+
# instead of cloning the receiver.
|
|
42
|
+
def clone(opts = nil)
|
|
43
|
+
@opts.merge!(opts)
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# This file contains the previous extensions to String for date/time
|
|
2
|
+
# conversions. These are provided mainly for backward compatibility,
|
|
3
|
+
# Sequel now uses a module level method instead of extending string
|
|
4
|
+
# to handle the internal conversions.
|
|
5
|
+
|
|
6
|
+
class String
|
|
7
|
+
# Converts a string into a Date object.
|
|
8
|
+
def to_date
|
|
9
|
+
begin
|
|
10
|
+
Date.parse(self, Sequel.convert_two_digit_years)
|
|
11
|
+
rescue => e
|
|
12
|
+
raise Sequel::Error::InvalidValue, "Invalid Date value '#{self}' (#{e.message})"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Converts a string into a DateTime object.
|
|
17
|
+
def to_datetime
|
|
18
|
+
begin
|
|
19
|
+
DateTime.parse(self, Sequel.convert_two_digit_years)
|
|
20
|
+
rescue => e
|
|
21
|
+
raise Sequel::Error::InvalidValue, "Invalid DateTime value '#{self}' (#{e.message})"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Converts a string into a Time or DateTime object, depending on the
|
|
26
|
+
# value of Sequel.datetime_class
|
|
27
|
+
def to_sequel_time
|
|
28
|
+
begin
|
|
29
|
+
if Sequel.datetime_class == DateTime
|
|
30
|
+
DateTime.parse(self, Sequel.convert_two_digit_years)
|
|
31
|
+
else
|
|
32
|
+
Sequel.datetime_class.parse(self)
|
|
33
|
+
end
|
|
34
|
+
rescue => e
|
|
35
|
+
raise Sequel::Error::InvalidValue, "Invalid #{Sequel.datetime_class} value '#{self}' (#{e.message})"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Converts a string into a Time object.
|
|
40
|
+
def to_time
|
|
41
|
+
begin
|
|
42
|
+
Time.parse(self)
|
|
43
|
+
rescue => e
|
|
44
|
+
raise Sequel::Error::InvalidValue, "Invalid Time value '#{self}' (#{e.message})"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
# Contains methods that ease metaprogramming, used by some of Sequel's classes.
|
|
3
|
+
module Metaprogramming
|
|
4
|
+
# Add methods to the object's metaclass
|
|
5
|
+
def meta_def(name, &block)
|
|
6
|
+
meta_eval{define_method(name, &block)}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# Make a singleton/class attribute accessor method(s).
|
|
12
|
+
# Replaces the construct:
|
|
13
|
+
#
|
|
14
|
+
# class << self
|
|
15
|
+
# attr_accessor *meths
|
|
16
|
+
# end
|
|
17
|
+
def metaattr_accessor(*meths)
|
|
18
|
+
meta_eval{attr_accessor(*meths)}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Make a singleton/class method(s) private.
|
|
22
|
+
# Make a singleton/class attribute reader method(s).
|
|
23
|
+
# Replaces the construct:
|
|
24
|
+
#
|
|
25
|
+
# class << self
|
|
26
|
+
# attr_reader *meths
|
|
27
|
+
# end
|
|
28
|
+
def metaattr_reader(*meths)
|
|
29
|
+
meta_eval{attr_reader(*meths)}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Evaluate the block in the context of the object's metaclass
|
|
33
|
+
def meta_eval(&block)
|
|
34
|
+
metaclass.instance_eval(&block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The hidden singleton lurks behind everyone
|
|
38
|
+
def metaclass
|
|
39
|
+
class << self
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
# The Migration class describes a database migration that can be reversed.
|
|
3
|
+
# The migration looks very similar to ActiveRecord (Rails) migrations, e.g.:
|
|
4
|
+
#
|
|
5
|
+
# class CreateSessions < Sequel::Migration
|
|
6
|
+
# def up
|
|
7
|
+
# create_table :sessions do
|
|
8
|
+
# primary_key :id
|
|
9
|
+
# String :session_id, :size => 32, :unique => true
|
|
10
|
+
# DateTime :created_at
|
|
11
|
+
# text :data
|
|
12
|
+
# end
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# def down
|
|
16
|
+
# # You can use raw SQL if you need to
|
|
17
|
+
# self << 'DROP TABLE sessions'
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# class AlterItems < Sequel::Migration
|
|
22
|
+
# def up
|
|
23
|
+
# alter_table :items do
|
|
24
|
+
# add_column :category, String, :default => 'ruby'
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# def down
|
|
29
|
+
# alter_table :items do
|
|
30
|
+
# drop_column :category
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# To apply a migration to a database, you can invoke the #apply with
|
|
36
|
+
# the target database instance and the direction :up or :down, e.g.:
|
|
37
|
+
#
|
|
38
|
+
# DB = Sequel.connect('sqlite://mydb')
|
|
39
|
+
# CreateSessions.apply(DB, :up)
|
|
40
|
+
#
|
|
41
|
+
# See Sequel::Schema::Generator for the syntax to use for creating tables,
|
|
42
|
+
# and Sequel::Schema::AlterTableGenerator for the syntax to use when
|
|
43
|
+
# altering existing tables. Migrations act as a proxy for the database
|
|
44
|
+
# given in #apply, so inside #down and #up, you can act as though self
|
|
45
|
+
# refers to the database. So you can use any of the Sequel::Database
|
|
46
|
+
# instance methods directly.
|
|
47
|
+
class Migration
|
|
48
|
+
# Creates a new instance of the migration and sets the @db attribute.
|
|
49
|
+
def initialize(db)
|
|
50
|
+
@db = db
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Applies the migration to the supplied database in the specified
|
|
54
|
+
# direction.
|
|
55
|
+
def self.apply(db, direction)
|
|
56
|
+
obj = new(db)
|
|
57
|
+
case direction
|
|
58
|
+
when :up
|
|
59
|
+
obj.up
|
|
60
|
+
when :down
|
|
61
|
+
obj.down
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError, "Invalid migration direction specified (#{direction.inspect})"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns the list of Migration descendants.
|
|
68
|
+
def self.descendants
|
|
69
|
+
@descendants ||= []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Adds the new migration class to the list of Migration descendants.
|
|
73
|
+
def self.inherited(base)
|
|
74
|
+
descendants << base
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The default down action does nothing
|
|
78
|
+
def down
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Intercepts method calls intended for the database and sends them along.
|
|
82
|
+
def method_missing(method_sym, *args, &block)
|
|
83
|
+
@db.send(method_sym, *args, &block)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The default up action does nothing
|
|
87
|
+
def up
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The Migrator module performs migrations based on migration files in a
|
|
92
|
+
# specified directory. The migration files should be named using the
|
|
93
|
+
# following pattern (in similar fashion to ActiveRecord migrations):
|
|
94
|
+
#
|
|
95
|
+
# <version>_<title>.rb
|
|
96
|
+
#
|
|
97
|
+
# For example, the following files are considered migration files:
|
|
98
|
+
#
|
|
99
|
+
# 001_create_sessions.rb
|
|
100
|
+
# 002_add_data_column.rb
|
|
101
|
+
# ...
|
|
102
|
+
#
|
|
103
|
+
# The migration files should contain one or more migration classes based
|
|
104
|
+
# on Sequel::Migration.
|
|
105
|
+
#
|
|
106
|
+
# Migrations are generally run via the sequel command line tool,
|
|
107
|
+
# using the -m and -M switches. The -m switch specifies the migration
|
|
108
|
+
# directory, and the -M switch specifies the version to which to migrate.
|
|
109
|
+
#
|
|
110
|
+
# You can apply migrations using the Migrator API, as well (this is necessary
|
|
111
|
+
# if you want to specify the version from which to migrate in addition to the version
|
|
112
|
+
# to which to migrate).
|
|
113
|
+
# To apply a migration, the #apply method must be invoked with the database
|
|
114
|
+
# instance, the directory of migration files and the target version. If
|
|
115
|
+
# no current version is supplied, it is read from the database. The migrator
|
|
116
|
+
# automatically creates a schema_info table in the database to keep track
|
|
117
|
+
# of the current migration version. If no migration version is stored in the
|
|
118
|
+
# database, the version is considered to be 0. If no target version is
|
|
119
|
+
# specified, the database is migrated to the latest version available in the
|
|
120
|
+
# migration directory.
|
|
121
|
+
#
|
|
122
|
+
# For example, to migrate the database to the latest version:
|
|
123
|
+
#
|
|
124
|
+
# Sequel::Migrator.apply(DB, '.')
|
|
125
|
+
#
|
|
126
|
+
# To migrate the database from version 1 to version 5:
|
|
127
|
+
#
|
|
128
|
+
# Sequel::Migrator.apply(DB, '.', 5, 1)
|
|
129
|
+
module Migrator
|
|
130
|
+
MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/.freeze
|
|
131
|
+
|
|
132
|
+
# Migrates the supplied database in the specified directory from the
|
|
133
|
+
# current version to the target version. If no current version is
|
|
134
|
+
# supplied, it is extracted from a schema_info table. The schema_info
|
|
135
|
+
# table is automatically created and maintained by the apply function.
|
|
136
|
+
def self.apply(db, directory, target = nil, current = nil)
|
|
137
|
+
# determine current and target version and direction
|
|
138
|
+
current ||= get_current_migration_version(db)
|
|
139
|
+
target ||= latest_migration_version(directory)
|
|
140
|
+
raise Error, "No current version available" if current.nil?
|
|
141
|
+
raise Error, "No target version available" if target.nil?
|
|
142
|
+
|
|
143
|
+
direction = current < target ? :up : :down
|
|
144
|
+
|
|
145
|
+
classes = migration_classes(directory, target, current, direction)
|
|
146
|
+
|
|
147
|
+
db.transaction do
|
|
148
|
+
classes.each {|c| c.apply(db, direction)}
|
|
149
|
+
set_current_migration_version(db, target)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
target
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Gets the current migration version stored in the database. If no version
|
|
156
|
+
# number is stored, 0 is returned.
|
|
157
|
+
def self.get_current_migration_version(db)
|
|
158
|
+
r = schema_info_dataset(db).first
|
|
159
|
+
r ? r[:version] : 0
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Returns the latest version available in the specified directory.
|
|
163
|
+
def self.latest_migration_version(directory)
|
|
164
|
+
l = migration_files(directory).last
|
|
165
|
+
l ? File.basename(l).to_i : nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns a list of migration classes filtered for the migration range and
|
|
169
|
+
# ordered according to the migration direction.
|
|
170
|
+
def self.migration_classes(directory, target, current, direction)
|
|
171
|
+
range = direction == :up ?
|
|
172
|
+
(current + 1)..target : (target + 1)..current
|
|
173
|
+
|
|
174
|
+
# Remove class definitions
|
|
175
|
+
Migration.descendants.each do |c|
|
|
176
|
+
Object.send(:remove_const, c.to_s) rescue nil
|
|
177
|
+
end
|
|
178
|
+
Migration.descendants.clear # remove any defined migration classes
|
|
179
|
+
|
|
180
|
+
# load migration files
|
|
181
|
+
migration_files(directory, range).each {|fn| load(fn)}
|
|
182
|
+
|
|
183
|
+
# get migration classes
|
|
184
|
+
classes = Migration.descendants
|
|
185
|
+
classes.reverse! if direction == :down
|
|
186
|
+
classes
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Returns any found migration files in the supplied directory.
|
|
190
|
+
def self.migration_files(directory, range = nil)
|
|
191
|
+
files = []
|
|
192
|
+
Dir.new(directory).each do |file|
|
|
193
|
+
files[file.to_i] = File.join(directory, file) if MIGRATION_FILE_PATTERN.match(file)
|
|
194
|
+
end
|
|
195
|
+
filtered = range ? files[range] : files
|
|
196
|
+
filtered ? filtered.compact : []
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Returns the dataset for the schema_info table. If no such table
|
|
200
|
+
# exists, it is automatically created.
|
|
201
|
+
def self.schema_info_dataset(db)
|
|
202
|
+
db.create_table(:schema_info) {integer :version} unless db.table_exists?(:schema_info)
|
|
203
|
+
db[:schema_info]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Sets the current migration version stored in the database.
|
|
207
|
+
def self.set_current_migration_version(db, version)
|
|
208
|
+
dataset = schema_info_dataset(db)
|
|
209
|
+
dataset.send(dataset.first ? :update : :<<, :version => version)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
data/lib/sequel/model.rb
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
require 'sequel/core'
|
|
2
|
+
|
|
3
|
+
module Sequel
|
|
4
|
+
# Holds the nameless subclasses that are created with
|
|
5
|
+
# Sequel::Model(), necessary for reopening subclasses with the
|
|
6
|
+
# Sequel::Model() superclass specified.
|
|
7
|
+
@models = {}
|
|
8
|
+
|
|
9
|
+
# Lets you create a Model subclass with its dataset already set.
|
|
10
|
+
# source can be an existing dataset or a symbol (in which case
|
|
11
|
+
# it will create a dataset using the default database with
|
|
12
|
+
# source as the table name).
|
|
13
|
+
#
|
|
14
|
+
# Example:
|
|
15
|
+
# class Comment < Sequel::Model(:something)
|
|
16
|
+
# table_name # => :something
|
|
17
|
+
# end
|
|
18
|
+
def self.Model(source)
|
|
19
|
+
@models[source] ||= Class.new(Model).set_dataset(source)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Model has some methods that are added via metaprogramming:
|
|
23
|
+
#
|
|
24
|
+
# * All of the methods in DATASET_METHODS have class methods created that call
|
|
25
|
+
# the Model's dataset with the method of the same name with the given
|
|
26
|
+
# arguments.
|
|
27
|
+
# * All of the methods in HOOKS have class methods created that accept
|
|
28
|
+
# either a method name symbol or an optional tag and a block. These
|
|
29
|
+
# methods run the code as a callback at the specified time. For example:
|
|
30
|
+
#
|
|
31
|
+
# Model.before_save :do_something
|
|
32
|
+
# Model.before_save(:do_something_else){ self.something_else = 42}
|
|
33
|
+
# object = Model.new
|
|
34
|
+
# object.save
|
|
35
|
+
#
|
|
36
|
+
# Would run the object's :do_something method following by the code
|
|
37
|
+
# block related to :do_something_else. Note that if you specify a
|
|
38
|
+
# block, a tag is optional. If the tag is not nil, it will overwrite
|
|
39
|
+
# a previous block with the same tag. This allows hooks to work with
|
|
40
|
+
# systems that reload code.
|
|
41
|
+
# * All of the methods in HOOKS also create instance methods, but you
|
|
42
|
+
# should not override these instance methods.
|
|
43
|
+
# * The following instance_methods all call the class method of the same
|
|
44
|
+
# name: columns, dataset, db, primary_key, db_schema.
|
|
45
|
+
# * The following class level attr_readers are created: allowed_columns,
|
|
46
|
+
# dataset_methods, primary_key, and restricted_columns.
|
|
47
|
+
# You should not usually need to access these directly.
|
|
48
|
+
# * All validation methods also accept the options specified in #validates_each,
|
|
49
|
+
# in addition to the options specified in the RDoc for that method.
|
|
50
|
+
# * The following class level attr_accessors are created: raise_on_typecast_failure,
|
|
51
|
+
# raise_on_save_failure, strict_param_setting, typecast_empty_string_to_nil,
|
|
52
|
+
# and typecast_on_assignment:
|
|
53
|
+
#
|
|
54
|
+
# # Don't raise an error if a validation attempt fails in
|
|
55
|
+
# # save/create/save_changes/etc.
|
|
56
|
+
# Model.raise_on_save_failure = false
|
|
57
|
+
# Model.before_save{false}
|
|
58
|
+
# Model.new.save # => nil
|
|
59
|
+
# # Don't raise errors in new/set/update/etc. if an attempt to
|
|
60
|
+
# # access a missing/restricted method occurs (just silently
|
|
61
|
+
# # skip it)
|
|
62
|
+
# Model.strict_param_setting = false
|
|
63
|
+
# Model.new(:id=>1) # No Error
|
|
64
|
+
# # Don't typecast attribute values on assignment
|
|
65
|
+
# Model.typecast_on_assignment = false
|
|
66
|
+
# m = Model.new
|
|
67
|
+
# m.number = '10'
|
|
68
|
+
# m.number # => '10' instead of 10
|
|
69
|
+
# # Don't typecast empty string to nil for non-string, non-blob columns.
|
|
70
|
+
# Model.typecast_empty_string_to_nil = false
|
|
71
|
+
# m.number = ''
|
|
72
|
+
# m.number # => '' instead of nil
|
|
73
|
+
# # Don't raise if unable to typecast data for a column
|
|
74
|
+
# Model.typecast_empty_string_to_nil = true
|
|
75
|
+
# Model.raise_on_typecast_failure = false
|
|
76
|
+
# m.not_null_column = '' # => nil
|
|
77
|
+
# m.number = 'A' # => 'A'
|
|
78
|
+
#
|
|
79
|
+
# * The following class level method aliases are defined:
|
|
80
|
+
# * Model.dataset= => set_dataset
|
|
81
|
+
# * Model.is_a => is
|
|
82
|
+
class Model
|
|
83
|
+
# Dataset methods to proxy via metaprogramming
|
|
84
|
+
DATASET_METHODS = %w'<< all avg count delete distinct eager eager_graph each each_page
|
|
85
|
+
empty? except exclude filter first from from_self full_outer_join get graph
|
|
86
|
+
group group_and_count group_by having inner_join insert
|
|
87
|
+
insert_multiple intersect interval join join_table last
|
|
88
|
+
left_outer_join limit map multi_insert naked order order_by order_more
|
|
89
|
+
paginate print query range reverse_order right_outer_join select
|
|
90
|
+
select_all select_more server set set_graph_aliases single_value to_csv to_hash
|
|
91
|
+
transform union unfiltered unordered update where with_sql'.map{|x| x.to_sym}
|
|
92
|
+
|
|
93
|
+
# Regular expression that much match for a public instance method of a plugin
|
|
94
|
+
# dataset to have a model method created that calls it
|
|
95
|
+
DATASET_METHOD_RE = /\A[A-Za-z_][A-Za-z0-9_]*\z/
|
|
96
|
+
|
|
97
|
+
# Empty instance variables, for -w compliance
|
|
98
|
+
EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@transform, :@db, :@skip_superclass_validations]
|
|
99
|
+
|
|
100
|
+
# Hooks that are safe for public use
|
|
101
|
+
HOOKS = [:after_initialize, :before_create, :after_create, :before_update,
|
|
102
|
+
:after_update, :before_save, :after_save, :before_destroy, :after_destroy,
|
|
103
|
+
:before_validation, :after_validation]
|
|
104
|
+
|
|
105
|
+
# Instance variables that are inherited in subclasses
|
|
106
|
+
INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@dataset_methods=>:dup,
|
|
107
|
+
:@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
|
|
108
|
+
:@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
|
|
109
|
+
:@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
|
|
110
|
+
:@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
|
|
111
|
+
:@raise_on_typecast_failure=>nil, :@association_reflections=>:dup}
|
|
112
|
+
|
|
113
|
+
# The setter methods (methods ending with =) that are never allowed
|
|
114
|
+
# to be called automatically via set.
|
|
115
|
+
RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
|
|
116
|
+
|
|
117
|
+
@allowed_columns = nil
|
|
118
|
+
@association_reflections = {}
|
|
119
|
+
@cache_store = nil
|
|
120
|
+
@cache_ttl = nil
|
|
121
|
+
@db = nil
|
|
122
|
+
@db_schema = nil
|
|
123
|
+
@dataset_method_modules = []
|
|
124
|
+
@dataset_methods = {}
|
|
125
|
+
@overridable_methods_module = nil
|
|
126
|
+
@primary_key = :id
|
|
127
|
+
@raise_on_save_failure = true
|
|
128
|
+
@raise_on_typecast_failure = true
|
|
129
|
+
@restrict_primary_key = true
|
|
130
|
+
@restricted_columns = nil
|
|
131
|
+
@simple_pk = nil
|
|
132
|
+
@simple_table = nil
|
|
133
|
+
@skip_superclass_validations = nil
|
|
134
|
+
@strict_param_setting = true
|
|
135
|
+
@transform = nil
|
|
136
|
+
@typecast_empty_string_to_nil = true
|
|
137
|
+
@typecast_on_assignment = true
|
|
138
|
+
@use_transactions = true
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
Sequel.require %w"inflections plugins base association_reflection associations exceptions errors deprecated", "model"
|