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.
- data/CHANGELOG +92 -0
- data/README +9 -9
- data/lib/active_record/acts/list.rb +1 -1
- data/lib/active_record/acts/nested_set.rb +13 -13
- data/lib/active_record/acts/tree.rb +7 -6
- data/lib/active_record/aggregations.rb +4 -4
- data/lib/active_record/associations.rb +82 -21
- data/lib/active_record/associations/association_collection.rb +0 -8
- data/lib/active_record/associations/association_proxy.rb +5 -2
- data/lib/active_record/associations/belongs_to_association.rb +6 -2
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +11 -1
- data/lib/active_record/associations/has_many_association.rb +34 -5
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/base.rb +144 -59
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/db2_adapter.rb +17 -1
- data/lib/active_record/connection_adapters/oci_adapter.rb +322 -185
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +9 -8
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +107 -14
- data/lib/active_record/fixtures.rb +10 -8
- data/lib/active_record/migration.rb +20 -6
- data/lib/active_record/observer.rb +4 -3
- data/lib/active_record/reflection.rb +5 -5
- data/lib/active_record/transactions.rb +2 -2
- data/lib/active_record/validations.rb +70 -40
- data/lib/active_record/vendor/mysql411.rb +9 -13
- data/lib/active_record/version.rb +2 -2
- data/rakefile +1 -1
- data/test/abstract_unit.rb +5 -0
- data/test/ar_schema_test.rb +1 -1
- data/test/associations_extensions_test.rb +37 -0
- data/test/associations_go_eager_test.rb +25 -0
- data/test/associations_test.rb +14 -6
- data/test/base_test.rb +63 -45
- data/test/connections/native_sqlite/connection.rb +2 -2
- data/test/connections/native_sqlite3/connection.rb +2 -2
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/debug.log +2857 -0
- data/test/deprecated_finder_test.rb +3 -9
- data/test/finder_test.rb +27 -13
- data/test/fixtures/author.rb +4 -0
- data/test/fixtures/comment.rb +4 -8
- data/test/fixtures/db_definitions/create_oracle_db.bat +0 -5
- data/test/fixtures/db_definitions/create_oracle_db.sh +0 -5
- data/test/fixtures/db_definitions/db2.drop.sql +0 -1
- data/test/fixtures/db_definitions/oci.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -1
- data/test/fixtures/developer.rb +18 -1
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
- data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
- data/test/fixtures/post.rb +18 -4
- data/test/fixtures/reply.rb +3 -1
- data/test/inheritance_test.rb +3 -2
- data/test/{conditions_scoping_test.rb → method_scoping_test.rb} +55 -10
- data/test/migration_test.rb +68 -31
- data/test/readonly_test.rb +65 -5
- data/test/validations_test.rb +10 -2
- metadata +13 -4
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
end
|
16
16
|
|
17
17
|
db = SQLite3::Database.new(
|
18
|
-
config[:
|
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[:
|
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
|
-
|
49
|
-
|
50
|
-
|
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[:
|
57
|
-
config[:
|
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>:
|
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,
|
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
|
310
|
-
|
311
|
-
|
312
|
-
|
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.
|
316
|
-
|
317
|
-
|
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 =~
|
450
|
+
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
359
451
|
$1
|
360
|
-
elsif sql =~ /from\s
|
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
|
396
|
-
@table_columns[table_name]
|
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
|
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
|
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
|
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.
|
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.
|
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 =
|
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
|
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
|
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
|
-
#
|
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.
|
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
|
-
|
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,
|
78
|
-
#
|
79
|
-
#
|
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.
|
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
|
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
|
-
|
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
|
33
|
-
# as a whole. These error messages
|
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
|
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
|
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
|
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
|
-
|
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
|
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?
|
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:
|