activerecord 1.10.1 → 1.11.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 (84) hide show
  1. data/CHANGELOG +187 -19
  2. data/RUNNING_UNIT_TESTS +11 -0
  3. data/lib/active_record.rb +3 -1
  4. data/lib/active_record/acts/list.rb +25 -14
  5. data/lib/active_record/acts/nested_set.rb +4 -4
  6. data/lib/active_record/acts/tree.rb +18 -1
  7. data/lib/active_record/associations.rb +90 -17
  8. data/lib/active_record/associations/association_collection.rb +44 -5
  9. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +17 -4
  10. data/lib/active_record/associations/has_many_association.rb +13 -3
  11. data/lib/active_record/associations/has_one_association.rb +19 -0
  12. data/lib/active_record/base.rb +292 -268
  13. data/lib/active_record/callbacks.rb +14 -14
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +137 -75
  15. data/lib/active_record/connection_adapters/db2_adapter.rb +10 -8
  16. data/lib/active_record/connection_adapters/mysql_adapter.rb +91 -64
  17. data/lib/active_record/connection_adapters/oci_adapter.rb +6 -6
  18. data/lib/active_record/connection_adapters/postgresql_adapter.rb +113 -60
  19. data/lib/active_record/connection_adapters/sqlite_adapter.rb +15 -12
  20. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +159 -132
  21. data/lib/active_record/fixtures.rb +59 -12
  22. data/lib/active_record/locking.rb +10 -9
  23. data/lib/active_record/migration.rb +112 -5
  24. data/lib/active_record/query_cache.rb +64 -0
  25. data/lib/active_record/timestamp.rb +10 -8
  26. data/lib/active_record/validations.rb +121 -26
  27. data/rakefile +16 -10
  28. data/test/aaa_create_tables_test.rb +26 -48
  29. data/test/abstract_unit.rb +3 -0
  30. data/test/aggregations_test.rb +19 -19
  31. data/test/association_callbacks_test.rb +110 -0
  32. data/test/associations_go_eager_test.rb +48 -14
  33. data/test/associations_test.rb +344 -142
  34. data/test/base_test.rb +150 -31
  35. data/test/binary_test.rb +7 -0
  36. data/test/callbacks_test.rb +24 -5
  37. data/test/column_alias_test.rb +2 -2
  38. data/test/connections/native_sqlserver_odbc/connection.rb +26 -0
  39. data/test/deprecated_associations_test.rb +27 -28
  40. data/test/deprecated_finder_test.rb +8 -9
  41. data/test/finder_test.rb +52 -17
  42. data/test/fixtures/author.rb +39 -0
  43. data/test/fixtures/categories.yml +7 -0
  44. data/test/fixtures/categories_posts.yml +8 -0
  45. data/test/fixtures/category.rb +2 -0
  46. data/test/fixtures/comment.rb +3 -1
  47. data/test/fixtures/comments.yml +43 -1
  48. data/test/fixtures/companies.yml +14 -0
  49. data/test/fixtures/company.rb +1 -1
  50. data/test/fixtures/computers.yml +2 -1
  51. data/test/fixtures/db_definitions/db2.sql +7 -2
  52. data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
  53. data/test/fixtures/db_definitions/mysql.sql +11 -6
  54. data/test/fixtures/db_definitions/oci.sql +7 -2
  55. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  56. data/test/fixtures/db_definitions/postgresql.sql +8 -5
  57. data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
  58. data/test/fixtures/db_definitions/sqlite.sql +9 -4
  59. data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
  60. data/test/fixtures/db_definitions/sqlserver.sql +12 -7
  61. data/test/fixtures/developer.rb +8 -1
  62. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  63. data/test/fixtures/post.rb +8 -2
  64. data/test/fixtures/posts.yml +21 -0
  65. data/test/fixtures/project.rb +14 -1
  66. data/test/fixtures/subscriber.rb +3 -0
  67. data/test/fixtures_test.rb +14 -0
  68. data/test/inheritance_test.rb +30 -22
  69. data/test/lifecycle_test.rb +3 -4
  70. data/test/locking_test.rb +2 -4
  71. data/test/migration_test.rb +186 -0
  72. data/test/mixin_nested_set_test.rb +19 -19
  73. data/test/mixin_test.rb +88 -88
  74. data/test/modules_test.rb +5 -10
  75. data/test/multiple_db_test.rb +2 -0
  76. data/test/pk_test.rb +8 -12
  77. data/test/reflection_test.rb +8 -4
  78. data/test/schema_test_postgresql.rb +63 -0
  79. data/test/thread_safety_test.rb +4 -1
  80. data/test/transactions_test.rb +9 -2
  81. data/test/unconnected_test.rb +1 -0
  82. data/test/validations_test.rb +151 -8
  83. metadata +11 -5
  84. data/test/migration_mysql.rb +0 -104
