activerecord 1.11.1 → 1.12.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +198 -0
- data/lib/active_record.rb +19 -14
- data/lib/active_record/acts/list.rb +8 -6
- data/lib/active_record/acts/tree.rb +33 -10
- data/lib/active_record/aggregations.rb +1 -7
- data/lib/active_record/associations.rb +151 -82
- data/lib/active_record/associations/association_collection.rb +25 -0
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +19 -5
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
- data/lib/active_record/associations/has_many_association.rb +6 -14
- data/lib/active_record/associations/has_one_association.rb +5 -3
- data/lib/active_record/base.rb +344 -130
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
- data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
- data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
- data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
- data/lib/active_record/fixtures.rb +47 -24
- data/lib/active_record/migration.rb +34 -5
- data/lib/active_record/observer.rb +32 -2
- data/lib/active_record/query_cache.rb +12 -11
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +84 -0
- data/lib/active_record/transactions.rb +1 -3
- data/lib/active_record/validations.rb +40 -26
- data/lib/active_record/vendor/mysql.rb +6 -0
- data/lib/active_record/version.rb +9 -0
- data/rakefile +5 -16
- data/test/abstract_unit.rb +6 -11
- data/test/adapter_test.rb +58 -0
- data/test/ar_schema_test.rb +33 -0
- data/test/association_callbacks_test.rb +14 -0
- data/test/associations_go_eager_test.rb +56 -14
- data/test/associations_test.rb +245 -25
- data/test/base_test.rb +205 -34
- data/test/binary_test.rb +25 -42
- data/test/callbacks_test.rb +75 -0
- data/test/conditions_scoping_test.rb +136 -0
- data/test/connections/native_mysql/connection.rb +0 -4
- data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
- data/test/copy_table_sqlite.rb +64 -0
- data/test/deprecated_associations_test.rb +7 -6
- data/test/deprecated_finder_test.rb +3 -3
- data/test/finder_test.rb +33 -3
- data/test/fixtures/accounts.yml +5 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/category.rb +11 -1
- data/test/fixtures/comment.rb +22 -2
- data/test/fixtures/comments.yml +6 -0
- data/test/fixtures/companies.yml +15 -0
- data/test/fixtures/company.rb +24 -1
- data/test/fixtures/db_definitions/db2.drop.sql +5 -1
- data/test/fixtures/db_definitions/db2.sql +15 -1
- data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql.sql +17 -2
- data/test/fixtures/db_definitions/oci.drop.sql +37 -5
- data/test/fixtures/db_definitions/oci.sql +47 -4
- data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
- data/test/fixtures/db_definitions/oci2.sql +2 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +33 -4
- data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite.sql +16 -2
- data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.sql +16 -2
- data/test/fixtures/developer.rb +1 -1
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/keyboard.rb +3 -0
- data/test/fixtures/mixins.yml +11 -1
- data/test/fixtures/order.rb +4 -0
- data/test/fixtures/post.rb +4 -0
- data/test/fixtures/posts.yml +7 -0
- data/test/fixtures/project.rb +1 -0
- data/test/fixtures/subject.rb +4 -0
- data/test/fixtures/subscriber.rb +2 -4
- data/test/fixtures/topics.yml +2 -2
- data/test/fixtures_test.rb +79 -7
- data/test/inheritance_test.rb +2 -2
- data/test/lifecycle_test.rb +14 -6
- data/test/migration_test.rb +164 -6
- data/test/mixin_test.rb +78 -2
- data/test/pk_test.rb +25 -1
- data/test/readonly_test.rb +31 -0
- data/test/reflection_test.rb +4 -1
- data/test/schema_dumper_test.rb +19 -0
- data/test/schema_test_postgresql.rb +3 -2
- data/test/synonym_test_oci.rb +17 -0
- data/test/threaded_connections_test.rb +2 -1
- data/test/transactions_test.rb +109 -10
- data/test/validations_test.rb +70 -42
- metadata +25 -5
- data/test/fixtures/associations.png +0 -0
- data/test/thread_safety_test.rb +0 -36
@@ -345,9 +345,9 @@ module ActiveRecord
|
|
345
345
|
return false if result == false
|
346
346
|
end
|
347
347
|
|
348
|
-
send(method) if respond_to_without_attributes?(method)
|
348
|
+
result = send(method) if respond_to_without_attributes?(method)
|
349
349
|
|
350
|
-
return
|
350
|
+
return result
|
351
351
|
end
|
352
352
|
|
353
353
|
def callbacks_for(method)
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# The root class of all active record objects.
|
3
|
+
class Base
|
4
|
+
class ConnectionSpecification #:nodoc:
|
5
|
+
attr_reader :config, :adapter_method
|
6
|
+
def initialize (config, adapter_method)
|
7
|
+
@config, @adapter_method = config, adapter_method
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# The class -> [adapter_method, config] map
|
12
|
+
@@defined_connections = {}
|
13
|
+
|
14
|
+
# Establishes the connection to the database. Accepts a hash as input where
|
15
|
+
# the :adapter key must be specified with the name of a database adapter (in lower-case)
|
16
|
+
# example for regular databases (MySQL, Postgresql, etc):
|
17
|
+
#
|
18
|
+
# ActiveRecord::Base.establish_connection(
|
19
|
+
# :adapter => "mysql",
|
20
|
+
# :host => "localhost",
|
21
|
+
# :username => "myuser",
|
22
|
+
# :password => "mypass",
|
23
|
+
# :database => "somedatabase"
|
24
|
+
# )
|
25
|
+
#
|
26
|
+
# Example for SQLite database:
|
27
|
+
#
|
28
|
+
# ActiveRecord::Base.establish_connection(
|
29
|
+
# :adapter => "sqlite",
|
30
|
+
# :dbfile => "path/to/dbfile"
|
31
|
+
# )
|
32
|
+
#
|
33
|
+
# Also accepts keys as strings (for parsing from yaml for example):
|
34
|
+
# ActiveRecord::Base.establish_connection(
|
35
|
+
# "adapter" => "sqlite",
|
36
|
+
# "dbfile" => "path/to/dbfile"
|
37
|
+
# )
|
38
|
+
#
|
39
|
+
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
40
|
+
# may be returned on an error.
|
41
|
+
def self.establish_connection(spec = nil)
|
42
|
+
case spec
|
43
|
+
when nil
|
44
|
+
raise AdapterNotSpecified unless defined? RAILS_ENV
|
45
|
+
establish_connection(RAILS_ENV)
|
46
|
+
when ConnectionSpecification
|
47
|
+
@@defined_connections[self] = spec
|
48
|
+
when Symbol, String
|
49
|
+
if configuration = configurations[spec.to_s]
|
50
|
+
establish_connection(configuration)
|
51
|
+
else
|
52
|
+
raise AdapterNotSpecified, "#{spec} database is not configured"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
spec = spec.symbolize_keys
|
56
|
+
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
57
|
+
adapter_method = "#{spec[:adapter]}_connection"
|
58
|
+
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
|
59
|
+
remove_connection
|
60
|
+
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.active_connections #:nodoc:
|
65
|
+
if allow_concurrency
|
66
|
+
Thread.current['active_connections'] ||= {}
|
67
|
+
else
|
68
|
+
@@active_connections ||= {}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Locate the connection of the nearest super class. This can be an
|
73
|
+
# active or defined connections: if it is the latter, it will be
|
74
|
+
# opened and set as the active connection for the class it was defined
|
75
|
+
# for (not necessarily the current class).
|
76
|
+
def self.retrieve_connection #:nodoc:
|
77
|
+
klass = self
|
78
|
+
ar_super = ActiveRecord::Base.superclass
|
79
|
+
until klass == ar_super
|
80
|
+
if conn = active_connections[klass]
|
81
|
+
return conn
|
82
|
+
elsif conn = @@defined_connections[klass]
|
83
|
+
klass.connection = conn
|
84
|
+
return self.connection
|
85
|
+
end
|
86
|
+
klass = klass.superclass
|
87
|
+
end
|
88
|
+
raise ConnectionNotEstablished
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns true if a connection that's accessible to this class have already been opened.
|
92
|
+
def self.connected?
|
93
|
+
klass = self
|
94
|
+
until klass == ActiveRecord::Base.superclass
|
95
|
+
if active_connections[klass]
|
96
|
+
return true
|
97
|
+
else
|
98
|
+
klass = klass.superclass
|
99
|
+
end
|
100
|
+
end
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
|
104
|
+
# Remove the connection for this class. This will close the active
|
105
|
+
# connection and the defined connection (if they exist). The result
|
106
|
+
# can be used as argument for establish_connection, for easy
|
107
|
+
# re-establishing of the connection.
|
108
|
+
def self.remove_connection(klass=self)
|
109
|
+
conn = @@defined_connections[klass]
|
110
|
+
@@defined_connections.delete(klass)
|
111
|
+
active_connections[klass] = nil
|
112
|
+
conn.config if conn
|
113
|
+
end
|
114
|
+
|
115
|
+
# Set the connection for the class.
|
116
|
+
def self.connection=(spec)
|
117
|
+
if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
|
118
|
+
active_connections[self] = spec
|
119
|
+
elsif spec.kind_of?(ConnectionSpecification)
|
120
|
+
self.connection = self.send(spec.adapter_method, spec.config)
|
121
|
+
elsif spec.nil?
|
122
|
+
raise ConnectionNotEstablished
|
123
|
+
else
|
124
|
+
establish_connection spec
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module DatabaseStatements
|
4
|
+
# Returns an array of record hashes with the column names as keys and
|
5
|
+
# column values as values.
|
6
|
+
def select_all(sql, name = nil)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns a record hash with the column names as keys and column values
|
10
|
+
# as values.
|
11
|
+
def select_one(sql, name = nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns a single value from a record
|
15
|
+
def select_value(sql, name = nil)
|
16
|
+
result = select_one(sql, name)
|
17
|
+
result.nil? ? nil : result.values.first
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns an array of the values of the first column in a select:
|
21
|
+
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
22
|
+
def select_values(sql, name = nil)
|
23
|
+
result = select_all(sql, name)
|
24
|
+
result.map{ |v| v.values.first }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Executes the SQL statement in the context of this connection.
|
28
|
+
# This abstract method raises a NotImplementedError.
|
29
|
+
def execute(sql, name = nil)
|
30
|
+
raise NotImplementedError, "execute is an abstract method"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the last auto-generated ID from the affected table.
|
34
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) end
|
35
|
+
|
36
|
+
# Executes the update statement and returns the number of rows affected.
|
37
|
+
def update(sql, name = nil) end
|
38
|
+
|
39
|
+
# Executes the delete statement and returns the number of rows affected.
|
40
|
+
def delete(sql, name = nil) end
|
41
|
+
|
42
|
+
# Wrap a block in a transaction. Returns result of block.
|
43
|
+
def transaction(start_db_transaction = true)
|
44
|
+
transaction_open = false
|
45
|
+
begin
|
46
|
+
if block_given?
|
47
|
+
if start_db_transaction
|
48
|
+
begin_db_transaction
|
49
|
+
transaction_open = true
|
50
|
+
end
|
51
|
+
yield
|
52
|
+
end
|
53
|
+
rescue Exception => database_transaction_rollback
|
54
|
+
if transaction_open
|
55
|
+
transaction_open = false
|
56
|
+
rollback_db_transaction
|
57
|
+
end
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
ensure
|
61
|
+
commit_db_transaction if transaction_open
|
62
|
+
end
|
63
|
+
|
64
|
+
# Begins the transaction (and turns off auto-committing).
|
65
|
+
def begin_db_transaction() end
|
66
|
+
|
67
|
+
# Commits the transaction (and turns on auto-committing).
|
68
|
+
def commit_db_transaction() end
|
69
|
+
|
70
|
+
# Rolls back the transaction (and turns on auto-committing). Must be
|
71
|
+
# done if the transaction block raises an exception or returns false.
|
72
|
+
def rollback_db_transaction() end
|
73
|
+
|
74
|
+
# Alias for #add_limit_offset!.
|
75
|
+
def add_limit!(sql, options)
|
76
|
+
add_limit_offset!(sql, options) if options
|
77
|
+
end
|
78
|
+
|
79
|
+
# Appends +LIMIT+ and +OFFSET+ options to a SQL statement.
|
80
|
+
# This method *modifies* the +sql+ parameter.
|
81
|
+
# ===== Examples
|
82
|
+
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
83
|
+
# generates
|
84
|
+
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
|
85
|
+
def add_limit_offset!(sql, options)
|
86
|
+
if limit = options[:limit]
|
87
|
+
sql << " LIMIT #{limit}"
|
88
|
+
if offset = options[:offset]
|
89
|
+
sql << " OFFSET #{offset}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_sequence_name(table, column)
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Set the sequence to the max value of the table's column.
|
99
|
+
def reset_sequence!(table, column, sequence = nil)
|
100
|
+
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module Quoting
|
4
|
+
# Quotes the column value to help prevent
|
5
|
+
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
6
|
+
def quote(value, column = nil)
|
7
|
+
case value
|
8
|
+
when String
|
9
|
+
if column && column.type == :binary
|
10
|
+
"'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
|
11
|
+
elsif column && [:integer, :float].include?(column.type)
|
12
|
+
value.to_s
|
13
|
+
else
|
14
|
+
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
15
|
+
end
|
16
|
+
when NilClass then "NULL"
|
17
|
+
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
18
|
+
when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
|
19
|
+
when Float, Fixnum, Bignum then value.to_s
|
20
|
+
when Date then "'#{value.to_s}'"
|
21
|
+
when Time, DateTime then "'#{quoted_date(value)}'"
|
22
|
+
else "'#{quote_string(value.to_yaml)}'"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
27
|
+
# characters.
|
28
|
+
def quote_string(s)
|
29
|
+
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a quoted form of the column name. This is highly adapter
|
33
|
+
# specific.
|
34
|
+
def quote_column_name(name)
|
35
|
+
name
|
36
|
+
end
|
37
|
+
|
38
|
+
def quoted_true
|
39
|
+
"'t'"
|
40
|
+
end
|
41
|
+
|
42
|
+
def quoted_false
|
43
|
+
"'f'"
|
44
|
+
end
|
45
|
+
|
46
|
+
def quoted_date(value)
|
47
|
+
value.strftime("%Y-%m-%d %H:%M:%S")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'parsedate'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters #:nodoc:
|
5
|
+
# An abstract definition of a column in a table.
|
6
|
+
class Column
|
7
|
+
attr_reader :name, :default, :type, :limit, :null
|
8
|
+
attr_accessor :primary
|
9
|
+
|
10
|
+
# Instantiates a new column in the table.
|
11
|
+
#
|
12
|
+
# +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>.
|
13
|
+
# +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>.
|
14
|
+
# +sql_type+ is only used to extract the column's length, if necessary. For example, <tt>company_name varchar(<b>60</b>)</tt>.
|
15
|
+
# +null+ determines if this column allows +NULL+ values.
|
16
|
+
def initialize(name, default, sql_type = nil, null = true)
|
17
|
+
@name, @type, @null = name, simplified_type(sql_type), null
|
18
|
+
# have to do this one separately because type_cast depends on #type
|
19
|
+
@default = type_cast(default)
|
20
|
+
@limit = extract_limit(sql_type) unless sql_type.nil?
|
21
|
+
@primary = nil
|
22
|
+
@text = [:string, :text].include? @type
|
23
|
+
@number = [:float, :integer].include? @type
|
24
|
+
end
|
25
|
+
|
26
|
+
def text?
|
27
|
+
@text
|
28
|
+
end
|
29
|
+
|
30
|
+
def number?
|
31
|
+
@number
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the Ruby class that corresponds to the abstract data type.
|
35
|
+
def klass
|
36
|
+
case type
|
37
|
+
when :integer then Fixnum
|
38
|
+
when :float then Float
|
39
|
+
when :datetime then Time
|
40
|
+
when :date then Date
|
41
|
+
when :timestamp then Time
|
42
|
+
when :time then Time
|
43
|
+
when :text, :string then String
|
44
|
+
when :binary then String
|
45
|
+
when :boolean then Object
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Casts value (which is a String) to an appropriate instance.
|
50
|
+
def type_cast(value)
|
51
|
+
return nil if value.nil?
|
52
|
+
case type
|
53
|
+
when :string then value
|
54
|
+
when :text then value
|
55
|
+
when :integer then value.to_i rescue value ? 1 : 0
|
56
|
+
when :float then value.to_f
|
57
|
+
when :datetime then self.class.string_to_time(value)
|
58
|
+
when :timestamp then self.class.string_to_time(value)
|
59
|
+
when :time then self.class.string_to_dummy_time(value)
|
60
|
+
when :date then self.class.string_to_date(value)
|
61
|
+
when :binary then self.class.binary_to_string(value)
|
62
|
+
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
|
63
|
+
else value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def type_cast_code(var_name)
|
68
|
+
case type
|
69
|
+
when :string then nil
|
70
|
+
when :text then nil
|
71
|
+
when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
|
72
|
+
when :float then "#{var_name}.to_f"
|
73
|
+
when :datetime then "#{self.class.name}.string_to_time(#{var_name})"
|
74
|
+
when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
|
75
|
+
when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
|
76
|
+
when :date then "#{self.class.name}.string_to_date(#{var_name})"
|
77
|
+
when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
|
78
|
+
when :boolean then "(#{var_name} == true or (#{var_name} =~ /^t(?:true)?$/i) == 0 or #{var_name}.to_s == '1')"
|
79
|
+
else nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the human name of the column name.
|
84
|
+
#
|
85
|
+
# ===== Examples
|
86
|
+
# Column.new('sales_stage', ...).human_name #=> 'Sales stage'
|
87
|
+
def human_name
|
88
|
+
Base.human_attribute_name(@name)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Used to convert from Strings to BLOBs
|
92
|
+
def self.string_to_binary(value)
|
93
|
+
value
|
94
|
+
end
|
95
|
+
|
96
|
+
# Used to convert from BLOBs to Strings
|
97
|
+
def self.binary_to_string(value)
|
98
|
+
value
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.string_to_date(string)
|
102
|
+
return string unless string.is_a?(String)
|
103
|
+
date_array = ParseDate.parsedate(string)
|
104
|
+
# treat 0000-00-00 as nil
|
105
|
+
Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.string_to_time(string)
|
109
|
+
return string unless string.is_a?(String)
|
110
|
+
time_array = ParseDate.parsedate(string)[0..5]
|
111
|
+
# treat 0000-00-00 00:00:00 as nil
|
112
|
+
Time.send(Base.default_timezone, *time_array) rescue nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.string_to_dummy_time(string)
|
116
|
+
return string unless string.is_a?(String)
|
117
|
+
time_array = ParseDate.parsedate(string)
|
118
|
+
# pad the resulting array with dummy date information
|
119
|
+
time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
|
120
|
+
Time.send(Base.default_timezone, *time_array) rescue nil
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
def extract_limit(sql_type)
|
125
|
+
$1.to_i if sql_type =~ /\((.*)\)/
|
126
|
+
end
|
127
|
+
|
128
|
+
def simplified_type(field_type)
|
129
|
+
case field_type
|
130
|
+
when /int/i
|
131
|
+
:integer
|
132
|
+
when /float|double|decimal|numeric/i
|
133
|
+
:float
|
134
|
+
when /datetime/i
|
135
|
+
:datetime
|
136
|
+
when /timestamp/i
|
137
|
+
:timestamp
|
138
|
+
when /time/i
|
139
|
+
:time
|
140
|
+
when /date/i
|
141
|
+
:date
|
142
|
+
when /clob/i, /text/i
|
143
|
+
:text
|
144
|
+
when /blob/i, /binary/i
|
145
|
+
:binary
|
146
|
+
when /char/i, /string/i
|
147
|
+
:string
|
148
|
+
when /boolean/i
|
149
|
+
:boolean
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
|
155
|
+
end
|
156
|
+
|
157
|
+
class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :default, :null) #:nodoc:
|
158
|
+
def to_sql
|
159
|
+
column_sql = "#{name} #{type_to_sql(type.to_sym, limit)}"
|
160
|
+
add_column_options!(column_sql, :null => null, :default => default)
|
161
|
+
column_sql
|
162
|
+
end
|
163
|
+
alias to_s :to_sql
|
164
|
+
|
165
|
+
private
|
166
|
+
def type_to_sql(name, limit)
|
167
|
+
base.type_to_sql(name, limit) rescue name
|
168
|
+
end
|
169
|
+
|
170
|
+
def add_column_options!(sql, options)
|
171
|
+
base.add_column_options!(sql, options.merge(:column => self))
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Represents a SQL table in an abstract way.
|
176
|
+
# Columns are stored as ColumnDefinition in the #columns attribute.
|
177
|
+
class TableDefinition
|
178
|
+
attr_accessor :columns
|
179
|
+
|
180
|
+
def initialize(base)
|
181
|
+
@columns = []
|
182
|
+
@base = base
|
183
|
+
end
|
184
|
+
|
185
|
+
# Appends a primary key definition to the table definition.
|
186
|
+
# Can be called multiple times, but this is probably not a good idea.
|
187
|
+
def primary_key(name)
|
188
|
+
column(name, native[:primary_key])
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns a ColumnDefinition for the column with name +name+.
|
192
|
+
def [](name)
|
193
|
+
@columns.find {|column| column.name.to_s == name.to_s}
|
194
|
+
end
|
195
|
+
|
196
|
+
# Instantiates a new column for the table.
|
197
|
+
# The +type+ parameter must be one of the following values:
|
198
|
+
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
|
199
|
+
# <tt>:integer</tt>, <tt>:float</tt>, <tt>:datetime</tt>,
|
200
|
+
# <tt>:timestamp</tt>, <tt>:time</tt>, <tt>:date</tt>,
|
201
|
+
# <tt>:binary</tt>, <tt>:boolean</tt>.
|
202
|
+
#
|
203
|
+
# Available options are (none of these exists by default):
|
204
|
+
# * <tt>:limit</tt>:
|
205
|
+
# Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
|
206
|
+
# <tt>:binary</tt> or <tt>:integer</tt> columns only)
|
207
|
+
# * <tt>:default</tt>:
|
208
|
+
# The column's default value. You cannot explicitely set the default
|
209
|
+
# value to +NULL+. Simply leave off this option if you want a +NULL+
|
210
|
+
# default value.
|
211
|
+
# * <tt>:null</tt>:
|
212
|
+
# Allows or disallows +NULL+ values in the column. This option could
|
213
|
+
# have been named <tt>:null_allowed</tt>.
|
214
|
+
#
|
215
|
+
# This method returns <tt>self</tt>.
|
216
|
+
#
|
217
|
+
# ===== Examples
|
218
|
+
# # Assuming def is an instance of TableDefinition
|
219
|
+
# def.column(:granted, :boolean)
|
220
|
+
# #=> granted BOOLEAN
|
221
|
+
#
|
222
|
+
# def.column(:picture, :binary, :limit => 2.megabytes)
|
223
|
+
# #=> picture BLOB(2097152)
|
224
|
+
#
|
225
|
+
# def.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
|
226
|
+
# #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
|
227
|
+
def column(name, type, options = {})
|
228
|
+
column = self[name] || ColumnDefinition.new(@base, name, type)
|
229
|
+
column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
|
230
|
+
column.default = options[:default]
|
231
|
+
column.null = options[:null]
|
232
|
+
@columns << column unless @columns.include? column
|
233
|
+
self
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns a String whose contents are the column definitions
|
237
|
+
# concatenated together. This string can then be pre and appended to
|
238
|
+
# to generate the final SQL to create the table.
|
239
|
+
def to_sql
|
240
|
+
@columns * ', '
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
def native
|
245
|
+
@base.native_database_types
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|