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.

Files changed (102) hide show
  1. data/CHANGELOG +198 -0
  2. data/lib/active_record.rb +19 -14
  3. data/lib/active_record/acts/list.rb +8 -6
  4. data/lib/active_record/acts/tree.rb +33 -10
  5. data/lib/active_record/aggregations.rb +1 -7
  6. data/lib/active_record/associations.rb +151 -82
  7. data/lib/active_record/associations/association_collection.rb +25 -0
  8. data/lib/active_record/associations/association_proxy.rb +9 -8
  9. data/lib/active_record/associations/belongs_to_association.rb +19 -5
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
  11. data/lib/active_record/associations/has_many_association.rb +6 -14
  12. data/lib/active_record/associations/has_one_association.rb +5 -3
  13. data/lib/active_record/base.rb +344 -130
  14. data/lib/active_record/callbacks.rb +2 -2
  15. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
  16. data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
  17. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
  18. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
  20. data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
  21. data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
  22. data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
  23. data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
  24. data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
  25. data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
  26. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
  27. data/lib/active_record/fixtures.rb +47 -24
  28. data/lib/active_record/migration.rb +34 -5
  29. data/lib/active_record/observer.rb +32 -2
  30. data/lib/active_record/query_cache.rb +12 -11
  31. data/lib/active_record/schema.rb +58 -0
  32. data/lib/active_record/schema_dumper.rb +84 -0
  33. data/lib/active_record/transactions.rb +1 -3
  34. data/lib/active_record/validations.rb +40 -26
  35. data/lib/active_record/vendor/mysql.rb +6 -0
  36. data/lib/active_record/version.rb +9 -0
  37. data/rakefile +5 -16
  38. data/test/abstract_unit.rb +6 -11
  39. data/test/adapter_test.rb +58 -0
  40. data/test/ar_schema_test.rb +33 -0
  41. data/test/association_callbacks_test.rb +14 -0
  42. data/test/associations_go_eager_test.rb +56 -14
  43. data/test/associations_test.rb +245 -25
  44. data/test/base_test.rb +205 -34
  45. data/test/binary_test.rb +25 -42
  46. data/test/callbacks_test.rb +75 -0
  47. data/test/conditions_scoping_test.rb +136 -0
  48. data/test/connections/native_mysql/connection.rb +0 -4
  49. data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
  50. data/test/copy_table_sqlite.rb +64 -0
  51. data/test/deprecated_associations_test.rb +7 -6
  52. data/test/deprecated_finder_test.rb +3 -3
  53. data/test/finder_test.rb +33 -3
  54. data/test/fixtures/accounts.yml +5 -0
  55. data/test/fixtures/categories_ordered.yml +7 -0
  56. data/test/fixtures/category.rb +11 -1
  57. data/test/fixtures/comment.rb +22 -2
  58. data/test/fixtures/comments.yml +6 -0
  59. data/test/fixtures/companies.yml +15 -0
  60. data/test/fixtures/company.rb +24 -1
  61. data/test/fixtures/db_definitions/db2.drop.sql +5 -1
  62. data/test/fixtures/db_definitions/db2.sql +15 -1
  63. data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
  64. data/test/fixtures/db_definitions/mysql.sql +17 -2
  65. data/test/fixtures/db_definitions/oci.drop.sql +37 -5
  66. data/test/fixtures/db_definitions/oci.sql +47 -4
  67. data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
  68. data/test/fixtures/db_definitions/oci2.sql +2 -2
  69. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  70. data/test/fixtures/db_definitions/postgresql.sql +33 -4
  71. data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
  72. data/test/fixtures/db_definitions/sqlite.sql +16 -2
  73. data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
  74. data/test/fixtures/db_definitions/sqlserver.sql +16 -2
  75. data/test/fixtures/developer.rb +1 -1
  76. data/test/fixtures/flowers.jpg +0 -0
  77. data/test/fixtures/keyboard.rb +3 -0
  78. data/test/fixtures/mixins.yml +11 -1
  79. data/test/fixtures/order.rb +4 -0
  80. data/test/fixtures/post.rb +4 -0
  81. data/test/fixtures/posts.yml +7 -0
  82. data/test/fixtures/project.rb +1 -0
  83. data/test/fixtures/subject.rb +4 -0
  84. data/test/fixtures/subscriber.rb +2 -4
  85. data/test/fixtures/topics.yml +2 -2
  86. data/test/fixtures_test.rb +79 -7
  87. data/test/inheritance_test.rb +2 -2
  88. data/test/lifecycle_test.rb +14 -6
  89. data/test/migration_test.rb +164 -6
  90. data/test/mixin_test.rb +78 -2
  91. data/test/pk_test.rb +25 -1
  92. data/test/readonly_test.rb +31 -0
  93. data/test/reflection_test.rb +4 -1
  94. data/test/schema_dumper_test.rb +19 -0
  95. data/test/schema_test_postgresql.rb +3 -2
  96. data/test/synonym_test_oci.rb +17 -0
  97. data/test/threaded_connections_test.rb +2 -1
  98. data/test/transactions_test.rb +109 -10
  99. data/test/validations_test.rb +70 -42
  100. metadata +25 -5
  101. data/test/fixtures/associations.png +0 -0
  102. 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 true
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