@@ -153,7 +153,7 @@ module ActiveRecord
153
153
  #
154
154
  # == The after_find and after_initialize exceptions
155
155
  #
156
- # Because after_find and after_initialize is called for each object instantiated found by a finder, such as Base.find_all, we've had
156
+ # Because after_find and after_initialize is called for each object instantiated found by a finder, such as Base.find(:all), we've had
157
157
  # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, after_find and
158
158
  # after_initialize will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
159
159
  # callback types will be called.
@@ -213,17 +213,13 @@ module ActiveRecord
213
213
  module ClassMethods #:nodoc:
214
214
  def instantiate_with_callbacks(record)
215
215
  object = instantiate_without_callbacks(record)
216
-
217
- if object.send(:respond_to_without_attributes?, :after_find)
216
+
217
+ if object.respond_to_without_attributes?(:after_find)
218
218
  object.send(:callback, :after_find)
219
- else
220
- object.send(:invoke_and_notify, :after_find)
221
219
  end
222
220
 
223
- if object.send(:respond_to_without_attributes?, :after_initialize)
221
+ if object.respond_to_without_attributes?(:after_initialize)
224
222
  object.send(:callback, :after_initialize)
225
- else
226
- object.send(:invoke_and_notify, :after_initialize)
227
223
  end
228
224
 
229
225
  object
@@ -231,14 +227,15 @@ module ActiveRecord
231
227
  end
232
228
 
233
229
  # Is called when the object was instantiated by one of the finders, like Base.find.
234
- # def after_find() end
230
+ #def after_find() end
235
231
 
236
232
  # Is called after the object has been instantiated by a call to Base.new.
237
- # def after_initialize() end
233
+ #def after_initialize() end
234
+
238
235
  def initialize_with_callbacks(attributes = nil) #:nodoc:
239
236
  initialize_without_callbacks(attributes)
240
237
  result = yield self if block_given?
241
- respond_to_without_attributes?(:after_initialize) ? callback(:after_initialize) : invoke_and_notify(:after_initialize)
238
+ callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
242
239
  result
243
240
  end
244
241
 
@@ -328,6 +325,8 @@ module ActiveRecord
328
325
 
329
326
  private
330
327
  def callback(method)
328
+ notify(method)
329
+
331
330
  callbacks_for(method).each do |callback|
332
331
  result = case callback
333
332
  when Symbol
@@ -346,8 +345,9 @@ module ActiveRecord
346
345
  return false if result == false
347
346
  end
348
347
 
349
- invoke_and_notify(method)
350
- true
348
+ send(method) if respond_to_without_attributes?(method)
349
+
350
+ return true
351
351
  end
352
352
 
353
353
  def callbacks_for(method)
@@ -355,8 +355,8 @@ module ActiveRecord
355
355
  end
356
356
 
357
357
  def invoke_and_notify(method)
358
- send(method) if respond_to_without_attributes?(method)
359
358
  notify(method)
359
+ send(method) if respond_to_without_attributes?(method)
360
360
  end
361
361
 
362
362
  def notify(method) #:nodoc:
@@ -16,7 +16,7 @@ def require_library_or_gem(library_name)
16
16
  raise cannot_require
17
17
  end
18
18
  # 2. Rubygems is installed and loaded. Try to load the library again
19
- begin
19
+ begin
20
20
  require library_name
21
21
  rescue LoadError => gem_not_installed
22
22
  raise cannot_require
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
  @config, @adapter_method = config, adapter_method
33
33
  end
34
34
  end
35
-
35
+
36
36
  # The class -> [adapter_method, config] map
37
37
  @@defined_connections = {}
38
38
 
@@ -92,32 +92,32 @@ module ActiveRecord
92
92
  # for (not necessarily the current class).
93
93
  def self.retrieve_connection #:nodoc:
94
94
  klass = self
95
- until klass == ActiveRecord::Base.superclass
96
- Thread.current['active_connections'] ||= {}
97
- if Thread.current['active_connections'][klass]
98
- return Thread.current['active_connections'][klass]
99
- elsif @@defined_connections[klass]
100
- klass.connection = @@defined_connections[klass]
95
+ ar_super = ActiveRecord::Base.superclass
96
+ until klass == ar_super
97
+ if conn = (Thread.current['active_connections'] ||= {})[klass]
98
+ return conn
99
+ elsif conn = @@defined_connections[klass]
100
+ klass.connection = conn
101
101
  return self.connection
