activerecord 1.12.2 → 1.13.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 (66) hide show
  1. data/CHANGELOG +92 -0
  2. data/README +9 -9
  3. data/lib/active_record/acts/list.rb +1 -1
  4. data/lib/active_record/acts/nested_set.rb +13 -13
  5. data/lib/active_record/acts/tree.rb +7 -6
  6. data/lib/active_record/aggregations.rb +4 -4
  7. data/lib/active_record/associations.rb +82 -21
  8. data/lib/active_record/associations/association_collection.rb +0 -8
  9. data/lib/active_record/associations/association_proxy.rb +5 -2
  10. data/lib/active_record/associations/belongs_to_association.rb +6 -2
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +11 -1
  12. data/lib/active_record/associations/has_many_association.rb +34 -5
  13. data/lib/active_record/associations/has_one_association.rb +1 -1
  14. data/lib/active_record/base.rb +144 -59
  15. data/lib/active_record/callbacks.rb +6 -6
  16. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
  17. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
  18. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -8
  19. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -1
  20. data/lib/active_record/connection_adapters/db2_adapter.rb +17 -1
  21. data/lib/active_record/connection_adapters/oci_adapter.rb +322 -185
  22. data/lib/active_record/connection_adapters/sqlite_adapter.rb +9 -8
  23. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +107 -14
  24. data/lib/active_record/fixtures.rb +10 -8
  25. data/lib/active_record/migration.rb +20 -6
  26. data/lib/active_record/observer.rb +4 -3
  27. data/lib/active_record/reflection.rb +5 -5
  28. data/lib/active_record/transactions.rb +2 -2
  29. data/lib/active_record/validations.rb +70 -40
  30. data/lib/active_record/vendor/mysql411.rb +9 -13
  31. data/lib/active_record/version.rb +2 -2
  32. data/rakefile +1 -1
  33. data/test/abstract_unit.rb +5 -0
  34. data/test/ar_schema_test.rb +1 -1
  35. data/test/associations_extensions_test.rb +37 -0
  36. data/test/associations_go_eager_test.rb +25 -0
  37. data/test/associations_test.rb +14 -6
  38. data/test/base_test.rb +63 -45
  39. data/test/connections/native_sqlite/connection.rb +2 -2
  40. data/test/connections/native_sqlite3/connection.rb +2 -2
  41. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  42. data/test/debug.log +2857 -0
  43. data/test/deprecated_finder_test.rb +3 -9
  44. data/test/finder_test.rb +27 -13
  45. data/test/fixtures/author.rb +4 -0
  46. data/test/fixtures/comment.rb +4 -8
  47. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -5
  48. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -5
  49. data/test/fixtures/db_definitions/db2.drop.sql +0 -1
  50. data/test/fixtures/db_definitions/oci.sql +2 -0
  51. data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -1
  52. data/test/fixtures/developer.rb +18 -1
  53. data/test/fixtures/fixture_database.sqlite +0 -0
  54. data/test/fixtures/fixture_database_2.sqlite +0 -0
  55. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  56. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  57. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  58. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  59. data/test/fixtures/post.rb +18 -4
  60. data/test/fixtures/reply.rb +3 -1
  61. data/test/inheritance_test.rb +3 -2
  62. data/test/{conditions_scoping_test.rb → method_scoping_test.rb} +55 -10
  63. data/test/migration_test.rb +68 -31
  64. data/test/readonly_test.rb +65 -5
  65. data/test/validations_test.rb +10 -2
  66. metadata +13 -4
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  end
16
16
 
17
17
  db = SQLite3::Database.new(
18
- config[:dbfile],
18
+ config[:database],
19
19
  :results_as_hash => true,
20
20
  :type_translation => false
21
21
  )
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  unless self.class.const_defined?(:SQLite)
30
30
  require_library_or_gem(config[:adapter])
31
31
 
32
- db = SQLite::Database.new(config[:dbfile], 0)
32
+ db = SQLite::Database.new(config[:database], 0)
33
33
  db.show_datatypes = "ON" if !defined? SQLite::Version
