activerecord 1.13.2 → 1.14.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 +452 -10
- data/RUNNING_UNIT_TESTS +1 -1
- data/lib/active_record.rb +5 -2
- data/lib/active_record/acts/list.rb +1 -1
- data/lib/active_record/acts/tree.rb +29 -25
- data/lib/active_record/aggregations.rb +3 -2
- data/lib/active_record/associations.rb +783 -337
- data/lib/active_record/associations/association_collection.rb +7 -12
- data/lib/active_record/associations/association_proxy.rb +62 -24
- data/lib/active_record/associations/belongs_to_association.rb +27 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
- data/lib/active_record/associations/has_many_association.rb +61 -56
- data/lib/active_record/associations/has_many_through_association.rb +144 -0
- data/lib/active_record/associations/has_one_association.rb +22 -16
- data/lib/active_record/base.rb +482 -182
- data/lib/active_record/calculations.rb +225 -0
- data/lib/active_record/callbacks.rb +7 -7
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
- data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
- data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
- data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
- data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
- data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
- data/lib/active_record/fixtures.rb +42 -17
- data/lib/active_record/locking.rb +36 -15
- data/lib/active_record/migration.rb +111 -8
- data/lib/active_record/observer.rb +25 -1
- data/lib/active_record/reflection.rb +103 -41
- data/lib/active_record/schema.rb +2 -2
- data/lib/active_record/schema_dumper.rb +55 -18
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/validations.rb +65 -40
- data/lib/active_record/vendor/db2.rb +10 -5
- data/lib/active_record/vendor/simple.rb +693 -702
- data/lib/active_record/version.rb +2 -2
- data/rakefile +4 -4
- data/test/aaa_create_tables_test.rb +25 -6
- data/test/abstract_unit.rb +39 -1
- data/test/adapter_test.rb +31 -4
- data/test/associations_cascaded_eager_loading_test.rb +106 -0
- data/test/associations_go_eager_test.rb +85 -16
- data/test/associations_join_model_test.rb +338 -0
- data/test/associations_test.rb +129 -50
- data/test/base_test.rb +204 -49
- data/test/binary_test.rb +1 -1
- data/test/calculations_test.rb +169 -0
- data/test/callbacks_test.rb +5 -23
- data/test/class_inheritable_attributes_test.rb +1 -1
- data/test/column_alias_test.rb +1 -1
- data/test/connections/native_mysql/connection.rb +1 -0
- data/test/connections/native_openbase/connection.rb +22 -0
- data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
- data/test/connections/native_sqlite/connection.rb +1 -1
- data/test/connections/native_sqlite3/connection.rb +1 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
- data/test/connections/native_sybase/connection.rb +24 -0
- data/test/defaults_test.rb +18 -0
- data/test/deprecated_associations_test.rb +2 -2
- data/test/deprecated_finder_test.rb +0 -6
- data/test/finder_test.rb +26 -23
- data/test/fixtures/accounts.yml +10 -0
- data/test/fixtures/author.rb +31 -6
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories_posts.yml +4 -0
- data/test/fixtures/categorization.rb +5 -0
- data/test/fixtures/categorizations.yml +11 -0
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +17 -5
- data/test/fixtures/company_in_module.rb +19 -5
- data/test/fixtures/db_definitions/db2.drop.sql +3 -0
- data/test/fixtures/db_definitions/db2.sql +121 -100
- data/test/fixtures/db_definitions/db22.sql +2 -2
- data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
- data/test/fixtures/db_definitions/firebird.sql +26 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
- data/test/fixtures/db_definitions/mysql.sql +21 -1
- data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase.sql +282 -0
- data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase2.sql +7 -0
- data/test/fixtures/db_definitions/{oci.drop.sql → oracle.drop.sql} +6 -0
- data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
- data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
- data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +22 -1
- data/test/fixtures/db_definitions/schema.rb +32 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlite.sql +18 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +23 -3
- data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
- data/test/fixtures/db_definitions/sybase.sql +204 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/developers.yml +6 -1
- data/test/fixtures/developers_projects.yml +4 -0
- data/test/fixtures/funny_jokes.yml +14 -0
- data/test/fixtures/joke.rb +6 -0
- data/test/fixtures/legacy_thing.rb +3 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/mixin.rb +1 -1
- data/test/fixtures/person.rb +4 -1
- data/test/fixtures/post.rb +26 -1
- data/test/fixtures/project.rb +1 -0
- data/test/fixtures/reader.rb +4 -0
- data/test/fixtures/readers.yml +4 -0
- data/test/fixtures/reply.rb +2 -1
- data/test/fixtures/tag.rb +5 -0
- data/test/fixtures/tagging.rb +6 -0
- data/test/fixtures/taggings.yml +18 -0
- data/test/fixtures/tags.yml +7 -0
- data/test/fixtures/tasks.yml +2 -2
- data/test/fixtures/topic.rb +2 -2
- data/test/fixtures/topics.yml +1 -0
- data/test/fixtures_test.rb +47 -13
- data/test/inheritance_test.rb +2 -2
- data/test/locking_test.rb +15 -1
- data/test/method_scoping_test.rb +248 -13
- data/test/migration_test.rb +68 -11
- data/test/mixin_nested_set_test.rb +1 -1
- data/test/modules_test.rb +6 -1
- data/test/readonly_test.rb +1 -1
- data/test/reflection_test.rb +63 -9
- data/test/schema_dumper_test.rb +41 -0
- data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
- data/test/threaded_connections_test.rb +10 -0
- data/test/unconnected_test.rb +12 -5
- data/test/validations_test.rb +197 -10
- metadata +295 -260
- data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
- data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -9,6 +9,9 @@ module YAML #:nodoc:
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
+
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
|
13
|
+
end
|
14
|
+
|
12
15
|
# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours:
|
13
16
|
#
|
14
17
|
# 1. YAML fixtures
|
@@ -220,8 +223,10 @@ class Fixtures < YAML::Omap
|
|
220
223
|
if load_instances
|
221
224
|
ActiveRecord::Base.silence do
|
222
225
|
fixtures.each do |name, fixture|
|
223
|
-
|
224
|
-
object.instance_variable_set "@#{name}",
|
226
|
+
begin
|
227
|
+
object.instance_variable_set "@#{name}", fixture.find
|
228
|
+
rescue FixtureClassNotFound
|
229
|
+
nil
|
225
230
|
end
|
226
231
|
end
|
227
232
|
end
|
@@ -237,14 +242,13 @@ class Fixtures < YAML::Omap
|
|
237
242
|
cattr_accessor :all_loaded_fixtures
|
238
243
|
self.all_loaded_fixtures = {}
|
239
244
|
|
240
|
-
def self.create_fixtures(fixtures_directory,
|
241
|
-
table_names = table_names.flatten.map { |n| n.to_s }
|
245
|
+
def self.create_fixtures(fixtures_directory, table_names, class_names = {})
|
246
|
+
table_names = [table_names].flatten.map { |n| n.to_s }
|
242
247
|
connection = block_given? ? yield : ActiveRecord::Base.connection
|
243
|
-
|
244
248
|
ActiveRecord::Base.silence do
|
245
249
|
fixtures_map = {}
|
246
250
|
fixtures = table_names.map do |table_name|
|
247
|
-
fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s))
|
251
|
+
fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
|
248
252
|
end
|
249
253
|
all_loaded_fixtures.merge! fixtures_map
|
250
254
|
|
@@ -267,10 +271,10 @@ class Fixtures < YAML::Omap
|
|
267
271
|
|
268
272
|
attr_reader :table_name
|
269
273
|
|
270
|
-
def initialize(connection, table_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
|
274
|
+
def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
|
271
275
|
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
|
272
|
-
|
273
|
-
|
276
|
+
@class_name = class_name ||
|
277
|
+
(ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
|
274
278
|
@table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
|
275
279
|
read_fixture_files
|
276
280
|
end
|
@@ -286,11 +290,18 @@ class Fixtures < YAML::Omap
|
|
286
290
|
end
|
287
291
|
|
288
292
|
private
|
293
|
+
|
289
294
|
def read_fixture_files
|
290
295
|
if File.file?(yaml_file_path)
|
291
296
|
# YAML fixtures
|
292
297
|
begin
|
293
|
-
|
298
|
+
yaml_string = ""
|
299
|
+
Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|
|
300
|
+
yaml_string << IO.read(subfixture_path)
|
301
|
+
end
|
302
|
+
yaml_string << IO.read(yaml_file_path)
|
303
|
+
|
304
|
+
if yaml = YAML::load(erb_render(yaml_string))
|
294
305
|
yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
|
295
306
|
yaml.each do |name, data|
|
296
307
|
self[name] = Fixture.new(data, @class_name)
|
@@ -385,9 +396,11 @@ class Fixture #:nodoc:
|
|
385
396
|
end
|
386
397
|
|
387
398
|
def find
|
388
|
-
|
389
|
-
|
399
|
+
klass = @class_name.is_a?(Class) ? @class_name : Object.const_get(@class_name) rescue nil
|
400
|
+
if klass
|
390
401
|
klass.find(self[klass.primary_key])
|
402
|
+
else
|
403
|
+
raise FixtureClassNotFound, "The class #{@class_name.inspect} was not found."
|
391
404
|
end
|
392
405
|
end
|
393
406
|
|
@@ -416,6 +429,7 @@ module Test #:nodoc:
|
|
416
429
|
class TestCase #:nodoc:
|
417
430
|
cattr_accessor :fixture_path
|
418
431
|
class_inheritable_accessor :fixture_table_names
|
432
|
+
class_inheritable_accessor :fixture_class_names
|
419
433
|
class_inheritable_accessor :use_transactional_fixtures
|
420
434
|
class_inheritable_accessor :use_instantiated_fixtures # true, false, or :no_instances
|
421
435
|
class_inheritable_accessor :pre_loaded_fixtures
|
@@ -424,9 +438,16 @@ module Test #:nodoc:
|
|
424
438
|
self.use_transactional_fixtures = false
|
425
439
|
self.use_instantiated_fixtures = true
|
426
440
|
self.pre_loaded_fixtures = false
|
427
|
-
|
441
|
+
|
442
|
+
self.fixture_class_names = {}
|
443
|
+
|
428
444
|
@@already_loaded_fixtures = {}
|
429
|
-
|
445
|
+
self.fixture_class_names = {}
|
446
|
+
|
447
|
+
def self.set_fixture_class(class_names = {})
|
448
|
+
self.fixture_class_names = self.fixture_class_names.merge(class_names)
|
449
|
+
end
|
450
|
+
|
430
451
|
def self.fixtures(*table_names)
|
431
452
|
table_names = table_names.flatten.map { |n| n.to_s }
|
432
453
|
self.fixture_table_names |= table_names
|
@@ -453,7 +474,11 @@ module Test #:nodoc:
|
|
453
474
|
force_reload = optionals.shift
|
454
475
|
@fixture_cache[table_name] ||= Hash.new
|
455
476
|
@fixture_cache[table_name][fixture] = nil if force_reload
|
456
|
-
|
477
|
+
if @loaded_fixtures[table_name][fixture.to_s]
|
478
|
+
@fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
|
479
|
+
else
|
480
|
+
raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
|
481
|
+
end
|
457
482
|
end
|
458
483
|
end
|
459
484
|
end
|
@@ -508,7 +533,7 @@ module Test #:nodoc:
|
|
508
533
|
ActiveRecord::Base.connection.rollback_db_transaction
|
509
534
|
ActiveRecord::Base.unlock_mutex
|
510
535
|
end
|
511
|
-
ActiveRecord::Base.
|
536
|
+
ActiveRecord::Base.verify_active_connections!
|
512
537
|
end
|
513
538
|
|
514
539
|
alias_method :teardown, :teardown_with_fixtures
|
@@ -537,7 +562,7 @@ module Test #:nodoc:
|
|
537
562
|
private
|
538
563
|
def load_fixtures
|
539
564
|
@loaded_fixtures = {}
|
540
|
-
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names)
|
565
|
+
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
|
541
566
|
unless fixtures.nil?
|
542
567
|
if fixtures.instance_of?(Fixtures)
|
543
568
|
@loaded_fixtures[fixtures.table_name] = fixtures
|
@@ -18,6 +18,8 @@ module ActiveRecord
|
|
18
18
|
# You must ensure that your database schema defaults the lock_version column to 0.
|
19
19
|
#
|
20
20
|
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
|
21
|
+
# To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
|
22
|
+
# This method uses the same syntax as <tt>set_table_name</tt>
|
21
23
|
module Locking
|
22
24
|
def self.append_features(base) #:nodoc:
|
23
25
|
super
|
@@ -28,22 +30,24 @@ module ActiveRecord
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def update_with_lock #:nodoc:
|
31
|
-
|
32
|
-
previous_value = self.lock_version
|
33
|
-
self.lock_version = previous_value + 1
|
34
|
-
|
35
|
-
affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
|
36
|
-
UPDATE #{self.class.table_name}
|
37
|
-
SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))}
|
38
|
-
WHERE #{self.class.primary_key} = #{quote(id)} AND lock_version = #{quote(previous_value)}
|
39
|
-
end_sql
|
33
|
+
return update_without_lock unless locking_enabled?
|
40
34
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
35
|
+
lock_col = self.class.locking_column
|
36
|
+
previous_value = send(lock_col)
|
37
|
+
send(lock_col + '=', previous_value + 1)
|
38
|
+
|
39
|
+
affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
|
40
|
+
UPDATE #{self.class.table_name}
|
41
|
+
SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))}
|
42
|
+
WHERE #{self.class.primary_key} = #{quote(id)}
|
43
|
+
AND #{lock_col} = #{quote(previous_value)}
|
44
|
+
end_sql
|
45
|
+
|
46
|
+
unless affected_rows == 1
|
47
|
+
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
|
46
48
|
end
|
49
|
+
|
50
|
+
return true
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
@@ -52,7 +56,24 @@ module ActiveRecord
|
|
52
56
|
cattr_accessor :lock_optimistically
|
53
57
|
|
54
58
|
def locking_enabled? #:nodoc:
|
55
|
-
lock_optimistically && respond_to?(
|
59
|
+
lock_optimistically && respond_to?(self.class.locking_column)
|
60
|
+
end
|
61
|
+
|
62
|
+
class << self
|
63
|
+
def set_locking_column(value = nil, &block)
|
64
|
+
define_attr_method :locking_column, value, &block
|
65
|
+
end
|
66
|
+
|
67
|
+
def locking_column #:nodoc:
|
68
|
+
reset_locking_column
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset_locking_column #:nodoc:
|
72
|
+
default = 'lock_version'
|
73
|
+
set_locking_column(default)
|
74
|
+
default
|
75
|
+
end
|
56
76
|
end
|
77
|
+
|
57
78
|
end
|
58
79
|
end
|
@@ -93,7 +93,7 @@ module ActiveRecord
|
|
93
93
|
# pending migrations, creating the <tt>schema_info</tt> table if missing.
|
94
94
|
#
|
95
95
|
# To roll the database back to a previous migration version, use
|
96
|
-
# <tt>rake migrate
|
96
|
+
# <tt>rake migrate VERSION=X</tt> where <tt>X</tt> is the version to which
|
97
97
|
# you wish to downgrade. If any of the migrations throw an
|
98
98
|
# <tt>IrreversibleMigration</tt> exception, that step will fail and you'll
|
99
99
|
# have some manual work to do.
|
@@ -101,7 +101,7 @@ module ActiveRecord
|
|
101
101
|
# == Database support
|
102
102
|
#
|
103
103
|
# Migrations are currently supported in MySQL, PostgreSQL, SQLite,
|
104
|
-
# SQL Server, and Oracle (all supported databases except DB2).
|
104
|
+
# SQL Server, Sybase, and Oracle (all supported databases except DB2).
|
105
105
|
#
|
106
106
|
# == More examples
|
107
107
|
#
|
@@ -159,16 +159,118 @@ module ActiveRecord
|
|
159
159
|
# end
|
160
160
|
# end
|
161
161
|
# end
|
162
|
+
#
|
163
|
+
# == Controlling verbosity
|
164
|
+
#
|
165
|
+
# By default, migrations will describe the actions they are taking, writing
|
166
|
+
# them to the console as they happen, along with benchmarks describing how
|
167
|
+
# long each step took.
|
168
|
+
#
|
169
|
+
# You can quiet them down by setting ActiveRecord::Migration.verbose = false.
|
170
|
+
#
|
171
|
+
# You can also insert your own messages and benchmarks by using the #say_with_time
|
172
|
+
# method:
|
173
|
+
#
|
174
|
+
# def self.up
|
175
|
+
# ...
|
176
|
+
# say_with_time "Updating salaries..." do
|
177
|
+
# Person.find(:all).each do |p|
|
178
|
+
# p.salary = SalaryCalculator.compute(p)
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
# ...
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# The phrase "Updating salaries..." would then be printed, along with the
|
185
|
+
# benchmark for the block when the block completes.
|
162
186
|
class Migration
|
187
|
+
@@verbose = true
|
188
|
+
cattr_accessor :verbose
|
189
|
+
|
163
190
|
class << self
|
164
|
-
def
|
165
|
-
|
191
|
+
def up_using_benchmarks #:nodoc:
|
192
|
+
migrate(:up)
|
193
|
+
end
|
194
|
+
|
195
|
+
def down_using_benchmarks #:nodoc:
|
196
|
+
migrate(:down)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Execute this migration in the named direction
|
200
|
+
def migrate(direction)
|
201
|
+
return unless respond_to?(direction)
|
202
|
+
|
203
|
+
case direction
|
204
|
+
when :up then announce "migrating"
|
205
|
+
when :down then announce "reverting"
|
206
|
+
end
|
207
|
+
|
208
|
+
result = nil
|
209
|
+
time = Benchmark.measure { result = send("real_#{direction}") }
|
210
|
+
|
211
|
+
case direction
|
212
|
+
when :up then announce "migrated (%.4fs)" % time.real; write
|
213
|
+
when :down then announce "reverted (%.4fs)" % time.real; write
|
214
|
+
end
|
215
|
+
|
216
|
+
result
|
217
|
+
end
|
218
|
+
|
219
|
+
# Because the method added may do an alias_method, it can be invoked
|
220
|
+
# recursively. We use @ignore_new_methods as a guard to indicate whether
|
221
|
+
# it is safe for the call to proceed.
|
222
|
+
def singleton_method_added(sym) #:nodoc:
|
223
|
+
return if @ignore_new_methods
|
224
|
+
|
225
|
+
begin
|
226
|
+
@ignore_new_methods = true
|
227
|
+
|
228
|
+
case sym
|
229
|
+
when :up, :down
|
230
|
+
klass = (class << self; self; end)
|
231
|
+
klass.send(:alias_method, "real_#{sym}", sym)
|
232
|
+
klass.send(:alias_method, sym, "#{sym}_using_benchmarks")
|
233
|
+
end
|
234
|
+
ensure
|
235
|
+
@ignore_new_methods = false
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def write(text="")
|
240
|
+
puts(text) if verbose
|
241
|
+
end
|
166
242
|
|
167
|
-
|
168
|
-
|
169
|
-
|
243
|
+
def announce(message)
|
244
|
+
text = "#{name}: #{message}"
|
245
|
+
write "== %s %s" % [ text, "=" * (75 - text.length) ]
|
246
|
+
end
|
247
|
+
|
248
|
+
def say(message, subitem=false)
|
249
|
+
write "#{subitem ? " ->" : "--"} #{message}"
|
250
|
+
end
|
251
|
+
|
252
|
+
def say_with_time(message)
|
253
|
+
say(message)
|
254
|
+
result = nil
|
255
|
+
time = Benchmark.measure { result = yield }
|
256
|
+
say "%.4fs" % time.real, :subitem
|
257
|
+
result
|
258
|
+
end
|
259
|
+
|
260
|
+
def suppress_messages
|
261
|
+
save = verbose
|
262
|
+
self.verbose = false
|
263
|
+
yield
|
264
|
+
ensure
|
265
|
+
self.verbose = save
|
266
|
+
end
|
267
|
+
|
268
|
+
def method_missing(method, *arguments, &block)
|
269
|
+
say_with_time "#{method}(#{arguments.map { |a| a.inspect }.join(", ")})" do
|
270
|
+
arguments[0] = Migrator.proper_table_name(arguments.first) unless arguments.empty? || method == :execute
|
170
271
|
ActiveRecord::Base.connection.send(method, *arguments, &block)
|
171
272
|
end
|
273
|
+
end
|
172
274
|
end
|
173
275
|
end
|
174
276
|
|
@@ -176,6 +278,7 @@ module ActiveRecord
|
|
176
278
|
class << self
|
177
279
|
def migrate(migrations_path, target_version = nil)
|
178
280
|
Base.connection.initialize_schema_information
|
281
|
+
|
179
282
|
case
|
180
283
|
when target_version.nil?, current_version < target_version
|
181
284
|
up(migrations_path, target_version)
|
@@ -225,7 +328,7 @@ module ActiveRecord
|
|
225
328
|
next if irrelevant_migration?(version)
|
226
329
|
|
227
330
|
Base.logger.info "Migrating to #{migration_class} (#{version})"
|
228
|
-
migration_class.
|
331
|
+
migration_class.migrate(@direction)
|
229
332
|
set_schema_version(version)
|
230
333
|
end
|
231
334
|
end
|
@@ -42,6 +42,18 @@ module ActiveRecord
|
|
42
42
|
#
|
43
43
|
# This Observer sends an email when a Comment#save is finished.
|
44
44
|
#
|
45
|
+
# class ContactObserver < ActiveRecord::Observer
|
46
|
+
# def after_create(contact)
|
47
|
+
# contact.logger.info('New contact added!')
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# def after_destroy(contact)
|
51
|
+
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# This Observer uses logger to log when specific callbacks are triggered.
|
56
|
+
#
|
45
57
|
# == Observing a class that can't be inferred
|
46
58
|
#
|
47
59
|
# Observers will by default be mapped to the class with which they share a name. So CommentObserver will
|
@@ -72,15 +84,27 @@ module ActiveRecord
|
|
72
84
|
#
|
73
85
|
# The observer can implement callback methods for each of the methods described in the Callbacks module.
|
74
86
|
#
|
75
|
-
# ==
|
87
|
+
# == Storing Observers in Rails
|
88
|
+
#
|
89
|
+
# If you're using Active Record within Rails, observer classes are usually stored in app/models with the
|
90
|
+
# naming convention of app/models/audit_observer.rb.
|
91
|
+
#
|
92
|
+
# == Configuration
|
76
93
|
#
|
77
94
|
# In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
|
78
95
|
# <tt>config/environment.rb</tt> file.
|
79
96
|
#
|
80
97
|
# config.active_record.observers = :comment_observer, :signup_observer
|
98
|
+
#
|
99
|
+
# Observers will not be invoked unless you define these in your application configuration.
|
100
|
+
#
|
81
101
|
class Observer
|
82
102
|
include Singleton
|
83
103
|
|
104
|
+
# Observer subclasses should be reloaded by the dispatcher in Rails
|
105
|
+
# when Dependencies.mechanism = :load.
|
106
|
+
include Reloadable::Subclasses
|
107
|
+
|
84
108
|
# Attaches the observer to the supplied model classes.
|
85
109
|
def self.observe(*models)
|
86
110
|
define_method(:observed_class) { models }
|
@@ -1,36 +1,7 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Reflection # :nodoc:
|
3
|
-
def self.
|
4
|
-
super
|
3
|
+
def self.included(base)
|
5
4
|
base.extend(ClassMethods)
|
6
|
-
|
7
|
-
base.class_eval do
|
8
|
-
class << self
|
9
|
-
alias_method :composed_of_without_reflection, :composed_of
|
10
|
-
|
11
|
-
def composed_of_with_reflection(part_id, options = {})
|
12
|
-
composed_of_without_reflection(part_id, options)
|
13
|
-
reflect_on_all_aggregations << AggregateReflection.new(:composed_of, part_id, options, self)
|
14
|
-
end
|
15
|
-
|
16
|
-
alias_method :composed_of, :composed_of_with_reflection
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
for association_type in %w( belongs_to has_one has_many has_and_belongs_to_many )
|
21
|
-
base.module_eval <<-"end_eval"
|
22
|
-
class << self
|
23
|
-
alias_method :#{association_type}_without_reflection, :#{association_type}
|
24
|
-
|
25
|
-
def #{association_type}_with_reflection(association_id, options = {}, &block)
|
26
|
-
#{association_type}_without_reflection(association_id, options, &block)
|
27
|
-
reflect_on_all_associations << AssociationReflection.new(:#{association_type}, association_id, options, self)
|
28
|
-
end
|
29
|
-
|
30
|
-
alias_method :#{association_type}, :#{association_type}_with_reflection
|
31
|
-
end
|
32
|
-
end_eval
|
33
|
-
end
|
34
5
|
end
|
35
6
|
|
36
7
|
# Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
|
@@ -39,26 +10,46 @@ module ActiveRecord
|
|
39
10
|
#
|
40
11
|
# You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
|
41
12
|
module ClassMethods
|
13
|
+
def create_reflection(macro, name, options, active_record)
|
14
|
+
case macro
|
15
|
+
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
16
|
+
reflection = AssociationReflection.new(macro, name, options, active_record)
|
17
|
+
when :composed_of
|
18
|
+
reflection = AggregateReflection.new(macro, name, options, active_record)
|
19
|
+
end
|
20
|
+
write_inheritable_hash :reflections, name => reflection
|
21
|
+
reflection
|
22
|
+
end
|
23
|
+
|
24
|
+
def reflections
|
25
|
+
read_inheritable_attribute(:reflections) or write_inheritable_attribute(:reflections, {})
|
26
|
+
end
|
27
|
+
|
42
28
|
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
43
29
|
def reflect_on_all_aggregations
|
44
|
-
|
30
|
+
reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
|
45
31
|
end
|
46
32
|
|
47
33
|
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
|
48
34
|
# Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
|
49
35
|
def reflect_on_aggregation(aggregation)
|
50
|
-
|
36
|
+
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
|
51
37
|
end
|
52
38
|
|
53
|
-
# Returns an array of AssociationReflection objects for all the aggregations in the class.
|
54
|
-
|
55
|
-
|
39
|
+
# Returns an array of AssociationReflection objects for all the aggregations in the class. If you only want to reflect on a
|
40
|
+
# certain association type, pass in the symbol (:has_many, :has_one, :belongs_to) for that as the first parameter. Example:
|
41
|
+
# Account.reflect_on_all_associations # returns an array of all associations
|
42
|
+
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
43
|
+
def reflect_on_all_associations(macro = nil)
|
44
|
+
association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
|
45
|
+
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
56
46
|
end
|
57
47
|
|
58
48
|
# Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
|
59
49
|
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
50
|
+
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
60
51
|
def reflect_on_association(association)
|
61
|
-
|
52
|
+
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
62
53
|
end
|
63
54
|
end
|
64
55
|
|
@@ -92,6 +83,10 @@ module ActiveRecord
|
|
92
83
|
# Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" would return the Money class and
|
93
84
|
# "has_many :clients" would return the Client class.
|
94
85
|
def klass() end
|
86
|
+
|
87
|
+
def class_name
|
88
|
+
@class_name ||= name_to_class_name(name.id2name)
|
89
|
+
end
|
95
90
|
|
96
91
|
def ==(other_aggregation)
|
97
92
|
name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
|
@@ -102,7 +97,7 @@ module ActiveRecord
|
|
102
97
|
# Holds all the meta-data about an aggregation as it was specified in the Active Record class.
|
103
98
|
class AggregateReflection < MacroReflection #:nodoc:
|
104
99
|
def klass
|
105
|
-
Object.const_get(
|
100
|
+
@klass ||= Object.const_get(class_name)
|
106
101
|
end
|
107
102
|
|
108
103
|
private
|
@@ -114,23 +109,90 @@ module ActiveRecord
|
|
114
109
|
# Holds all the meta-data about an association as it was specified in the Active Record class.
|
115
110
|
class AssociationReflection < MacroReflection #:nodoc:
|
116
111
|
def klass
|
117
|
-
@klass ||= active_record.send(:compute_type,
|
112
|
+
@klass ||= active_record.send(:compute_type, class_name)
|
118
113
|
end
|
119
114
|
|
120
115
|
def table_name
|
121
116
|
@table_name ||= klass.table_name
|
122
117
|
end
|
123
118
|
|
119
|
+
def primary_key_name
|
120
|
+
return @primary_key_name if @primary_key_name
|
121
|
+
case
|
122
|
+
when macro == :belongs_to
|
123
|
+
@primary_key_name = options[:foreign_key] || class_name.foreign_key
|
124
|
+
when options[:as]
|
125
|
+
@primary_key_name = options[:foreign_key] || "#{options[:as]}_id"
|
126
|
+
else
|
127
|
+
@primary_key_name = options[:foreign_key] || active_record.name.foreign_key
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def association_foreign_key
|
132
|
+
@association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
|
133
|
+
end
|
134
|
+
|
135
|
+
def counter_cache_column
|
136
|
+
if options[:counter_cache] == true
|
137
|
+
"#{active_record.name.underscore.pluralize}_count"
|
138
|
+
elsif options[:counter_cache]
|
139
|
+
options[:counter_cache]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def through_reflection
|
144
|
+
@through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false
|
145
|
+
end
|
146
|
+
|
147
|
+
# Gets an array of possible :through source reflection names
|
148
|
+
#
|
149
|
+
# [singularized, pluralized]
|
150
|
+
def source_reflection_names
|
151
|
+
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
|
152
|
+
end
|
153
|
+
|
154
|
+
# Gets the source of the through reflection. It checks both a singularized and pluralized form for :belongs_to or :has_many.
|
155
|
+
# (The :tags association on Tagging below)
|
156
|
+
#
|
157
|
+
# class Post
|
158
|
+
# has_many :tags, :through => :taggings
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
def source_reflection
|
162
|
+
return nil unless through_reflection
|
163
|
+
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
|
164
|
+
end
|
165
|
+
|
166
|
+
def check_validity!
|
167
|
+
if options[:through]
|
168
|
+
if through_reflection.nil?
|
169
|
+
raise HasManyThroughAssociationNotFoundError.new(self)
|
170
|
+
end
|
171
|
+
|
172
|
+
if source_reflection.nil?
|
173
|
+
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
174
|
+
end
|
175
|
+
|
176
|
+
if source_reflection.options[:polymorphic]
|
177
|
+
raise HasManyThroughAssociationPolymorphicError.new(class_name, @reflection, source_reflection)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
124
182
|
private
|
125
183
|
def name_to_class_name(name)
|
126
184
|
if name =~ /::/
|
127
185
|
name
|
128
186
|
else
|
129
|
-
|
187
|
+
if options[:class_name]
|
188
|
+
options[:class_name]
|
189
|
+
elsif through_reflection # get the class_name of the belongs_to association of the through reflection
|
190
|
+
source_reflection.class_name
|
191
|
+
else
|
130
192
|
class_name = name.to_s.camelize
|
131
|
-
class_name = class_name.singularize if [:has_many, :has_and_belongs_to_many].include?(macro)
|
193
|
+
class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
|
194
|
+
class_name
|
132
195
|
end
|
133
|
-
active_record.send(:type_name_with_module, class_name)
|
134
196
|
end
|
135
197
|
end
|
136
198
|
end
|