activerecord 4.1.15 → 4.2.11.3

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 (185) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1162 -1792
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +4 -0
  5. data/lib/active_record/aggregations.rb +15 -8
  6. data/lib/active_record/association_relation.rb +13 -0
  7. data/lib/active_record/associations.rb +158 -49
  8. data/lib/active_record/associations/alias_tracker.rb +3 -12
  9. data/lib/active_record/associations/association.rb +16 -4
  10. data/lib/active_record/associations/association_scope.rb +83 -38
  11. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  12. data/lib/active_record/associations/builder/association.rb +15 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  16. data/lib/active_record/associations/builder/has_many.rb +1 -1
  17. data/lib/active_record/associations/builder/has_one.rb +2 -2
  18. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  19. data/lib/active_record/associations/collection_association.rb +63 -27
  20. data/lib/active_record/associations/collection_proxy.rb +29 -35
  21. data/lib/active_record/associations/foreign_association.rb +11 -0
  22. data/lib/active_record/associations/has_many_association.rb +83 -22
  23. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  24. data/lib/active_record/associations/has_one_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  27. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/preloader/association.rb +14 -11
  30. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  31. data/lib/active_record/associations/singular_association.rb +17 -2
  32. data/lib/active_record/associations/through_association.rb +5 -12
  33. data/lib/active_record/attribute.rb +163 -0
  34. data/lib/active_record/attribute_assignment.rb +19 -11
  35. data/lib/active_record/attribute_decorators.rb +66 -0
  36. data/lib/active_record/attribute_methods.rb +56 -94
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  39. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  40. data/lib/active_record/attribute_methods/query.rb +1 -1
  41. data/lib/active_record/attribute_methods/read.rb +22 -59
  42. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  44. data/lib/active_record/attribute_methods/write.rb +9 -24
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attribute_set/builder.rb +106 -0
  47. data/lib/active_record/attributes.rb +147 -0
  48. data/lib/active_record/autosave_association.rb +19 -12
  49. data/lib/active_record/base.rb +13 -24
  50. data/lib/active_record/callbacks.rb +6 -6
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  55. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  63. data/lib/active_record/connection_adapters/column.rb +29 -240
  64. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  68. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  71. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  99. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  100. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  101. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  102. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  103. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  104. data/lib/active_record/connection_handling.rb +1 -1
  105. data/lib/active_record/core.rb +163 -39
  106. data/lib/active_record/counter_cache.rb +60 -6
  107. data/lib/active_record/enum.rb +9 -11
  108. data/lib/active_record/errors.rb +53 -30
  109. data/lib/active_record/explain.rb +1 -1
  110. data/lib/active_record/explain_subscriber.rb +1 -1
  111. data/lib/active_record/fixtures.rb +55 -69
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +35 -10
  114. data/lib/active_record/integration.rb +4 -4
  115. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  116. data/lib/active_record/locking/optimistic.rb +46 -26
  117. data/lib/active_record/migration.rb +71 -46
  118. data/lib/active_record/migration/command_recorder.rb +19 -2
  119. data/lib/active_record/migration/join_table.rb +1 -1
  120. data/lib/active_record/model_schema.rb +52 -58
  121. data/lib/active_record/nested_attributes.rb +5 -5
  122. data/lib/active_record/no_touching.rb +1 -1
  123. data/lib/active_record/persistence.rb +46 -26
  124. data/lib/active_record/query_cache.rb +3 -3
  125. data/lib/active_record/querying.rb +10 -7
  126. data/lib/active_record/railtie.rb +18 -11
  127. data/lib/active_record/railties/databases.rake +50 -51
  128. data/lib/active_record/readonly_attributes.rb +0 -1
  129. data/lib/active_record/reflection.rb +273 -114
  130. data/lib/active_record/relation.rb +57 -25
  131. data/lib/active_record/relation/batches.rb +0 -2
  132. data/lib/active_record/relation/calculations.rb +41 -37
  133. data/lib/active_record/relation/finder_methods.rb +70 -47
  134. data/lib/active_record/relation/merger.rb +39 -29
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  137. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  138. data/lib/active_record/relation/query_methods.rb +114 -65
  139. data/lib/active_record/relation/spawn_methods.rb +3 -0
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -6
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +53 -27
  155. data/lib/active_record/type.rb +23 -0
  156. data/lib/active_record/type/big_integer.rb +13 -0
  157. data/lib/active_record/type/binary.rb +50 -0
  158. data/lib/active_record/type/boolean.rb +31 -0
  159. data/lib/active_record/type/date.rb +50 -0
  160. data/lib/active_record/type/date_time.rb +54 -0
  161. data/lib/active_record/type/decimal.rb +64 -0
  162. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  163. data/lib/active_record/type/decorator.rb +14 -0
  164. data/lib/active_record/type/float.rb +19 -0
  165. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  166. data/lib/active_record/type/integer.rb +59 -0
  167. data/lib/active_record/type/mutable.rb +16 -0
  168. data/lib/active_record/type/numeric.rb +36 -0
  169. data/lib/active_record/type/serialized.rb +62 -0
  170. data/lib/active_record/type/string.rb +40 -0
  171. data/lib/active_record/type/text.rb +11 -0
  172. data/lib/active_record/type/time.rb +26 -0
  173. data/lib/active_record/type/time_value.rb +38 -0
  174. data/lib/active_record/type/type_map.rb +64 -0
  175. data/lib/active_record/type/unsigned_integer.rb +15 -0
  176. data/lib/active_record/type/value.rb +110 -0
  177. data/lib/active_record/validations.rb +25 -19
  178. data/lib/active_record/validations/associated.rb +5 -3
  179. data/lib/active_record/validations/presence.rb +5 -3
  180. data/lib/active_record/validations/uniqueness.rb +25 -29
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  # = Active Record Query Cache
4
3
  class QueryCache