34
34
  db.results_as_hash = true if defined? SQLite::Version
35
35
  db.type_translation = false
@@ -45,16 +45,17 @@ module ActiveRecord
45
45
 
46
46
  private
47
47
  def parse_config!(config)
48
- # Require dbfile.
49
- unless config.has_key?(:dbfile)
50
- raise ArgumentError, "No database file specified. Missing argument: dbfile"
48
+ config[:database] ||= config[:dbfile]
49
+ # Require database.
50
+ unless config.has_key?(:database)
51
+ raise ArgumentError, "No database file specified. Missing argument: database"
51
52
  end
52
53
 
53
54
  # Allow database path relative to RAILS_ROOT, but only if
54
55
  # the database path is not the special path that tells
55
56
  # Sqlite build a database only in memory.
56
- if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:dbfile]
57
- config[:dbfile] = File.expand_path(config[:dbfile], RAILS_ROOT)
57
+ if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
58
+ config[:database] = File.expand_path(config[:database], RAILS_ROOT)
58
59
  end
59
60
  end
60
61
  end
@@ -88,7 +89,7 @@ module ActiveRecord
88
89
  #
89
90
  # Options:
90
91
  #
91
- # * <tt>:dbfile</tt> -- Path to the database file.
92
+ # * <tt>:database</tt> -- Path to the database file.
92
93
  class SQLiteAdapter < AbstractAdapter
93
94
  def adapter_name #:nodoc:
94
95
  'SQLite'
@@ -11,6 +11,12 @@ require 'active_record/connection_adapters/abstract_adapter'
11
11
  # Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
12
12
  # Date: 6/26/2005
13
13
  #
14
+ # Current maintainer: Ryan Tomayko <rtomayko@gmail.com>
15
+ #
16
+ # Modifications (Migrations): Tom Ward <tom@popdog.net>
17
+ # Date: 27/10/2005
18
+ #
19
+
14
20
  module ActiveRecord
15
21
  class Base
16
22
  def self.sqlserver_connection(config) #:nodoc:
@@ -71,6 +77,8 @@ module ActiveRecord
71
77
  when :datetime then cast_to_datetime(value)
72
78
  when :timestamp then cast_to_time(value)
73
79
  when :time then cast_to_time(value)
80
+ when :date then cast_to_datetime(value)
81
+ when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
74
82
  else value
75
83
  end
76
84
  end
@@ -167,7 +175,7 @@ module ActiveRecord
167
175
  :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
168
176
  :string => { :name => "varchar", :limit => 255 },
169
177
  :text => { :name => "text" },
170
- :integer => { :name => "int"},
178
+ :integer => { :name => "int" },
171
179
  :float => { :name => "float", :limit => 8 },
172
180
  :datetime => { :name => "datetime" },
173
181
  :timestamp => { :name => "datetime" },
@@ -181,13 +189,17 @@ module ActiveRecord
181
189
  def adapter_name
182
190
  'SQLServer'
183
191
  end
192
+
193
+ def supports_migrations? #:nodoc:
194
+ true
195
+ end
184
196
 
185
197
  def select_all(sql, name = nil)
186
198
  select(sql, name)
187
199
  end
188
200
 
189
201
  def select_one(sql, name = nil)
190
- add_limit!(sql, nil)
202
+ add_limit!(sql, :limit => 1)
191
203
  result = select(sql, name)
192
204
  result.nil? ? nil : result.first
193
205
  end
@@ -306,15 +318,27 @@ module ActiveRecord
306
318
  end
307
319
 
308
320
  def add_limit_offset!(sql, options)