102
102
  end
103
103
  klass = klass.superclass
104
104
  end
105
105
  raise ConnectionNotEstablished
106
106
  end
107
-
107
+
108
108
  # Returns true if a connection that's accessible to this class have already been opened.
109
109
  def self.connected?
110
110
  klass = self
111
111
  until klass == ActiveRecord::Base.superclass
112
112
  if Thread.current['active_connections'].is_a?(Hash) && Thread.current['active_connections'][klass]
113
- return true
113
+ return true
114
114
  else
115
115
  klass = klass.superclass
116
116
  end
117
117
  end
118
118
  return false
119
119
  end
120
-
120
+
121
121
  # Remove the connection for this class. This will close the active
122
122
  # connection and the defined connection (if they exist). The result
123
123
  # can be used as argument for establish_connection, for easy
@@ -129,7 +129,7 @@ module ActiveRecord
129
129
  Thread.current['active_connections'][klass] = nil
130
130
  conn.config if conn
131
131
  end
132
-
132
+
133
133
  # Set the connection for the class.
134
134
  def self.connection=(spec)
135
135
  raise ConnectionNotEstablished unless spec
@@ -152,14 +152,10 @@ module ActiveRecord
152
152
  # The type parameter should either contain :integer, :float, :datetime, :date, :text, or :string
153
153
  # The sql_type is just used for extracting the limit, such as 10 in "varchar(10)"
154
154
  def initialize(name, default, sql_type = nil)
155
- @name, @default, @type = name, default, simplified_type(sql_type)
155
+ @name, @default, @type = name, type_cast(default), simplified_type(sql_type)
156
156
  @limit = extract_limit(sql_type) unless sql_type.nil?
157
157
  end
158
158
 
159
- def default
160
- type_cast(@default)
161
- end
162
-
163
159
  def klass
164
160
  case type
165
161
  when :integer then Fixnum
@@ -173,7 +169,7 @@ module ActiveRecord
173
169
  when :boolean then Object
174
170
  end
175
171
  end
176
-
172
+
177
173
  def type_cast(value)
178
174
  if value.nil? then return nil end
179
175
  case type
@@ -190,18 +186,18 @@ module ActiveRecord
190
186
  else value
191
187
  end
192
188
  end
193
-
189
+
194
190
  def human_name
195
191
  Base.human_attribute_name(@name)
196
192
  end
197
-
193
+
198
194
  def string_to_binary(value)
199
195
  value
200
- end
196
+ end
201
197
 
202
198
  def binary_to_string(value)
203
199
  value
204
- end
200
+ end
205
201
 
206
202
  private
207
203
  def string_to_date(string)
@@ -210,7 +206,7 @@ module ActiveRecord
210
206
  # treat 0000-00-00 as nil
211
207
  Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
212
208
  end
213
-
209
+
214
210
  def string_to_time(string)
215
211
  return string unless string.is_a?(String)
216
212
  time_array = ParseDate.parsedate(string.to_s).compact
@@ -224,12 +220,12 @@ module ActiveRecord
224
220
  # pad the resulting array with dummy date information
225
221
  time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
226
222
  Time.send(Base.default_timezone, *time_array) rescue nil
227
- end
228
-
223
+ end
224
+
229
225
  def extract_limit(sql_type)
230
226
  $1.to_i if sql_type =~ /\((.*)\)/
231
227
  end
232
-
228
+
233
229
  def simplified_type(field_type)
234
230
  case field_type
235
231
  when /int/i
@@ -262,8 +258,6 @@ module ActiveRecord
262
258
  class AbstractAdapter
263
259
  @@row_even = true
264
260
 
265
- include Benchmark
266
-
267
261
  def initialize(connection, logger = nil) # :nodoc:
268
262
  @connection, @logger = connection, logger
269
263
  @runtime = 0
@@ -271,7 +265,7 @@ module ActiveRecord
271
265
 
272
266
  # Returns an array of record hashes with the column names as a keys and fields as values.
273
267
  def select_all(sql, name = nil) end
274
-
268
+
275
269
  # Returns a record hash with the column names as a keys and fields as values.
276
270
  def select_one(sql, name = nil) end
277
271
 