@@ -29,9 +28,10 @@ module ActiveRecord
29
28
  end
30
29
 
31
30
  def call(env)
32
- enabled = ActiveRecord::Base.connection.query_cache_enabled
31
+ connection = ActiveRecord::Base.connection
32
+ enabled = connection.query_cache_enabled
33
33
  connection_id = ActiveRecord::Base.connection_id
34
- ActiveRecord::Base.connection.enable_query_cache!
34
+ connection.enable_query_cache!
35
35
 
36
36
  response = @app.call(env)
37
37
  response[2] = Rack::BodyProxy.new(response[2]) do
@@ -37,15 +37,18 @@ module ActiveRecord
37
37
  # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
38
38
  def find_by_sql(sql, binds = [])
39
39
  result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
40
- column_types = {}
40
+ column_types = result_set.column_types.dup
41
+ columns_hash.each_key { |k| column_types.delete k }
42
+ message_bus = ActiveSupport::Notifications.instrumenter
41
43
 
42
- if result_set.respond_to? :column_types
43
- column_types = result_set.column_types
44
- else
45
- ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
46
- end
44
+ payload = {
45
+ record_count: result_set.length,
46
+ class_name: name
47
+ }
47
48
 
48
- result_set.map { |record| instantiate(record, column_types) }
49
+ message_bus.instrument('instantiation.active_record', payload) do
50
+ result_set.map { |record| instantiate(record, column_types) }
51
+ end
49
52
  end
50
53
 
51
54
  # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
@@ -57,8 +57,10 @@ module ActiveRecord
57
57
  console do |app|
58
58
  require "active_record/railties/console_sandbox" if app.sandbox?
59
59
  require "active_record/base"
60
- console = ActiveSupport::Logger.new(STDERR)
61
- Rails.logger.extend ActiveSupport::Logger.broadcast console
60
+ unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
61
+ console = ActiveSupport::Logger.new(STDERR)
62
+ Rails.logger.extend ActiveSupport::Logger.broadcast console
63
+ end
62
64
  end
63
65
 
64
66
  runner do
@@ -114,17 +116,22 @@ module ActiveRecord
114
116
  # and then establishes the connection.
115
117
  initializer "active_record.initialize_database" do |app|
116
118
  ActiveSupport.on_load(:active_record) do
119
+ self.configurations = Rails.application.config.database_configuration
117
120
 
118
- class ActiveRecord::NoDatabaseError
119
- remove_possible_method :extend_message
120
- def extend_message(message)
121
- message << "Run `$ bin/rake db:create db:migrate` to create your database"
122
- message
123
- end
124
- end
121
+ begin
122
+ establish_connection
123
+ rescue ActiveRecord::NoDatabaseError
124
+ warn <<-end_warning
125
+ Oops - You have a database configured, but it doesn't exist yet!
125
126
 
126
- self.configurations = Rails.application.config.database_configuration
127
- establish_connection
127
+ Here's how to get started:
128
+
129
+ 1. Configure your database in config/database.yml.
130
+ 2. Run `bin/rake db:create` to create the database.
131
+ 3. Run `bin/rake db:setup` to load your database schema.
132
+ end_warning
133
+ raise
134
+ end
128
135
  end
129
136
  end
130
137
 
@@ -28,21 +28,32 @@ db_namespace = namespace :db do
28
28
  ActiveRecord::Tasks::DatabaseTasks.drop_current
29
29
  end
30
30
 
31
+ namespace :purge do
32
+ task :all => :load_config do
33
+ ActiveRecord::Tasks::DatabaseTasks.purge_all
34
+ end
35
+ end
36
+
37
+ # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
38
+ task :purge => [:load_config] do
39
+ ActiveRecord::Tasks::DatabaseTasks.purge_current
40
+ end
41
+
31
42
  desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
