activerecord 1.0.0 → 3.0.0

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 (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -0,0 +1,739 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'date'
3
+ require 'set'
4
+ require 'bigdecimal'
5
+ require 'bigdecimal/util'
6
+
7
+ module ActiveRecord
8
+ module ConnectionAdapters #:nodoc:
9
+ # An abstract definition of a column in a table.
10
+ class Column
11
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
12
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
13
+
14
+ module Format
15
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
16
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
17
+ end
18
+
19
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
20
+ attr_accessor :primary
21
+
22
+ # Instantiates a new column in the table.
23
+ #
24
+ # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
25
+ # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
26
+ # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
27
+ # <tt>company_name varchar(60)</tt>.
28
+ # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
29
+ # +null+ determines if this column allows +NULL+ values.
30
+ def initialize(name, default, sql_type = nil, null = true)
31
+ @name, @sql_type, @null = name, sql_type, null
32
+ @limit, @precision, @scale = extract_limit(sql_type), extract_precision(sql_type), extract_scale(sql_type)
33
+ @type = simplified_type(sql_type)
34
+ @default = extract_default(default)
35
+
36
+ @primary = nil
37
+ end
38
+
39
+ # Returns +true+ if the column is either of type string or text.
40
+ def text?
41
+ type == :string || type == :text
42
+ end
43
+
44
+ # Returns +true+ if the column is either of type integer, float or decimal.
45
+ def number?
46
+ type == :integer || type == :float || type == :decimal
47
+ end
48
+
49
+ def has_default?
50
+ !default.nil?
51
+ end
52
+
53
+ # Returns the Ruby class that corresponds to the abstract data type.
54
+ def klass
55
+ case type
56
+ when :integer then Fixnum
57
+ when :float then Float
58
+ when :decimal then BigDecimal
59
+ when :datetime then Time
60
+ when :date then Date
61
+ when :timestamp then Time
62
+ when :time then Time
63
+ when :text, :string then String
64
+ when :binary then String
65
+ when :boolean then Object
66
+ end
67
+ end
68
+
69
+ # Casts value (which is a String) to an appropriate instance.
70
+ def type_cast(value)
71
+ return nil if value.nil?
72
+ case type
73
+ when :string then value
74
+ when :text then value
75
+ when :integer then value.to_i rescue value ? 1 : 0
76
+ when :float then value.to_f
77
+ when :decimal then self.class.value_to_decimal(value)
78
+ when :datetime then self.class.string_to_time(value)
79
+ when :timestamp then self.class.string_to_time(value)
80
+ when :time then self.class.string_to_dummy_time(value)
81
+ when :date then self.class.string_to_date(value)
82
+ when :binary then self.class.binary_to_string(value)
83
+ when :boolean then self.class.value_to_boolean(value)
84
+ else value
85
+ end
86
+ end
87
+
88
+ def type_cast_code(var_name)
89
+ case type
90
+ when :string then nil
91
+ when :text then nil
92
+ when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
93
+ when :float then "#{var_name}.to_f"
94
+ when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
95
+ when :datetime then "#{self.class.name}.string_to_time(#{var_name})"
96
+ when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
97
+ when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
98
+ when :date then "#{self.class.name}.string_to_date(#{var_name})"
99
+ when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
100
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
101
+ else nil
102
+ end
103
+ end
104
+
105
+ # Returns the human name of the column name.
106
+ #
107
+ # ===== Examples
108
+ # Column.new('sales_stage', ...).human_name # => 'Sales stage'
109
+ def human_name
110
+ Base.human_attribute_name(@name)
111
+ end
112
+
113
+ def extract_default(default)
114
+ type_cast(default)
115
+ end
116
+
117
+ class << self
118
+ # Used to convert from Strings to BLOBs
119
+ def string_to_binary(value)
120
+ value
121
+ end
122
+
123
+ # Used to convert from BLOBs to Strings
124
+ def binary_to_string(value)
125
+ value
126
+ end
127
+
128
+ def string_to_date(string)
129
+ return string unless string.is_a?(String)
130
+ return nil if string.empty?
131
+
132
+ fast_string_to_date(string) || fallback_string_to_date(string)
133
+ end
134
+
135
+ def string_to_time(string)
136
+ return string unless string.is_a?(String)
137
+ return nil if string.empty?
138
+
139
+ fast_string_to_time(string) || fallback_string_to_time(string)
140
+ end
141
+
142
+ def string_to_dummy_time(string)
143
+ return string unless string.is_a?(String)
144
+ return nil if string.empty?
145
+
146
+ string_to_time "2000-01-01 #{string}"
147
+ end
148
+
149
+ # convert something to a boolean
150
+ def value_to_boolean(value)
151
+ if value.is_a?(String) && value.blank?
152
+ nil
153
+ else
154
+ TRUE_VALUES.include?(value)
155
+ end
156
+ end
157
+
158
+ # convert something to a BigDecimal
159
+ def value_to_decimal(value)
160
+ # Using .class is faster than .is_a? and
161
+ # subclasses of BigDecimal will be handled
162
+ # in the else clause
163
+ if value.class == BigDecimal
164
+ value
165
+ elsif value.respond_to?(:to_d)
166
+ value.to_d
167
+ else
168
+ value.to_s.to_d
169
+ end
170
+ end
171
+
172
+ protected
173
+ # '0.123456' -> 123456
174
+ # '1.123456' -> 123456
175
+ def microseconds(time)
176
+ ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
177
+ end
178
+
179
+ def new_date(year, mon, mday)
180
+ if year && year != 0
181
+ Date.new(year, mon, mday) rescue nil
182
+ end
183
+ end
184
+
185
+ def new_time(year, mon, mday, hour, min, sec, microsec)
186
+ # Treat 0000-00-00 00:00:00 as nil.
187
+ return nil if year.nil? || year == 0
188
+
189
+ Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
190
+ end
191
+
192
+ def fast_string_to_date(string)
193
+ if string =~ Format::ISO_DATE
194
+ new_date $1.to_i, $2.to_i, $3.to_i
195
+ end
196
+ end
197
+
198
+ # Doesn't handle time zones.
199
+ def fast_string_to_time(string)
200
+ if string =~ Format::ISO_DATETIME
201
+ microsec = ($7.to_f * 1_000_000).to_i
202
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
203
+ end
204
+ end
205
+
206
+ def fallback_string_to_date(string)
207
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
208
+ end
209
+
210
+ def fallback_string_to_time(string)
211
+ time_hash = Date._parse(string)
212
+ time_hash[:sec_fraction] = microseconds(time_hash)
213
+
214
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
215
+ end
216
+ end
217
+
218
+ private
219
+ def extract_limit(sql_type)
220
+ $1.to_i if sql_type =~ /\((.*)\)/
221
+ end
222
+
223
+ def extract_precision(sql_type)
224
+ $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
225
+ end
226
+
227
+ def extract_scale(sql_type)
228
+ case sql_type
229
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
230
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
231
+ end
232
+ end
233
+
234
+ def simplified_type(field_type)
235
+ case field_type
236
+ when /int/i
237
+ :integer
238
+ when /float|double/i
239
+ :float
240
+ when /decimal|numeric|number/i
241
+ extract_scale(field_type) == 0 ? :integer : :decimal
242
+ when /datetime/i
243
+ :datetime
244
+ when /timestamp/i
245
+ :timestamp
246
+ when /time/i
247
+ :time
248
+ when /date/i
249
+ :date
250
+ when /clob/i, /text/i
251
+ :text
252
+ when /blob/i, /binary/i
253
+ :binary
254
+ when /char/i, /string/i
255
+ :string
256
+ when /boolean/i
257
+ :boolean
258
+ end
259
+ end
260
+ end
261
+
262
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
263
+ end
264
+
265
+ # Abstract representation of a column definition. Instances of this type
266
+ # are typically created by methods in TableDefinition, and added to the
267
+ # +columns+ attribute of said TableDefinition object, in order to be used
268
+ # for generating a number of table creation or table changing SQL statements.
269
+ class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
270
+
271
+ def sql_type
272
+ base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
273
+ end
274
+
275
+ def to_sql
276
+ column_sql = "#{base.quote_column_name(name)} #{sql_type}"
277
+ column_options = {}
278
+ column_options[:null] = null unless null.nil?
279
+ column_options[:default] = default unless default.nil?
280
+ add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
281
+ column_sql
282
+ end
283
+
284
+ private
285
+
286
+ def add_column_options!(sql, options)
287
+ base.add_column_options!(sql, options.merge(:column => self))
288
+ end
289
+ end
290
+
291
+ # Represents the schema of an SQL table in an abstract way. This class
292
+ # provides methods for manipulating the schema representation.
293
+ #
294
+ # Inside migration files, the +t+ object in +create_table+ and
295
+ # +change_table+ is actually of this type:
296
+ #
297
+ # class SomeMigration < ActiveRecord::Migration
298
+ # def self.up
299
+ # create_table :foo do |t|
300
+ # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
301
+ # end
302
+ # end
303
+ #
304
+ # def self.down
305
+ # ...
306
+ # end
307
+ # end
308
+ #
309
+ # The table definitions
310
+ # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
311
+ class TableDefinition
312
+ # An array of ColumnDefinition objects, representing the column changes
313
+ # that have been defined.
314
+ attr_accessor :columns
315
+
316
+ def initialize(base)
317
+ @columns = []
318
+ @base = base
319
+ end
320
+
321
+ #Handles non supported datatypes - e.g. XML
322
+ def method_missing(symbol, *args)
323
+ if symbol.to_s == 'xml'
324
+ xml_column_fallback(args)
325
+ else
326
+ super
327
+ end
328
+ end
329
+
330
+ def xml_column_fallback(*args)
331
+ case @base.adapter_name.downcase
332
+ when 'sqlite', 'mysql'
333
+ options = args.extract_options!
334
+ column(args[0], :text, options)
335
+ end
336
+ end
337
+
338
+ # Appends a primary key definition to the table definition.
339
+ # Can be called multiple times, but this is probably not a good idea.
340
+ def primary_key(name)
341
+ column(name, :primary_key)
342
+ end
343
+
344
+ # Returns a ColumnDefinition for the column with name +name+.
345
+ def [](name)
346
+ @columns.find {|column| column.name.to_s == name.to_s}
347
+ end
348
+
349
+ # Instantiates a new column for the table.
350
+ # The +type+ parameter is normally one of the migrations native types,
351
+ # which is one of the following:
352
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
353
+ # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
354
+ # <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
355
+ # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>.
356
+ #
357
+ # You may use a type not in this list as long as it is supported by your
358
+ # database (for example, "polygon" in MySQL), but this will not be database
359
+ # agnostic and should usually be avoided.
360
+ #
361
+ # Available options are (none of these exists by default):
362
+ # * <tt>:limit</tt> -
363
+ # Requests a maximum column length. This is number of characters for <tt>:string</tt> and
364
+ # <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
365
+ # * <tt>:default</tt> -
366
+ # The column's default value. Use nil for NULL.
367
+ # * <tt>:null</tt> -
368
+ # Allows or disallows +NULL+ values in the column. This option could
369
+ # have been named <tt>:null_allowed</tt>.
370
+ # * <tt>:precision</tt> -
371
+ # Specifies the precision for a <tt>:decimal</tt> column.
372
+ # * <tt>:scale</tt> -
373
+ # Specifies the scale for a <tt>:decimal</tt> column.
374
+ #
375
+ # For clarity's sake: the precision is the number of significant digits,
376
+ # while the scale is the number of digits that can be stored following
377
+ # the decimal point. For example, the number 123.45 has a precision of 5
378
+ # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
379
+ # range from -999.99 to 999.99.
380
+ #
381
+ # Please be aware of different RDBMS implementations behavior with
382
+ # <tt>:decimal</tt> columns:
383
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
384
+ # <tt>:precision</tt>, and makes no comments about the requirements of
385
+ # <tt>:precision</tt>.
386
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
387
+ # Default is (10,0).
388
+ # * PostgreSQL: <tt>:precision</tt> [1..infinity],
389
+ # <tt>:scale</tt> [0..infinity]. No default.
390
+ # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
391
+ # Internal storage as strings. No default.
392
+ # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
393
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
394
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
395
+ # Default is (38,0).
396
+ # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
397
+ # Default unknown.
398
+ # * Firebird: <tt>:precision</tt> [1..18], <tt>:scale</tt> [0..18].
399
+ # Default (9,0). Internal types NUMERIC and DECIMAL have different
400
+ # storage rules, decimal being better.
401
+ # * FrontBase?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
402
+ # Default (38,0). WARNING Max <tt>:precision</tt>/<tt>:scale</tt> for
403
+ # NUMERIC is 19, and DECIMAL is 38.
404
+ # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
405
+ # Default (38,0).
406
+ # * Sybase: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
407
+ # Default (38,0).
408
+ # * OpenBase?: Documentation unclear. Claims storage in <tt>double</tt>.
409
+ #
410
+ # This method returns <tt>self</tt>.
411
+ #
412
+ # == Examples
413
+ # # Assuming td is an instance of TableDefinition
414
+ # td.column(:granted, :boolean)
415
+ # # granted BOOLEAN
416
+ #
417
+ # td.column(:picture, :binary, :limit => 2.megabytes)
418
+ # # => picture BLOB(2097152)
419
+ #
420
+ # td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
421
+ # # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
422
+ #
423
+ # td.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2)
424
+ # # => bill_gates_money DECIMAL(15,2)
425
+ #
426
+ # td.column(:sensor_reading, :decimal, :precision => 30, :scale => 20)
427
+ # # => sensor_reading DECIMAL(30,20)
428
+ #
429
+ # # While <tt>:scale</tt> defaults to zero on most databases, it
430
+ # # probably wouldn't hurt to include it.
431
+ # td.column(:huge_integer, :decimal, :precision => 30)
432
+ # # => huge_integer DECIMAL(30)
433
+ #
434
+ # # Defines a column with a database-specific type.
435
+ # td.column(:foo, 'polygon')
436
+ # # => foo polygon
437
+ #
438
+ # == Short-hand examples
439
+ #
440
+ # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types.
441
+ # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
442
+ # in a single statement.
443
+ #
444
+ # What can be written like this with the regular calls to column:
445
+ #
446
+ # create_table "products", :force => true do |t|
447
+ # t.column "shop_id", :integer
448
+ # t.column "creator_id", :integer
449
+ # t.column "name", :string, :default => "Untitled"
450
+ # t.column "value", :string, :default => "Untitled"
451
+ # t.column "created_at", :datetime
452
+ # t.column "updated_at", :datetime
453
+ # end
454
+ #
455
+ # Can also be written as follows using the short-hand:
456
+ #
457
+ # create_table :products do |t|
458
+ # t.integer :shop_id, :creator_id
459
+ # t.string :name, :value, :default => "Untitled"
460
+ # t.timestamps
461
+ # end
462
+ #
463
+ # There's a short-hand method for each of the type values declared at the top. And then there's
464
+ # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes.
465
+ #
466
+ # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
467
+ # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
468
+ # options, these will be used when creating the <tt>_type</tt> column. So what can be written like this:
469
+ #
470
+ # create_table :taggings do |t|
471
+ # t.integer :tag_id, :tagger_id, :taggable_id
472
+ # t.string :tagger_type
473
+ # t.string :taggable_type, :default => 'Photo'
474
+ # end
475
+ #
476
+ # Can also be written as follows using references:
477
+ #
478
+ # create_table :taggings do |t|
479
+ # t.references :tag
480
+ # t.references :tagger, :polymorphic => true
481
+ # t.references :taggable, :polymorphic => { :default => 'Photo' }
482
+ # end
483
+ def column(name, type, options = {})
484
+ column = self[name] || ColumnDefinition.new(@base, name, type)
485
+ if options[:limit]
486
+ column.limit = options[:limit]
487
+ elsif native[type.to_sym].is_a?(Hash)
488
+ column.limit = native[type.to_sym][:limit]
489
+ end
490
+ column.precision = options[:precision]
491
+ column.scale = options[:scale]
492
+ column.default = options[:default]
493
+ column.null = options[:null]
494
+ @columns << column unless @columns.include? column
495
+ self
496
+ end
497
+
498
+ %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
499
+ class_eval <<-EOV, __FILE__, __LINE__ + 1
500
+ def #{column_type}(*args) # def string(*args)
501
+ options = args.extract_options! # options = args.extract_options!
502
+ column_names = args # column_names = args
503
+ #
504
+ column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) }
505
+ end # end
506
+ EOV
507
+ end
508
+
509
+ # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
510
+ # <tt>:updated_at</tt> to the table.
511
+ def timestamps(*args)
512
+ options = args.extract_options!
513
+ column(:created_at, :datetime, options)
514
+ column(:updated_at, :datetime, options)
515
+ end
516
+
517
+ def references(*args)
518
+ options = args.extract_options!
519
+ polymorphic = options.delete(:polymorphic)
520
+ args.each do |col|
521
+ column("#{col}_id", :integer, options)
522
+ column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
523
+ end
524
+ end
525
+ alias :belongs_to :references
526
+
527
+ # Returns a String whose contents are the column definitions
528
+ # concatenated together. This string can then be prepended and appended to
529
+ # to generate the final SQL to create the table.
530
+ def to_sql
531
+ @columns.map { |c| c.to_sql } * ', '
532
+ end
533
+
534
+ private
535
+ def native
536
+ @base.native_database_types
537
+ end
538
+ end
539
+
540
+ # Represents an SQL table in an abstract way for updating a table.
541
+ # Also see TableDefinition and SchemaStatements#create_table
542
+ #
543
+ # Available transformations are:
544
+ #
545
+ # change_table :table do |t|
546
+ # t.column
547
+ # t.index
548
+ # t.timestamps
549
+ # t.change
550
+ # t.change_default
551
+ # t.rename
552
+ # t.references
553
+ # t.belongs_to
554
+ # t.string
555
+ # t.text
556
+ # t.integer
557
+ # t.float
558
+ # t.decimal
559
+ # t.datetime
560
+ # t.timestamp
561
+ # t.time
562
+ # t.date
563
+ # t.binary
564
+ # t.boolean
565
+ # t.remove
566
+ # t.remove_references
567
+ # t.remove_belongs_to
568
+ # t.remove_index
569
+ # t.remove_timestamps
570
+ # end
571
+ #
572
+ class Table
573
+ def initialize(table_name, base)
574
+ @table_name = table_name
575
+ @base = base
576
+ end
577
+
578
+ # Adds a new column to the named table.
579
+ # See TableDefinition#column for details of the options you can use.
580
+ # ===== Example
581
+ # ====== Creating a simple column
582
+ # t.column(:name, :string)
583
+ def column(column_name, type, options = {})
584
+ @base.add_column(@table_name, column_name, type, options)
585
+ end
586
+
587
+ # Checks to see if a column exists. See SchemaStatements#column_exists?
588
+ def column_exists?(column_name, type = nil, options = nil)
589
+ @base.column_exists?(@table_name, column_name, type, options)
590
+ end
591
+
592
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
593
+ # an Array of Symbols. See SchemaStatements#add_index
594
+ #
595
+ # ===== Examples
596
+ # ====== Creating a simple index
597
+ # t.index(:name)
598
+ # ====== Creating a unique index
599
+ # t.index([:branch_id, :party_id], :unique => true)
600
+ # ====== Creating a named index
601
+ # t.index([:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
602
+ def index(column_name, options = {})
603
+ @base.add_index(@table_name, column_name, options)
604
+ end
605
+
606
+ # Checks to see if an index exists. See SchemaStatements#index_exists?
607
+ def index_exists?(column_name, options = {})
608
+ @base.index_exists?(@table_name, column_name, options)
609
+ end
610
+
611
+ # Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
612
+ # ===== Example
613
+ # t.timestamps
614
+ def timestamps
615
+ @base.add_timestamps(@table_name)
616
+ end
617
+
618
+ # Changes the column's definition according to the new options.
619
+ # See TableDefinition#column for details of the options you can use.
620
+ # ===== Examples
621
+ # t.change(:name, :string, :limit => 80)
622
+ # t.change(:description, :text)
623
+ def change(column_name, type, options = {})
624
+ @base.change_column(@table_name, column_name, type, options)
625
+ end
626
+
627
+ # Sets a new default value for a column. See SchemaStatements#change_column_default
628
+ # ===== Examples
629
+ # t.change_default(:qualification, 'new')
630
+ # t.change_default(:authorized, 1)
631
+ def change_default(column_name, default)
632
+ @base.change_column_default(@table_name, column_name, default)
633
+ end
634
+
635
+ # Removes the column(s) from the table definition.
636
+ # ===== Examples
637
+ # t.remove(:qualification)
638
+ # t.remove(:qualification, :experience)
639
+ def remove(*column_names)
640
+ @base.remove_column(@table_name, column_names)
641
+ end
642
+
643
+ # Removes the given index from the table.
644
+ #
645
+ # ===== Examples
646
+ # ====== Remove the suppliers_name_index in the suppliers table
647
+ # t.remove_index :name
648
+ # ====== Remove the index named accounts_branch_id_index in the accounts table
649
+ # t.remove_index :column => :branch_id
650
+ # ====== Remove the index named accounts_branch_id_party_id_index in the accounts table
651
+ # t.remove_index :column => [:branch_id, :party_id]
652
+ # ====== Remove the index named by_branch_party in the accounts table
653
+ # t.remove_index :name => :by_branch_party
654
+ def remove_index(options = {})
655
+ @base.remove_index(@table_name, options)
656
+ end
657
+
658
+ # Removes the timestamp columns (created_at and updated_at) from the table.
659
+ # ===== Example
660
+ # t.remove_timestamps
661
+ def remove_timestamps
662
+ @base.remove_timestamps(@table_name)
663
+ end
664
+
665
+ # Renames a column.
666
+ # ===== Example
667
+ # t.rename(:description, :name)
668
+ def rename(column_name, new_column_name)
669
+ @base.rename_column(@table_name, column_name, new_column_name)
670
+ end
671
+
672
+ # Adds a reference. Optionally adds a +type+ column.
673
+ # <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
674
+ # ===== Examples
675
+ # t.references(:goat)
676
+ # t.references(:goat, :polymorphic => true)
677
+ # t.belongs_to(:goat)
678
+ def references(*args)
679
+ options = args.extract_options!
680
+ polymorphic = options.delete(:polymorphic)
681
+ args.each do |col|
682
+ @base.add_column(@table_name, "#{col}_id", :integer, options)
683
+ @base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
684
+ end
685
+ end
686
+ alias :belongs_to :references
687
+
688
+ # Removes a reference. Optionally removes a +type+ column.
689
+ # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
690
+ # ===== Examples
691
+ # t.remove_references(:goat)
692
+ # t.remove_references(:goat, :polymorphic => true)
693
+ # t.remove_belongs_to(:goat)
694
+ def remove_references(*args)
695
+ options = args.extract_options!
696
+ polymorphic = options.delete(:polymorphic)
697
+ args.each do |col|
698
+ @base.remove_column(@table_name, "#{col}_id")
699
+ @base.remove_column(@table_name, "#{col}_type") unless polymorphic.nil?
700
+ end
701
+ end
702
+ alias :remove_belongs_to :remove_references
703
+
704
+ # Adds a column or columns of a specified type
705
+ # ===== Examples
706
+ # t.string(:goat)
707
+ # t.string(:goat, :sheep)
708
+ %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
709
+ class_eval <<-EOV, __FILE__, __LINE__ + 1
710
+ def #{column_type}(*args) # def string(*args)
711
+ options = args.extract_options! # options = args.extract_options!
712
+ column_names = args # column_names = args
713
+ #
714
+ column_names.each do |name| # column_names.each do |name|
715
+ column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string')
716
+ if options[:limit] # if options[:limit]
717
+ column.limit = options[:limit] # column.limit = options[:limit]
718
+ elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash)
719
+ column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit]
720
+ end # end
721
+ column.precision = options[:precision] # column.precision = options[:precision]
722
+ column.scale = options[:scale] # column.scale = options[:scale]
723
+ column.default = options[:default] # column.default = options[:default]
724
+ column.null = options[:null] # column.null = options[:null]
725
+ @base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
726
+ end # end
727
+ end # end
728
+ EOV
729
+ end
730
+
731
+ private
732
+ def native
733
+ @base.native_database_types
734
+ end
735
+ end
736
+
737
+ end
738
+ end
739
+