@@ -310,8 +304,8 @@ module ActiveRecord
310
304
 
311
305
  # Begins the transaction (and turns off auto-committing).
312
306
  def begin_db_transaction() end
313
-
314
- # Commits the transaction (and turns on auto-committing).
307
+
308
+ # Commits the transaction (and turns on auto-committing).
315
309
  def commit_db_transaction() end
316
310
 
317
311
  # Rolls back the transaction (and turns on auto-committing). Must be done if the transaction block
@@ -320,7 +314,7 @@ module ActiveRecord
320
314
 
321
315
  def quote(value, column = nil)
322
316
  case value
323
- when String
317
+ when String
324
318
  if column && column.type == :binary
325
319
  "'#{quote_string(column.string_to_binary(value))}'" # ' (for ruby-mode)
326
320
  else
@@ -330,7 +324,7 @@ module ActiveRecord
330
324
  when TrueClass then (column && column.type == :boolean ? "'t'" : "1")
331
325
  when FalseClass then (column && column.type == :boolean ? "'f'" : "0")
332
326
  when Float, Fixnum, Bignum then value.to_s
333
- when Date then "'#{value.to_s}'"
327
+ when Date then "'#{value.to_s}'"
334
328
  when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
335
329
  else "'#{quote_string(value.to_yaml)}'"
336
330
  end
@@ -352,36 +346,37 @@ module ActiveRecord
352
346
  # Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database.
353
347
  def structure_dump() end
354
348
 
355
- def add_limit!(sql, limit)
356
- if limit.is_a? Array
357
- limit, offset = *limit
358
- add_limit_with_offset!(sql, limit.to_i, offset.to_i)
359
- else
360
- add_limit_without_offset!(sql, limit)
361
- end
362
- end
363
-
364
- def add_limit_with_offset!(sql, limit, offset)
365
- sql << " LIMIT #{limit} OFFSET #{offset}"
349
+ def add_limit!(sql, options)
350
+ return unless options
351
+ add_limit_offset!(sql, options)
366
352
  end
367
-
368
- def add_limit_without_offset!(sql, limit)
369
- sql << " LIMIT #{limit}"
353
+
354
+ def add_limit_offset!(sql, options)
355
+ return if options[:limit].nil?
356
+ sql << " LIMIT #{options[:limit]}"
357
+ sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil?
370
358
  end
371
359
 
360
+
372
361
  def initialize_schema_information
373
362
  begin
374
- execute "CREATE TABLE schema_info (version #{native_database_types[:integer][:name]}#{native_database_types[:integer][:limit]})"
363
+ execute "CREATE TABLE schema_info (version #{type_to_sql(:integer)})"
375
364
  insert "INSERT INTO schema_info (version) VALUES(0)"
376
365
  rescue ActiveRecord::StatementInvalid
377
366
  # Schema has been intialized
378
367
  end
379
368
  end
380
-
381
- def create_table(name, options = "")
382
- execute "CREATE TABLE #{name} (id #{native_database_types[:primary_key]}) #{options}"
383
- table_definition = yield TableDefinition.new
384
- table_definition.columns.each { |column_name, type, options| add_column(name, column_name, type, options) }
369
+
370
+ def create_table(name, options = {})
371
+ table_definition = TableDefinition.new(self)
372
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
373
+
374
+ yield table_definition
375
+ create_sql = "CREATE TABLE #{name} ("
376
+ create_sql << table_definition.to_sql
377
+ create_sql << ") #{options[:options]}"
378
+
379
+ execute create_sql
385
380
  end
386
381
 
387
382
  def drop_table(name)
@@ -390,29 +385,68 @@ module ActiveRecord
390
385
 
391
386
  def add_column(table_name, column_name, type, options = {})
392
387
  native_type = native_database_types[type]
393
- add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{native_type[:name]}"
394
- add_column_sql << "(#{options[:limit] || native_type[:limit]})" if options[:limit] || native_type[:limit]
395
- add_column_sql << " DEFAULT '#{options[:default]}'" if options[:default]
388
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
389
+ add_column_options!(add_column_sql, options)
396
390
  execute(add_column_sql)
397
391
  end
398
-
392
+
399
393
  def remove_column(table_name, column_name)
400
394
  execute "ALTER TABLE #{table_name} DROP #{column_name}"