309
- if options.has_key?(:limit) and options.has_key?(:offset) and !options[:limit].nil? and !options[:offset].nil?
310
- options[:order] ||= "id ASC"
311
- total_rows = @connection.select_all("SELECT count(*) as TotalRows from #{get_table_name(sql)}")[0][:TotalRows].to_i
312
- if (options[:limit] + options[:offset]) > total_rows
313
- options[:limit] = (total_rows - options[:offset] > 0) ? (total_rows - options[:offset]) : 1
321
+ if options[:limit] and options[:offset]
322
+ total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/SELECT/i, "SELECT TOP 1000000000")}) tally")[0][:TotalRows].to_i
323
+ if (options[:limit] + options[:offset]) >= total_rows
324
+ options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
314
325
  end
315
- sql.gsub!(/SELECT/i, "SELECT * FROM ( SELECT TOP #{options[:limit]} * FROM ( SELECT TOP #{options[:limit] + options[:offset]}")<<" ) AS tmp1 ORDER BY #{change_order_direction(options[:order])} ) AS tmp2 ORDER BY #{options[:order]}"
316
- else
317
- sql.gsub!(/SELECT/i, "SELECT TOP #{options[:limit]}") unless options[:limit].nil?
326
+ sql.sub!(/^\s*SELECT/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT TOP #{options[:limit] + options[:offset]} ")
327
+ sql << ") AS tmp1"
328
+ if options[:order]
329
+ options[:order] = options[:order].split(',').map do |field|
330
+ parts = field.split(" ")
331
+ if sql =~ /#{parts[0]} AS (t\d_r\d\d?)/
332
+ parts[0] = $1
333
+ end
334
+ parts.join(' ')
335
+ end.join(', ')
336
+ sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
337
+ else
338
+ sql << " ) AS tmp2"
339
+ end
340
+ elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
341
+ sql.sub!(/^\s*SELECT/i, "SELECT TOP #{options[:limit]}") unless options[:limit].nil?
318
342
  end
319
343
  end
320
344
 
@@ -331,6 +355,74 @@ module ActiveRecord
331
355
  execute "CREATE DATABASE #{name}"
332
356
  end
333
357
 
358
+ def tables(name = nil)
359
+ execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name).inject([]) do |tables, field|
360
+ table_name = field[0]
361
+ tables << table_name unless table_name == 'dtproperties'
362
+ tables
363
+ end
364
+ end
365
+
366
+ def indexes(table_name, name = nil)
367
+ indexes = []
368
+ execute("EXEC sp_helpindex #{table_name}", name).each do |index|
369
+ unique = index[1] =~ /unique/
370
+ primary = index[1] =~ /primary key/
371
+ if !primary
372
+ indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
373
+ end
374
+ end
375
+ indexes
376
+ end
377
+
378
+ def rename_table(name, new_name)
379
+ execute "EXEC sp_rename '#{name}', '#{new_name}'"
380
+ end
381
+
382
+ def remove_column(table_name, column_name)
383
+ execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
384
+ end
385
+
386
+ def rename_column(table, column, new_column_name)
387
+ execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
388
+ end
389
+
390
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
391
+ sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"]
392
+ if options[:default]
393
+ remove_default_constraint(table_name, column_name)
394
+ sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
395
+ end
396
+ sql_commands.each {|c|
397
+ execute(c)
398
+ }
399
+ end
400
+
401
+ def remove_column(table_name, column_name)
402
+ remove_default_constraint(table_name, column_name)
403
+ execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
404
+ end
405
+
406
+ def remove_default_constraint(table_name, column_name)
407
+ defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
408
+ defaults.each {|constraint|
409
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
410
+ }
411
+ end
412
+
413
+ def remove_index(table_name, options = {})
414
+ execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
415
+ end
416
+
417
+ def type_to_sql(type, limit = nil) #:nodoc:
418
+ native = native_database_types[type]
419
+ # if there's no :limit in the default type definition, assume that type doesn't support limits
420
+ limit = native[:limit] ? limit || native[:limit] : nil
421
+ column_type_sql = native[:name]
422
+ column_type_sql << "(#{limit})" if limit
423
+ column_type_sql
424
+ end
425
+
334
426
  private
335
427
  def select(sql, name = nil)
336
428
  rows = []