32
43
  task :migrate => [:environment, :load_config] do
33
- ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
34
- ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
35
- ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
36
- end
37
- db_namespace['_dump'].invoke if ActiveRecord::Base.dump_schema_after_migration
44
+ ActiveRecord::Tasks::DatabaseTasks.migrate
45
+ db_namespace['_dump'].invoke
38
46
  end
39
47
 
48
+ # IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false
40
49
  task :_dump do
41
- case ActiveRecord::Base.schema_format
42
- when :ruby then db_namespace["schema:dump"].invoke
43
- when :sql then db_namespace["structure:dump"].invoke
44
- else
45
- raise "unknown schema format #{ActiveRecord::Base.schema_format}"
50
+ if ActiveRecord::Base.dump_schema_after_migration
51
+ case ActiveRecord::Base.schema_format
52
+ when :ruby then db_namespace["schema:dump"].invoke
53
+ when :sql then db_namespace["structure:dump"].invoke
54
+ else
55
+ raise "unknown schema format #{ActiveRecord::Base.schema_format}"
56
+ end
46
57
  end
47
58
  # Allow this task to be called as many times as required. An example is the
48
59
  # migrate:redo task, which calls other two internally that depend on this one.
@@ -52,6 +63,8 @@ db_namespace = namespace :db do
52
63
  namespace :migrate do
53
64
  # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
54
65
  task :redo => [:environment, :load_config] do
66
+ raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
67
+
55
68
  if ENV['VERSION']
56
69
  db_namespace['migrate:down'].invoke
57
70
  db_namespace['migrate:up'].invoke
@@ -66,48 +79,34 @@ db_namespace = namespace :db do
66
79
 
67
80
  # desc 'Runs the "up" for a given migration VERSION.'
68
81
  task :up => [:environment, :load_config] do
82
+ raise "VERSION is required" if ENV["VERSION"] && ENV["VERSION"].empty?
83
+
69
84
  version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
70
- raise 'VERSION is required' unless version
71
85
  ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
72
86
  db_namespace['_dump'].invoke
73
87
  end
74
88
 
75
89
  # desc 'Runs the "down" for a given migration VERSION.'
76
90
  task :down => [:environment, :load_config] do
91
+ raise "VERSION is required - To go down one migration, use db:rollback" if ENV["VERSION"] && ENV["VERSION"].empty?
77
92
  version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
78
- raise 'VERSION is required - To go down one migration, run db:rollback' unless version
79
93
  ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
80
94
  db_namespace['_dump'].invoke
81
95
  end
82
96
 
83
97
  desc 'Display status of migrations'
84
98
  task :status => [:environment, :load_config] do
85
- unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
86
- puts 'Schema migrations table does not exist yet.'
87
- next # means "return" for rake task
88
- end
89
- db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
90
- db_list.map! { |version| ActiveRecord::SchemaMigration.normalize_migration_number(version) }
91
- file_list = []
92
- ActiveRecord::Migrator.migrations_paths.each do |path|
93
- Dir.foreach(path) do |file|
94
- # match "20091231235959_some_name.rb" and "001_some_name.rb" pattern
95
- if match_data = /^(\d{3,})_(.+)\.rb$/.match(file)
96
- version = ActiveRecord::SchemaMigration.normalize_migration_number(match_data[1])
97
- status = db_list.delete(version) ? 'up' : 'down'
98
- file_list << [status, version, match_data[2].humanize]
99
- end
100
- end
101
- end
102
- db_list.map! do |version|
103
- ['up', version, '********** NO FILE **********']
99
+ unless ActiveRecord::SchemaMigration.table_exists?
100
+ abort 'Schema migrations table does not exist yet.'
104
101
  end
102
+
105
103
  # output
106
104
  puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
107
105
  puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
108
106
  puts "-" * 50
109
- (db_list + file_list).sort_by {|migration| migration[1]}.each do |migration|
110
- puts "#{migration[0].center(8)} #{migration[1].ljust(14)} #{migration[2]}"
107
+ paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
108
+ ActiveRecord::Migrator.migrations_status(paths).each do |status, version, name|
109
+ puts "#{status.center(8)} #{version.ljust(14)} #{name}"
111
110
  end
112
111
  puts
113
112
  end
@@ -179,17 +178,22 @@ db_namespace = namespace :db do
179
178
  task :load => [:environment, :load_config] do
180
179
  require 'active_record/fixtures'
181
180
 
182
- base_dir = if ENV['FIXTURES_PATH']
183
- File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
184
- else
185
- ActiveRecord::Tasks::DatabaseTasks.fixtures_path
186
- end
181
+ base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
187
182
 