395
+ end
396
+
397
+ def change_column(table_name, column_name, type, options = {})
398
+ raise NotImplementedError, "change_column is not implemented"
399
+ end
400
+
401
+ def change_column_default(table_name, column_name, default)
402
+ raise NotImplementedError, "change_column_default is not implemented"
403
+ end
404
+
405
+ def supports_migrations?
406
+ false
407
+ end
408
+
409
+ def rename_column(table_name, column_name, new_column_name)
410
+ raise NotImplementedError, "rename_column is not implemented"
401
411
  end
402
412
 
413
+ def add_index(table_name, column_name, index_type = '')
414
+ execute "CREATE #{index_type} INDEX #{table_name}_#{column_name.to_a.first}_index ON #{table_name} (#{column_name.to_a.join(", ")})"
415
+ end
403
416
 
404
- protected
405
- def log(sql, name, connection = nil)
406
- connection ||= @connection
417
+ def remove_index(table_name, column_name)
418
+ execute "DROP INDEX #{table_name}_#{column_name}_index ON #{table_name}"
419
+ end
420
+
421
+ def supports_migrations?
422
+ false
423
+ end
424
+
425
+ def native_database_types
426
+ {}
427
+ end
428
+
429
+ def type_to_sql(type, limit = nil)
430
+ native = native_database_types[type]
431
+ limit ||= native[:limit]
432
+ column_type_sql = native[:name]
433
+ column_type_sql << "(#{limit})" if limit
434
+ column_type_sql
435
+ end
436
+
437
+ protected
438
+ def log(sql, name)
407
439
  begin
408
- if @logger.nil? || @logger.level > Logger::INFO
409
- yield connection
410
- elsif block_given?
411
- result = nil
412
- bm = measure { result = yield connection }
413
- @runtime += bm.real
414
- log_info(sql, name, bm.real)
415
- result
440
+ if block_given?
441
+ if @logger and @logger.level <= Logger::INFO
442
+ result = nil
443
+ seconds = Benchmark.realtime { result = yield }
444
+ @runtime += seconds
445
+ log_info(sql, name, seconds)
446
+ result
447
+ else
448
+ yield
449
+ end
416
450
  else
417
451
  log_info(sql, name, 0)
418
452
  nil
@@ -424,11 +458,11 @@ module ActiveRecord
424
458
  end
425
459
 
426
460
  def log_info(sql, name, runtime)
427
- if @logger.nil? then return end
461
+ return unless @logger
428
462
 
429
- @logger.info(
463
+ @logger.debug(
430
464
  format_log_entry(
431
- "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
465
+ "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
432
466
  sql.gsub(/ +/, " ")
433
467
  )
434
468
  )
@@ -441,7 +475,7 @@ module ActiveRecord
441
475
  else
442
476
  @@row_even = true; caller_color = "1;36"; message_color = "4;35"; dump_color = "0;37"
443
477
  end
444
-
478
+
445
479
  log_entry = " \e[#{message_color}m#{message}\e[m"
446
480
  log_entry << " \e[#{dump_color}m%s\e[m" % dump if dump.kind_of?(String) && !dump.nil?
447
481
  log_entry << " \e[#{dump_color}m%p\e[m" % dump if !dump.kind_of?(String) && !dump.nil?
@@ -450,19 +484,47 @@ module ActiveRecord
450
484
  "%s %s" % [message, dump]
451
485
  end
452
486
  end
487
+
488
+ def add_column_options!(sql, options)
489
+ sql << " DEFAULT '#{options[:default]}'" unless options[:default].nil?
490
+ end
453
491
  end
454
492
 
455
493
  class TableDefinition
456
494
  attr_accessor :columns
457
-
458
- def initialize
495
+
496
+ def initialize(base)
459
497
  @columns = []
498
+ @base = base
499
+ end
500
+
501
+ def primary_key(name)
502
+ @columns << "#{name} #{native[:primary_key]}"
503
+ self
460
504
  end
461
505
 
462
506
  def column(name, type, options = {})
463
- @columns << [ name, type, options ]
507
+ limit = options[:limit] || native[type.to_sym][:limit]
508
+
509
+ column_sql = "#{name} #{type_to_sql(type.to_sym, options[:limit])}"
510
+ column_sql << " DEFAULT '#{options[:default]}'" if options[:default]
511
+ @columns << column_sql
464
512
  self
465
513
  end
514
+
515
+ def to_sql
516
+ @columns.join(", ")
517
+ end
518
+
519
+ private
520
+
521
+ def type_to_sql(name, limit)
522
+ @base.type_to_sql(name, limit)
523
+ end
524
+
525
+ def native
526
+ @base.native_database_types
527
+ end
466
528
  end
467
529
  end
468
530
  end