@@ -355,9 +447,9 @@ module ActiveRecord
355
447
  end
356
448
 
357
449
  def get_table_name(sql)
358
- if sql =~ /into\s*([^\s]+)\s*|update\s*([^\s]+)\s*/i
450
+ if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
359
451
  $1
360
- elsif sql =~ /from\s*([^\s]+)\s*/i
452
+ elsif sql =~ /from\s+([^\(\s]+)\s*/i
361
453
  $1
362
454
  else
363
455
  nil
@@ -392,8 +484,8 @@ module ActiveRecord
392
484
 
393
485
  def get_special_columns(table_name)
394
486
  special = []
395
- @table_columns = {} unless @table_columns
396
- @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
487
+ @table_columns ||= {}
488
+ @table_columns[table_name] ||= columns(table_name)
397
489
  @table_columns[table_name].each do |col|
398
490
  special << col.name if col.is_special
399
491
  end
@@ -404,6 +496,7 @@ module ActiveRecord
404
496
  special_cols = get_special_columns(get_table_name(sql))
405
497
  for col in special_cols.to_a
406
498
  sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
499
+ sql.gsub!(/ORDER BY #{col.to_s}/i, '')
407
500
  end
408
501
  sql
409
502
  end
@@ -20,7 +20,7 @@ end
20
20
  # This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
21
21
  # in a non-verbose, humanly-readable format. It ships with Ruby 1.8.1+.
22
22
  #
23
- # Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which is place in the directory appointed
23
+ # Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
24
24
  # by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
25
25
  # put your files in <your-rails-app>/test/fixtures/). The fixture file ends with the .yml file extension (Rails example:
26
26
  # "<your-rails-app>/test/fixtures/web_sites.yml"). The format of a YAML fixture file looks like this:
@@ -56,7 +56,7 @@ end
56
56
  # = CSV fixtures
57
57
  #
58
58
  # Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
59
- # in a single file, but, instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
59
+ # in a single file, but instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
60
60
  #
61
61
  # The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
62
62
  # humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
@@ -103,7 +103,7 @@ end
103
103
  # = Using Fixtures
104
104
  #
105
105
  # Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
106
- # fixtures, but first lets take a look at a sample unit test found:
106
+ # fixtures, but first let's take a look at a sample unit test found:
107
107
  #
108
108
  # require 'web_site'
109
109
  #
@@ -139,7 +139,7 @@ end
139
139
  #
140
140
  # As seen above, the data hash created from the YAML fixtures would have @web_sites["rubyonrails"]["url"] return
141
141
  # "http://www.rubyonrails.org" and @web_sites["google"]["name"] would return "Google". The same fixtures, but loaded
142
- # from a CSV fixture file would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
142
+ # from a CSV fixture file, would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
143
143
  # fixtures available as instance variables @web_site_1 and @web_site_2.
144
144
  #
145
145
  # If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
@@ -218,7 +218,7 @@ class Fixtures < YAML::Omap
218
218
  def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)
219
219
  object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
220
220
  if load_instances
221
- ActiveRecord::Base.logger.silence do
221
+ ActiveRecord::Base.silence do
222
222
  fixtures.each do |name, fixture|
223
223
  if model = fixture.find
224
224
  object.instance_variable_set "@#{name}", model
@@ -241,7 +241,7 @@ class Fixtures < YAML::Omap
241
241
  table_names = table_names.flatten.map { |n| n.to_s }
242
242
  connection = block_given? ? yield : ActiveRecord::Base.connection
243
243
 
244
- ActiveRecord::Base.logger.silence do
244
+ ActiveRecord::Base.silence do
245
245
  fixtures_map = {}
246
246
  fixtures = table_names.map do |table_name|
247
247
  fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s))