188
- fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
183
+ fixtures_dir = if ENV['FIXTURES_DIR']
184
+ File.join base_dir, ENV['FIXTURES_DIR']
185
+ else
186
+ base_dir
187
+ end
189
188
 
190
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(',') : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
191
- ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_file)
192
- end
189
+ fixture_files = if ENV['FIXTURES']
190
+ ENV['FIXTURES'].split(',')
191
+ else
192
+ # The use of String#[] here is to support namespaced fixtures
193
+ Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }
194
+ end
195
+
196
+ ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files)
193
197
  end
194
198
 
195
199
  # desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
@@ -201,16 +205,11 @@ db_namespace = namespace :db do
201
205
 
202
206
  puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label
203
207
 
204
- base_dir = if ENV['FIXTURES_PATH']
205
- File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
206
- else
207
- ActiveRecord::Tasks::DatabaseTasks.fixtures_path
208
- end
209
-
208
+ base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
210
209
 
211
210
  Dir["#{base_dir}/**/*.yml"].each do |file|
212
211
  if data = YAML::load(ERB.new(IO.read(file)).result)
213
- data.keys.each do |key|
212
+ data.each_key do |key|
214
213
  key_id = ActiveRecord::FixtureSet.identify(key)
215
214
 
216
215
  if key == label || key_id == id.to_i
@@ -279,8 +278,8 @@ db_namespace = namespace :db do
279
278
  db_namespace['structure:dump'].reenable
280
279
  end
281
280
 
282
- # desc "Recreate the databases from the structure.sql file"
283
- task :load => [:environment, :load_config] do
281
+ desc "Recreate the databases from the structure.sql file"
282
+ task :load => [:load_config] do
284
283
  ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['DB_STRUCTURE'])
285
284
  end
286
285
 
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  module ReadonlyAttributes
4
3
  extend ActiveSupport::Concern
@@ -1,3 +1,6 @@
1
+ require 'thread'
2
+ require 'active_support/core_ext/string/filters'
3
+
1
4
  module ActiveRecord
2
5
  # = Active Record Reflection
3
6
  module Reflection # :nodoc:
@@ -11,25 +14,33 @@ module ActiveRecord
11
14
  end
12
15
 
13
16
  def self.create(macro, name, scope, options, ar)
14
- case macro
15
- when :has_many, :belongs_to, :has_one
16
- klass = options[:through] ? ThroughReflection : AssociationReflection
17
- when :composed_of
18
- klass = AggregateReflection
19
- end
20
-
21
- klass.new(macro, name, scope, options, ar)
17
+ klass = case macro
18
+ when :composed_of
19
+ AggregateReflection
20
+ when :has_many
21
+ HasManyReflection
22
+ when :has_one
23
+ HasOneReflection
24
+ when :belongs_to
25
+ BelongsToReflection
26
+ else
27
+ raise "Unsupported Macro: #{macro}"
28
+ end
29
+
30
+ reflection = klass.new(name, scope, options, ar)
31
+ options[:through] ? ThroughReflection.new(reflection) : reflection
22
32
  end
23
33
 
24
34
  def self.add_reflection(ar, name, reflection)
25
- ar._reflections = ar._reflections.merge(name.to_sym => reflection)
35
+ ar.clear_reflections_cache
36
+ ar._reflections = ar._reflections.merge(name.to_s => reflection)
26
37
  end
27
38
 
28
39
  def self.add_aggregate_reflection(ar, name, reflection)
29
- ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
40
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
30
41
  end
31
42
 
32
- # \Reflection enables to interrogate Active Record classes and objects
43
+ # \Reflection enables interrogating of Active Record classes and objects
33
44
  # about their associations and aggregations. This information can,
34
45
  # for example, be used in a form builder that takes an Active Record object
35
46
  # and creates input fields for all of the attributes depending on their type
@@ -48,25 +59,30 @@ module ActiveRecord
48
59
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
49
60
  #
50
61
  def reflect_on_aggregation(aggregation)
51
- aggregate_reflections[aggregation.to_sym]
62
+ aggregate_reflections[aggregation.to_s]
52
63
  end
53
64
 
54
65
  # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
55
66
  #
56
- # Account.reflections # => {balance: AggregateReflection}
67
+ # Account.reflections # => {"balance" => AggregateReflection}
57
68
  #
58
69
  # @api public
59
70
  def reflections
60
- ref = {}
61
- _reflections.each do |name, reflection|
62
- parent_name, parent_reflection = reflection.parent_reflection
63
- if parent_name
64
- ref[parent_name] = parent_reflection
65
- else
66
- ref[name] = reflection
71
+ @__reflections ||= begin
72
+ ref = {}
73
+
74
+ _reflections.each do |name, reflection|
75
+ parent_name, parent_reflection = reflection.parent_reflection
76
+
77
+ if parent_name
78
+ ref[parent_name] = parent_reflection
79
+ else
80
+ ref[name] = reflection
81
+ end
67
82
  end
83
+
84
+ ref
68
85
  end
69
- ref
70
86
  end
71
87
 
72
88
  # Returns an array of AssociationReflection objects for all the
@@ -92,12 +108,12 @@ module ActiveRecord
92
108
  #
93
109
  # @api public
94
110
  def reflect_on_association(association)
95
- reflections[association.to_sym]
111
+ reflections[association.to_s]
96
112
  end
97
113
 
98
114
  # @api private
99
115
  def _reflect_on_association(association) #:nodoc:
100
- _reflections[association.to_sym]
116
+ _reflections[association.to_s]
101
117
  end
102
118
 
103
119
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -106,28 +122,87 @@ module ActiveRecord
106
122
  def reflect_on_all_autosave_associations
107
123
  reflections.values.select { |reflection| reflection.options[:autosave] }
108
124
  end
125
+
126
+ def clear_reflections_cache #:nodoc:
127
+ @__reflections = nil
128
+ end
109
129
  end
110
130
 
131
+ # Holds all the methods that are shared between MacroReflection, AssociationReflection
132
+ # and ThroughReflection
133
+ class AbstractReflection # :nodoc:
134
+ def table_name
135
+ klass.table_name
136
+ end
137
+
138
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
139
+ # be passed to the class's constructor.
140
+ def build_association(attributes, &block)
141
+ klass.new(attributes, &block)
142
+ end
143
+
144
+ def quoted_table_name
145
+ klass.quoted_table_name
146
+ end
147
+
148
+ def primary_key_type
149
+ klass.type_for_attribute(klass.primary_key)
150
+ end
151
+
152
+ # Returns the class name for the macro.
153
+ #
154
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
155
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
156
+ def class_name
157
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
158
+ end
159
+
160
+ JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
161
+
162
+ def join_keys(assoc_klass)
163
+ JoinKeys.new(foreign_key, active_record_primary_key)
164
+ end
165
+
166
+ def source_macro
167
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
168
+ ActiveRecord::Base.source_macro is deprecated and will be removed
169
+ without replacement.
170
+ MSG
171
+
172
+ macro
173
+ end
174
+
175
+ def inverse_of
176
+ return unless inverse_name
177
+
178
+ @inverse_of ||= klass._reflect_on_association inverse_name
179
+ end
180
+
181
+ def check_validity_of_inverse!
182
+ unless polymorphic?
183
+ if has_inverse? && inverse_of.nil?
184
+ raise InverseOfAssociationNotFoundError.new(self)
185
+ end
186
+ end
187
+ end
188
+ end
111
189
  # Base class for AggregateReflection and AssociationReflection. Objects of
112
190
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
113
191
  #
114
192
  # MacroReflection
115
- # AggregateReflection
116
193
  # AssociationReflection
117
- # ThroughReflection
118
- class MacroReflection
194
+ # AggregateReflection
195
+ # HasManyReflection
196
+ # HasOneReflection
197
+ # BelongsToReflection
198
+ # ThroughReflection
199
+ class MacroReflection < AbstractReflection
119
200
  # Returns the name of the macro.
120
201
  #
121
202
  # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
122
203
  # <tt>has_many :clients</tt> returns <tt>:clients</tt>
123
204
  attr_reader :name
124
205
 
125
- # Returns the macro type.
126
- #
127
- # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
128
- # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
129
- attr_reader :macro
130
-
131
206
  attr_reader :scope
132
207
 
133
208
  # Returns the hash of options used for the macro.
@@ -140,8 +215,7 @@ module ActiveRecord
140
215
 
141
216
  attr_reader :plural_name # :nodoc:
142
217
 
143
- def initialize(macro, name, scope, options, active_record)
144
- @macro = macro
218
+ def initialize(name, scope, options, active_record)
145
219
  @name = name
146
220
  @scope = scope
147
221
  @options = options
@@ -165,15 +239,11 @@ module ActiveRecord
165
239
  # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
166
240
  # <tt>has_many :clients</tt> returns the Client class
167
241
  def klass
168
- @klass ||= class_name.constantize
242
+ @klass ||= compute_class(class_name)
169
243
  end
170
244
 
171
- # Returns the class name for the macro.
172
- #
173
- # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
174
- # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
175
- def class_name
176
- @class_name ||= (options[:class_name] || derive_class_name).to_s
245
+ def compute_class(name)
246
+ name.constantize
177
247
  end
178
248
 
179
249
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -218,49 +288,46 @@ module ActiveRecord
218
288
  # a new association object. Use +build_association+ or +create_association+
219
289
  # instead. This allows plugins to hook into association object creation.
220
290
  def klass
221
- @klass ||= active_record.send(:compute_type, class_name)
291
+ @klass ||= compute_class(class_name)
292
+ end
293
+
294
+ def compute_class(name)
295
+ active_record.send(:compute_type, name)
222
296
  end
223
297
 
224
298
  attr_reader :type, :foreign_type
225
299
  attr_accessor :parent_reflection # [:name, Reflection]
226
300
 
227
- def initialize(macro, name, scope, options, active_record)
301
+ def initialize(name, scope, options, active_record)
228
302
  super
229
- @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
230
303
  @automatic_inverse_of = nil
231
- @type = options[:as] && "#{options[:as]}_type"
304
+ @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
232
305
  @foreign_type = options[:foreign_type] || "#{name}_type"
233
306
  @constructable = calculate_constructable(macro, options)
307
+ @association_scope_cache = {}
308
+ @scope_lock = Mutex.new
234
309
  end
235
310
 
236
- # Returns a new, unsaved instance of the associated class. +attributes+ will
237
- # be passed to the class's constructor.
238
- def build_association(attributes, &block)
239
- klass.new(attributes, &block)
311
+ def association_scope_cache(conn, owner)
312
+ key = conn.prepared_statements
313
+ if polymorphic?
314
+ key = [key, owner._read_attribute(@foreign_type)]
315
+ end
316
+ @association_scope_cache[key] ||= @scope_lock.synchronize {
317
+ @association_scope_cache[key] ||= yield
318
+ }
240
319
  end
241
320
 
242
321
  def constructable? # :nodoc:
243
322
  @constructable
244
323
  end
245
324
 
246
- def table_name
247
- klass.table_name
248
- end
249
-
250
- def quoted_table_name
251
- klass.quoted_table_name
252
- end
253
-
254
325
  def join_table
255
326
  @join_table ||= options[:join_table] || derive_join_table
256
327
  end
257
328
 
258
329
  def foreign_key
259
- @foreign_key ||= options[:foreign_key] || derive_foreign_key
260
- end
261
-
262
- def primary_key_column
263
- klass.columns_hash[klass.primary_key]
330
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
264
331
  end
265
332
 
266
333
  def association_foreign_key
@@ -288,13 +355,25 @@ module ActiveRecord
288
355
  check_validity_of_inverse!
289
356
  end
290
357
 
291
- def check_validity_of_inverse!
292
- unless options[:polymorphic]
293
- if has_inverse? && inverse_of.nil?
294
- raise InverseOfAssociationNotFoundError.new(self)
295
- end
358
+ def check_preloadable!
359
+ return unless scope
360
+
361
+ if scope.arity > 0
362
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
363
+ The association scope '#{name}' is instance dependent (the scope
364
+ block takes an argument). Preloading happens before the individual
365
+ instances are created. This means that there is no instance being
366
+ passed to the association scope. This will most likely result in
367
+ broken or incorrect behavior. Joining, Preloading and eager loading
368
+ of these associations is deprecated and will be removed in the future.
369
+ MSG
296
370
  end
297
371
  end
372
+ alias :check_eager_loadable! :check_preloadable!
373
+
374
+ def join_id_for(owner) # :nodoc:
375
+ owner[active_record_primary_key]
376
+ end
298
377
 
299
378
  def through_reflection
300
379
  nil
@@ -320,18 +399,10 @@ module ActiveRecord
320
399
  scope ? [[scope]] : [[]]
321
400
  end
322
401
 
323
- alias :source_macro :macro
324
-
325
402
  def has_inverse?
326
403
  inverse_name
327
404
  end
328
405
 
329
- def inverse_of
330
- return unless inverse_name
331
-
332
- @inverse_of ||= klass._reflect_on_association inverse_name
333
- end
334
-
335
406
  def polymorphic_inverse_of(associated_class)
336
407
  if has_inverse?
337
408
  if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
@@ -342,11 +413,16 @@ module ActiveRecord
342
413
  end
343
414
  end
344
415
 
416
+ # Returns the macro type.
417
+ #
418
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
419
+ def macro; raise NotImplementedError; end
420
+
345
421
  # Returns whether or not this association reflection is for a collection
346
422
  # association. Returns +true+ if the +macro+ is either +has_many+ or
347
423
  # +has_and_belongs_to_many+, +false+ otherwise.
348
424
  def collection?
349
- @collection
425
+ false
350
426
  end
351
427
 
352
428
  # Returns whether or not the association should be validated as part of
@@ -359,18 +435,19 @@ module ActiveRecord
359
435
  # * you use autosave; <tt>autosave: true</tt>
360
436
  # * the association is a +has_many+ association
361
437
  def validate?
362
- !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
438
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
363
439
  end
364
440
 
365
441
  # Returns +true+ if +self+ is a +belongs_to+ reflection.
366
- def belongs_to?
367
- macro == :belongs_to
368
- end
442
+ def belongs_to?; false; end
443
+
444
+ # Returns +true+ if +self+ is a +has_one+ reflection.
445
+ def has_one?; false; end
369
446
 
370
447
  def association_class
371
448
  case macro
372
449
  when :belongs_to
373
- if options[:polymorphic]
450
+ if polymorphic?
374
451
  Associations::BelongsToPolymorphicAssociation
375
452
  else
376
453
  Associations::BelongsToAssociation
@@ -391,7 +468,7 @@ module ActiveRecord
391
468
  end
392
469
 
393
470
  def polymorphic?
394
- options.key? :polymorphic
471
+ options[:polymorphic]
395
472
  end
396
473
 
397
474
  VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
@@ -408,7 +485,7 @@ module ActiveRecord
408
485
  def calculate_constructable(macro, options)
409
486
  case macro
410
487
  when :belongs_to
411
- !options[:polymorphic]
488
+ !polymorphic?
412
489
  when :has_one
413
490
  !options[:through]
414
491
  else
@@ -497,7 +574,7 @@ module ActiveRecord
497
574
  end
498
575
 
499
576
  def derive_join_table
500
- [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
577
+ ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
501
578
  end
502
579
 
503
580
  def primary_key(klass)
@@ -505,15 +582,72 @@ module ActiveRecord
505
582
  end
506
583
  end
507
584
 
585
+ class HasManyReflection < AssociationReflection # :nodoc:
586
+ def initialize(name, scope, options, active_record)
587
+ super(name, scope, options, active_record)
588
+ end
589
+
590
+ def macro; :has_many; end
591
+
592
+ def collection?; true; end
593
+ end
594
+
595
+ class HasOneReflection < AssociationReflection # :nodoc:
596
+ def initialize(name, scope, options, active_record)
597
+ super(name, scope, options, active_record)
598
+ end
599
+
600
+ def macro; :has_one; end
601
+
602
+ def has_one?; true; end
603
+ end
604
+
605
+ class BelongsToReflection < AssociationReflection # :nodoc:
606
+ def initialize(name, scope, options, active_record)
607
+ super(name, scope, options, active_record)
608
+ end
609
+
610
+ def macro; :belongs_to; end
611
+
612
+ def belongs_to?; true; end
613
+
614
+ def join_keys(assoc_klass)
615
+ key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
616
+ JoinKeys.new(key, foreign_key)
617
+ end
618
+
619
+ def join_id_for(owner) # :nodoc:
620
+ owner[foreign_key]
621
+ end
622
+ end
623
+
624
+ class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
625
+ def initialize(name, scope, options, active_record)
626
+ super
627
+ end
628
+
629
+ def macro; :has_and_belongs_to_many; end
630
+
631
+ def collection?
632
+ true
633
+ end
634
+ end
635
+
508
636
  # Holds all the meta-data about a :through association as it was specified
509
637
  # in the Active Record class.
510
- class ThroughReflection < AssociationReflection #:nodoc:
638
+ class ThroughReflection < AbstractReflection #:nodoc:
639
+ attr_reader :delegate_reflection
511
640
  delegate :foreign_key, :foreign_type, :association_foreign_key,
512
641
  :active_record_primary_key, :type, :to => :source_reflection
513
642
 
514
- def initialize(macro, name, scope, options, active_record)
515
- super
516
- @source_reflection_name = options[:source]
643
+ def initialize(delegate_reflection)
644
+ @delegate_reflection = delegate_reflection
645
+ @klass = delegate_reflection.options[:anonymous_class]
646
+ @source_reflection_name = delegate_reflection.options[:source]
647
+ end
648
+
649
+ def klass
650
+ @klass ||= delegate_reflection.compute_class(class_name)
517
651
  end
518
652
 
519
653
  # Returns the source of the through reflection. It checks both a singularized
@@ -531,12 +665,10 @@ module ActiveRecord
531
665
  #
532
666
  # tags_reflection = Post.reflect_on_association(:tags)
533
667
  # tags_reflection.source_reflection
534
- # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
668
+ # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
535
669
  #
536
670
  def source_reflection
537
- if source_reflection_name
538
- through_reflection.klass._reflect_on_association(source_reflection_name)
539
- end
671
+ through_reflection.klass._reflect_on_association(source_reflection_name)
540
672
  end
541
673
 
542
674
  # Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -549,7 +681,7 @@ module ActiveRecord
549
681
  #
550
682
  # tags_reflection = Post.reflect_on_association(:tags)
551
683
  # tags_reflection.through_reflection
552
- # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
684
+ # # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
553
685
  #
554
686
  def through_reflection
555
687
  active_record._reflect_on_association(options[:through])
@@ -569,8 +701,8 @@ module ActiveRecord
569
701
  #
570
702
  # tags_reflection = Post.reflect_on_association(:tags)
571
703
  # tags_reflection.chain
572
- # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
573
- # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
704
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
705
+ # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
574
706
  #
575
707
  def chain
576
708
  @chain ||= begin
@@ -623,8 +755,17 @@ module ActiveRecord
623
755
  end
624
756
  end
625
757
 
758
+ def join_keys(assoc_klass)
759
+ source_reflection.join_keys(assoc_klass)
760
+ end
761
+
626
762
  # The macro used by the source association
627
763
  def source_macro
764
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
765
+ ActiveRecord::Base.source_macro is deprecated and will be removed
766
+ without replacement.
767
+ MSG
768
+
628
769
  source_reflection.source_macro
629
770
  end
630
771
 
@@ -654,11 +795,11 @@ module ActiveRecord
654
795
  # # => [:tag, :tags]
655
796
  #
656
797
  def source_reflection_names
657
- (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
798
+ options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
658
799
  end
659
800
 
660
801
  def source_reflection_name # :nodoc:
661
- return @source_reflection_name.to_sym if @source_reflection_name
802
+ return @source_reflection_name if @source_reflection_name
662
803
 
663
804
  names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
664
805
  names = names.find_all { |n|
@@ -668,15 +809,13 @@ module ActiveRecord
668
809
  if names.length > 1
669
810
  example_options = options.dup
670
811
  example_options[:source] = source_reflection_names.first
671
- ActiveSupport::Deprecation.warn <<-eowarn
672
- Ambiguous source reflection for through association. Please specify a :source
673
- directive on your declaration like:
674
-
675
- class #{active_record.name} < ActiveRecord::Base
676
- #{macro} :#{name}, #{example_options}
677
- end
678
-
679
- eowarn
812
+ ActiveSupport::Deprecation.warn \
813
+ "Ambiguous source reflection for through association. Please " \
814
+ "specify a :source directive on your declaration like:\n" \
815
+ "\n" \
816
+ " class #{active_record.name} < ActiveRecord::Base\n" \
817
+ " #{macro} :#{name}, #{example_options}\n" \
818
+ " end"
680
819
  end
681
820
 
682
821
  @source_reflection_name = names.first
@@ -690,28 +829,36 @@ directive on your declaration like:
690
829
  through_reflection.options
691
830
  end
692
831
 
832
+ def join_id_for(owner) # :nodoc:
833
+ source_reflection.join_id_for(owner)
834
+ end
835
+
693
836
  def check_validity!
694
837
  if through_reflection.nil?
695
838
  raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
696
839
  end
697
840
 
698
- if through_reflection.options[:polymorphic]
699
- raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
841
+ if through_reflection.polymorphic?
842
+ if has_one?
843
+ raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
844
+ else
845
+ raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
846
+ end
700
847
  end
701
848
 
702
849
  if source_reflection.nil?
703
850
  raise HasManyThroughSourceAssociationNotFoundError.new(self)
704
851
  end
705
852
 
706
- if options[:source_type] && source_reflection.options[:polymorphic].nil?
853
+ if options[:source_type] && !source_reflection.polymorphic?
707
854
  raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
708
855
  end
709
856
 
710
- if source_reflection.options[:polymorphic] && options[:source_type].nil?
857
+ if source_reflection.polymorphic? && options[:source_type].nil?
711
858
  raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
712
859
  end
713
860
 
714
- if macro == :has_one && through_reflection.collection?
861
+ if has_one? && through_reflection.collection?
715
862
  raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
716
863
  end
717
864
 
@@ -720,15 +867,27 @@ directive on your declaration like:
720
867
 
721
868
  protected
722
869
 
723
- def actual_source_reflection # FIXME: this is a horrible name
724
- source_reflection.actual_source_reflection
725
- end
870
+ def actual_source_reflection # FIXME: this is a horrible name
871
+ source_reflection.send(:actual_source_reflection)
872
+ end
873
+
874
+ def primary_key(klass)
875
+ klass.primary_key || raise(UnknownPrimaryKey.new(klass))
876
+ end
877
+
878
+ def inverse_name; delegate_reflection.send(:inverse_name); end
726
879
 
727
880
  private
728
881
  def derive_class_name
729
882
  # get the class_name of the belongs_to association of the through reflection
730
883
  options[:source_type] || source_reflection.class_name
731
884
  end
885
+
886
+ delegate_methods = AssociationReflection.public_instance_methods -
887
+ public_instance_methods
888
+
889
+ delegate(*delegate_methods, to: :delegate_reflection)
890
+
732
891
  end
733
892
  end
734
893
  end