@@ -270,7 +270,7 @@ class Fixtures < YAML::Omap
270
270
  def initialize(connection, table_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
271
271
  @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
272
272
 
273
- @class_name = Inflector.classify(@table_name)
273
+ @class_name = ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize
274
274
  @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
275
275
  read_fixture_files
276
276
  end
@@ -436,8 +436,10 @@ module Test #:nodoc:
436
436
 
437
437
  def self.require_fixture_classes(table_names=nil)
438
438
  (table_names || fixture_table_names).each do |table_name|
439
+ file_name = table_name.to_s
440
+ file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
439
441
  begin
440
- require Inflector.singularize(table_name.to_s)
442
+ require file_name
441
443
  rescue LoadError
442
444
  # Let's hope the developer has included it himself
443
445
  end
@@ -2,6 +2,12 @@ module ActiveRecord
2
2
  class IrreversibleMigration < ActiveRecordError#:nodoc:
3
3
  end
4
4
 
5
+ class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
6
+ def initialize(version)
7
+ super("Multiple migrations have the version number #{version}")
8
+ end
9
+ end
10
+
5
11
  # Migrations can manage the evolution of a schema used by several physical databases. It's a solution
6
12
  # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
7
13
  # push that change to other developers and to the production server. With migrations, you can describe the transformations
@@ -92,6 +98,7 @@ module ActiveRecord
92
98
  #
93
99
  # def self.down
94
100
  # # not much we can do to restore deleted data
101
+ # raise IrreversibleMigration
95
102
  # end
96
103
  # end
97
104
  #
@@ -109,7 +116,7 @@ module ActiveRecord
109
116
  # end
110
117
  # end
111
118
  #
112
- # And some times you need to do something in SQL not abstracted directly by migrations:
119
+ # And sometimes you need to do something in SQL not abstracted directly by migrations:
113
120
  #
114
121
  # class MakeJoinUnique < ActiveRecord::Migration
115
122
  # def self.up
@@ -123,7 +130,7 @@ module ActiveRecord
123
130
  #
124
131
  # == Using the class after changing table
125
132
  #
126
- # Some times you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
133
+ # Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
127
134
  # to make a call to Base#reset_column_information in order to ensure that the class has the latest column data from
128
135
  # after the new column was added. Example:
129
136
  #
@@ -208,15 +215,22 @@ module ActiveRecord
208
215
 
209
216
  private
210
217
  def migration_classes
211
- migrations = migration_files.collect do |migration_file|
218
+ migrations = migration_files.inject([]) do |migrations, migration_file|
212
219
  load(migration_file)
213
220
  version, name = migration_version_and_name(migration_file)
214
- [ version.to_i, migration_class(name) ]
221
+ assert_unique_migration_version(migrations, version.to_i)
222
+ migrations << [ version.to_i, migration_class(name) ]
215
223
  end
216
-
224
+
217
225
  down? ? migrations.sort.reverse : migrations.sort
218
226
  end
219
-
227
+
228
+ def assert_unique_migration_version(migrations, version)
229
+ if !migrations.empty? && migrations.transpose.first.include?(version)
230
+ raise DuplicateMigrationVersionError.new(version)
231
+ end
232
+ end
233
+
220
234
  def migration_files
221
235
  files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f|
222
236
  migration_version_and_name(f).first.to_i
@@ -74,9 +74,10 @@ module ActiveRecord
74
74
  #
75
75
  # == Triggering Observers
76
76
  #
77
- # In order to activate an observer, you need to call Observer.instance. In Rails, this can be done in controllers
78
- # using the short-hand of for example observer :comment_observer. Or directly from Active Record, with
79
- # ActiveRecord::Base.observer(:comment_observer).
77
+ # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
78
+ # <tt>config/environment.rb</tt> file.
79
+ #
80
+ # config.active_record.observers = :comment_observer, :signup_observer
80
81
  class Observer
81
82
  include Singleton
82
83
 
@@ -21,12 +21,12 @@ module ActiveRecord
21
21
  base.module_eval <<-"end_eval"
22
22
  class << self
23
23
  alias_method :#{association_type}_without_reflection, :#{association_type}
24
-
25
- def #{association_type}_with_reflection(association_id, options = {})
26
- #{association_type}_without_reflection(association_id, options)
24
+
25
+ def #{association_type}_with_reflection(association_id, options = {}, &block)
26
+ #{association_type}_without_reflection(association_id, options, &block)
27
27
  reflect_on_all_associations << AssociationReflection.new(:#{association_type}, association_id, options, self)
28
28
  end
29
-
29
+
30
30
  alias_method :#{association_type}, :#{association_type}_with_reflection
31
31
  end
32
32
  end_eval
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  end
35
35
 
36
36
  # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
37
- # This information can for example be used in a form builder that took an Active Record object and created input
37
+ # This information can, for example, be used in a form builder that took an Active Record object and created input
38
38
  # fields for all of the attributes depending on their type and displayed the associations to other objects.
39
39
  #
40
40
  # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
 
25
25
  # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
26
26
  # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
27
- # vice versa. Transaction enforce the integrity of the database and guards the data against program errors or database break-downs.
27
+ # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
28
28
  # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
29
29
  # not at all. Example:
30
30
  #
@@ -62,7 +62,7 @@ module ActiveRecord
62
62
  #
63
63
  # == Object-level transactions
64
64
  #
65
- # You can enable object-level transactions for Active Record objects, though. You do this by naming the each of the Active Records
65
+ # You can enable object-level transactions for Active Record objects, though. You do this by naming each of the Active Records
66
66
  # that you want to enable object-level transactions for, like this:
67
67
  #
68
68
  # Account.transaction(david, mary) do
@@ -1,14 +1,26 @@
1
1
  module ActiveRecord
2
- class RecordInvalid < ActiveRecordError #:nodoc:
2
+ # Raised by save! and create! when the record is invalid. Use the
3
+ # record method to retrieve the record which did not validate.
4
+ # begin
5
+ # complex_operation_that_calls_save!_internally
6
+ # rescue ActiveRecord::RecordInvalid => invalid
7
+ # puts invalid.record.errors
8
+ # end
9
+ class RecordInvalid < ActiveRecordError
10
+ attr_reader :record
11
+ def initialize(record, *args)
12
+ @record = record
13
+ super(*args)
14
+ end
3
15
  end
4
-
16
+
5
17
  # Active Record validation is reported to and from this object, which is used by Base#save to
6
18
  # determine whether the object in a valid state to be saved. See usage example in Validations.
7
19
  class Errors
8
20
  def initialize(base) # :nodoc:
9
21
  @base, @errors = base, {}
10
22
  end
11
-
23
+
12
24
  @@default_error_messages = {
13
25
  :inclusion => "is not included in the list",
14
26
  :exclusion => "is reserved",
@@ -17,20 +29,20 @@ module ActiveRecord
17
29
  :accepted => "must be accepted",
18
30
  :empty => "can't be empty",
19
31
  :blank => "can't be blank",
20
- :too_long => "is too long (max is %d characters)",
21
- :too_short => "is too short (min is %d characters)",
22
- :wrong_length => "is the wrong length (should be %d characters)",
32
+ :too_long => "is too long (max is %d characters)",
33
+ :too_short => "is too short (min is %d characters)",
34
+ :wrong_length => "is the wrong length (should be %d characters)",
23
35
  :taken => "has already been taken",
24
36
  :not_a_number => "is not a number"
25
37
  }
26
-
38
+
27
39
  # Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
28
40
  cattr_accessor :default_error_messages
29
41
 
30
-
42
+
31
43
  # Adds an error to the base object instead of any particular attribute. This is used
32
- # to report errors that doesn't tie to any specific attribute, but rather to the object
33
- # as a whole. These error messages doesn't get prepended with any field name when iterating
44
+ # to report errors that don't tie to any specific attribute, but rather to the object
45
+ # as a whole. These error messages don't get prepended with any field name when iterating
34
46
  # with each_full, so they should be complete sentences.
35
47
  def add_to_base(msg)
36
48
  add(:base, msg)
@@ -53,7 +65,7 @@ module ActiveRecord
53
65
  add(attr, msg) unless !value.nil? && !is_empty
54
66
  end
55
67
  end
56
-
68
+
57
69
  # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
58
70
  def add_on_blank(attributes, msg = @@default_error_messages[:blank])
59
71
  for attr in [attributes].flatten
@@ -62,7 +74,7 @@ module ActiveRecord
62
74
  end
63
75
  end
64
76
 
65
- # Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
77
+ # Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
66
78
  # If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
67
79
  def add_on_boundary_breaking(attributes, range, too_long_msg = @@default_error_messages[:too_long], too_short_msg = @@default_error_messages[:too_short])
68
80
  for attr in [attributes].flatten
@@ -98,12 +110,12 @@ module ActiveRecord
98
110
  def on_base
99
111
  on(:base)
100
112
  end
101
-
113
+
102
114
  # Yields each attribute and associated message per error added.
103
115
  def each
104
116
  @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
105
117
  end
106
-
118
+
107
119
  # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
108
120
  # through iteration as "First name can't be empty".
109
121
  def each_full
@@ -113,11 +125,11 @@ module ActiveRecord
113
125
  # Returns all the full error messages in an array.
114
126
  def full_messages
115
127
  full_messages = []
116
-
117
- @errors.each_key do |attr|
128
+
129
+ @errors.each_key do |attr|
118
130
  @errors[attr].each do |msg|
119
131
  next if msg.nil?
120
-
132
+
121
133
  if attr == "base"
122
134
  full_messages << msg
123
135
  else
@@ -125,7 +137,7 @@ module ActiveRecord
125
137
  end
126
138
  end
127
139
  end
128
-
140
+
129
141
  return full_messages
130
142
  end
131
143
 
@@ -133,12 +145,12 @@ module ActiveRecord
133
145
  def empty?
134
146
  return @errors.empty?
135
147
  end
136
-
148
+
137
149
  # Removes all the errors that have been added.
138
150
  def clear
139
151
  @errors = {}
140
152
  end
141
-
153
+
142
154
  # Returns the total number of errors added. Two errors added to the same attribute will be counted as such
143
155
  # with this as well.
144
156
  def count
@@ -149,7 +161,7 @@ module ActiveRecord
149
161
  end
150
162
 
151
163
 
152
- # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
164
+ # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
153
165
  # +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
154
166
  # that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
155
167
  #
@@ -179,7 +191,7 @@ module ActiveRecord
179
191
  # person.errors.count # => 2
180
192
  # person.errors.on "last_name" # => "can't be empty"
181
193
  # person.errors.on "phone_number" # => "has invalid format"
182
- # person.errors.each_full { |msg| puts msg }
194
+ # person.errors.each_full { |msg| puts msg }
183
195
  # # => "Last name can't be empty\n" +
184
196
  # "Phone number has invalid format"
185
197
  #
@@ -312,7 +324,7 @@ module ActiveRecord
312
324
  configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
313
325
 
314
326
  attr_accessor *(attr_names.map { |n| "#{n}_confirmation" })
315
-
327
+
316
328
  validates_each(attr_names, configuration) do |record, attr_name, value|
317
329
  record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
318
330
  end
@@ -341,7 +353,7 @@ module ActiveRecord
341
353
  configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
342
354
 
343
355
  attr_accessor *attr_names
344
-
356
+
345
357
  validates_each(attr_names,configuration) do |record, attr_name, value|
346
358
  record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
347
359
  end
@@ -359,8 +371,8 @@ module ActiveRecord
359
371
  configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
360
372
  configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
361
373
 
362
- # can't use validates_each here, because it cannot cope with non-existant attributes,
363
- # while errors.add_on_empty can
374
+ # can't use validates_each here, because it cannot cope with nonexistent attributes,
375
+ # while errors.add_on_empty can
364
376
  attr_names.each do |attr_name|
365
377
  send(validation_method(configuration[:on])) do |record|
366
378
  unless configuration[:if] and not evaluate_condition(configuration[:if], record)
@@ -423,7 +435,7 @@ module ActiveRecord
423
435
 
424
436
  case option
425
437
  when :within, :in
426
- raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
438
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
427
439
 
428
440
  too_short = options[:too_short] % option_value.begin
429
441
  too_long = options[:too_long] % option_value.end
@@ -460,7 +472,7 @@ module ActiveRecord
460
472
  # validates_uniqueness_of :user_name, :scope => "account_id"
461
473
  # end
462
474
  #
463
- # When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
475
+ # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
464
476
  # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
465
477
  #
466
478
  # Configuration options:
@@ -483,8 +495,8 @@ module ActiveRecord
483
495
  end
484
496
  end
485
497
  end
486
-
487
- # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
498
+
499
+ # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
488
500
  # provided.
489
501
  #
490
502
  # class Person < ActiveRecord::Base
@@ -528,9 +540,9 @@ module ActiveRecord
528
540
  def validates_inclusion_of(*attr_names)
529
541
  configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
530
542
  configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
531
-
543
+
532
544
  enum = configuration[:in] || configuration[:within]
533
-
545
+
534
546
  raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
535
547
 
536
548
  validates_each(attr_names, configuration) do |record, attr_name, value|
@@ -565,7 +577,7 @@ module ActiveRecord
565
577
  end
566
578
  end
567
579
 
568
- # Validates whether the associated object or objects are all themselves valid. Works with any kind of association.
580
+ # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
569
581
  #
570
582
  # class Book < ActiveRecord::Base
571
583
  # has_many :pages
@@ -597,11 +609,11 @@ module ActiveRecord
597
609
  configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
598
610
 
599
611
  validates_each(attr_names, configuration) do |record, attr_name, value|
600
- record.errors.add(attr_name, configuration[:message]) unless
612
+ record.errors.add(attr_name, configuration[:message]) unless
601
613
  (value.is_a?(Array) ? value : [value]).all? { |r| r.nil? or r.valid? }
602
614
  end
603
615
  end
604
-
616
+
605
617
  # Validates whether the value of the specified attribute is numeric by trying to convert it to
606
618
  # a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
607
619
  # <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
@@ -636,9 +648,23 @@ module ActiveRecord
636
648
  record.errors.add(attr_name, configuration[:message])
637
649
  end
638
650
  end
639
- end
651
+ end
640
652
  end
641
653
 
654
+
655
+ # Creates an object just like Base.create but calls save! instead of save
656
+ # so an exception is raised if the record is invalid.
657
+ def create!(attributes = nil)
658
+ if attributes.is_a?(Array)
659
+ attributes.collect { |attr| create!(attr) }
660
+ else
661
+ object = new(attributes)
662
+ object.save!
663
+ object
664
+ end
665
+ end
666
+
667
+
642
668
  private
643
669
  def write_inheritable_set(key, methods)
644
670
  existing_methods = read_inheritable_attribute(key) || []
@@ -665,10 +691,14 @@ module ActiveRecord
665
691
  end
666
692
  end
667
693
 
668
- # Attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false
694
+ # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
669
695
  # if the record is not valid.
670
696
  def save!
671
- valid? ? save(false) : raise(RecordInvalid)
697
+ if valid?
698
+ save(false)
699
+ else
700
+ raise RecordInvalid.new(self)
701
+ end
672
702
  end
673
703
 
674
704
  # Updates a single attribute and saves the record without going through the normal validation procedure.
@@ -688,7 +718,7 @@ module ActiveRecord
688
718
 
689
719
  if new_record?
690
720
  run_validations(:validate_on_create)
691
- validate_on_create
721
+ validate_on_create
692
722
  else
693
723
  run_validations(:validate_on_update)
694
724
  validate_on_update
@@ -705,7 +735,7 @@ module ActiveRecord
705
735
  protected
706
736
  # Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.
707
737
  def validate #:doc:
708
- end
738
+ end
709
739
 
710
740
  # Overwrite this method for validation checks used only on creation.
711
741
  def validate_on_create #:doc: