aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG7
-rw-r--r--activerecord/lib/active_record.rb4
-rwxr-xr-xactiverecord/lib/active_record/associations.rb9
-rw-r--r--activerecord/lib/active_record/autosave_association.rb44
-rwxr-xr-xactiverecord/lib/active_record/base.rb12
-rw-r--r--activerecord/lib/active_record/callbacks.rb22
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb5
-rw-r--r--activerecord/lib/active_record/locale/en.yml44
-rw-r--r--activerecord/lib/active_record/migration.rb6
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb53
-rw-r--r--activerecord/lib/active_record/railtie.rb13
-rw-r--r--activerecord/lib/active_record/railties/databases.rake7
-rw-r--r--activerecord/lib/active_record/reflection.rb25
-rw-r--r--activerecord/lib/active_record/relation.rb74
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb85
-rw-r--r--activerecord/lib/active_record/test_case.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/test/cases/autosave_association_test.rb8
-rwxr-xr-xactiverecord/test/cases/base_test.rb3
-rw-r--r--activerecord/test/cases/callbacks_test.rb45
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb3
-rw-r--r--activerecord/test/cases/modules_test.rb4
-rw-r--r--activerecord/test/cases/multiple_db_test.rb6
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb39
-rw-r--r--activerecord/test/cases/reflection_test.rb58
-rw-r--r--activerecord/test/cases/relations_test.rb24
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb10
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb99
31 files changed, 445 insertions, 278 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 4846774deb..38bcf0c787 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,12 @@
*Edge*
+* Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default [DHH]
+
+* Add Relation#except. [Pratik Naik]
+
+ one_red_item = Item.where(:colour => 'red').limit(1)
+ all_items = one_red_item.except(:where, :limit)
+
* Add Relation#delete_all. [Pratik Naik]
Item.where(:colour => 'red').delete_all
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index cf439b0dc0..d5b6d40514 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -55,6 +55,7 @@ module ActiveRecord
autoload :FinderMethods
autoload :CalculationMethods
autoload :PredicateBuilder
+ autoload :SpawnMethods
end
autoload :Base
@@ -133,6 +134,9 @@ module ActiveRecord
autoload :AbstractAdapter
end
end
+
+ autoload :TestCase
+ autoload :TestFixtures, 'active_record/fixtures'
end
Arel::Table.engine = Arel::Sql::Engine.new(ActiveRecord::Base)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d74b21b690..1320f5b624 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1465,7 +1465,7 @@ module ActiveRecord
after_destroy(method_name)
end
- def find_with_associations(options = {}, join_dependency)
+ def find_with_associations(options, join_dependency)
rows = select_all_rows(options, join_dependency)
join_dependency.instantiate(rows)
rescue ThrowResult
@@ -1770,7 +1770,7 @@ module ActiveRecord
end
def using_limitable_reflections?(reflections)
- reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
+ reflections.collect(&:collection_association?).length.zero?
end
def column_aliases(join_dependency)
@@ -1843,7 +1843,7 @@ module ActiveRecord
case associations
when Symbol, String
reflection = base.reflections[associations]
- if reflection && [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
+ if reflection && reflection.collection_association?
records.each { |record| record.send(reflection.name).target.uniq! }
end
when Array
@@ -1853,12 +1853,11 @@ module ActiveRecord
when Hash
associations.keys.each do |name|
reflection = base.reflections[name]
- is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
parent_records = []
records.each do |record|
if descendant = record.send(reflection.name)
- if is_collection
+ if reflection.collection_association?
parent_records.concat descendant.target.uniq
else
parent_records << descendant
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 98ab64537e..f01d0903cd 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -158,47 +158,39 @@ module ActiveRecord
#
# For performance reasons, we don't check whether to validate at runtime,
# but instead only define the method and callback when needed. However,
- # this can change, for instance, when using nested attributes. Since we
- # don't want the callbacks to get defined multiple times, there are
- # guards that check if the save or validation methods have already been
- # defined before actually defining them.
+ # this can change, for instance, when using nested attributes, which is
+ # called _after_ the association has been defined. Since we don't want
+ # the callbacks to get defined multiple times, there are guards that
+ # check if the save or validation methods have already been defined
+ # before actually defining them.
def add_autosave_association_callbacks(reflection)
- save_method = "autosave_associated_records_for_#{reflection.name}"
- validation_method = "validate_associated_records_for_#{reflection.name}"
- force_validation = (reflection.options[:validate] == true || reflection.options[:autosave] == true)
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
+ collection = reflection.collection_association?
- case reflection.macro
- when :has_many, :has_and_belongs_to_many
- unless method_defined?(save_method)
+ unless method_defined?(save_method)
+ if collection
before_save :before_save_collection_association
define_method(save_method) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create save_method
after_update save_method
- end
-
- if !method_defined?(validation_method) &&
- (force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false))
- define_method(validation_method) { validate_collection_association(reflection) }
- validate validation_method
- end
- else
- unless method_defined?(save_method)
- case reflection.macro
- when :has_one
+ else
+ if reflection.macro == :has_one
define_method(save_method) { save_has_one_association(reflection) }
after_save save_method
- when :belongs_to
+ else
define_method(save_method) { save_belongs_to_association(reflection) }
before_save save_method
end
end
+ end
- if !method_defined?(validation_method) && force_validation
- define_method(validation_method) { validate_single_association(reflection) }
- validate validation_method
- end
+ if reflection.validate? && !method_defined?(validation_method)
+ method = (collection ? :validate_collection_association : :validate_single_association)
+ define_method(validation_method) { send(method, reflection) }
+ validate validation_method
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 70776c7aa2..026bf55aaa 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -550,7 +550,7 @@ module ActiveRecord #:nodoc:
# Determine whether to store the full constant name including namespace when using STI
superclass_delegating_accessor :store_full_sti_class
- self.store_full_sti_class = false
+ self.store_full_sti_class = true
# Stores the default scope for the class
class_inheritable_accessor :default_scoping, :instance_writer => false
@@ -1510,11 +1510,17 @@ module ActiveRecord #:nodoc:
end
def active_relation_table(table_name_alias = nil)
- Arel::Table.new(table_name, :as => table_name_alias)
+ Arel::Table.new(table_name, :as => table_name_alias, :engine => active_relation_engine)
end
def active_relation_engine
- @active_relation_engine ||= Arel::Sql::Engine.new(self)
+ @active_relation_engine ||= begin
+ if self == ActiveRecord::Base
+ Arel::Table.engine
+ else
+ connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.active_relation_engine
+ end
+ end
end
private
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index e2a8f03c8f..aecde5848c 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -9,7 +9,6 @@ module ActiveRecord
# * (-) <tt>valid</tt>
# * (1) <tt>before_validation</tt>
# * (-) <tt>validate</tt>
- # * (-) <tt>validate_on_create</tt>
# * (2) <tt>after_validation</tt>
# * (3) <tt>before_save</tt>
# * (4) <tt>before_create</tt>
@@ -223,9 +222,10 @@ module ActiveRecord
extend ActiveModel::Callbacks
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
- define_model_callbacks :validation, :only => [:before, :after]
end
module ClassMethods
@@ -236,6 +236,24 @@ module ActiveRecord
send(meth.to_sym, meth.to_sym)
end
end
+
+ def before_validation(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array(options[:if])
+ options[:if] << "@_on_validate == :#{options[:on]}"
+ end
+ set_callback(:validation, :before, *args, &block)
+ end
+
+ def after_validation(*args, &block)
+ options = args.extract_options!
+ options[:prepend] = true
+ options[:if] = Array(options[:if])
+ options[:if] << "!halted && value != false"
+ options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
+ set_callback(:validation, :after, *(args << options), &block)
+ end
end
def create_or_update_with_callbacks #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d09aa3c4d2..5eedf448a4 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -201,7 +201,7 @@ module ActiveRecord
protected
def log(sql, name)
result = nil
- ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name) do
+ ActiveSupport::Notifications.instrument("active_record.sql", :sql => sql, :name => name) do
@runtime += Benchmark.ms { result = yield }
end
result
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 99b812b5fc..b528c14bcc 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -3,7 +3,6 @@ require 'yaml'
require 'csv'
require 'zlib'
require 'active_support/dependencies'
-require 'active_support/test_case'
require 'active_support/core_ext/logger'
if RUBY_VERSION < '1.9'
@@ -823,8 +822,8 @@ module ActiveRecord
superclass_delegating_accessor :pre_loaded_fixtures
self.fixture_table_names = []
- self.use_transactional_fixtures = false
- self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = true
+ self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
self.fixture_class_names = {}
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index e33d389f8c..4115cc8e17 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -1,33 +1,9 @@
en:
- activerecord:
- errors:
- # model.errors.full_messages format.
- format: "{{attribute}} {{message}}"
-
- # The values :model, :attribute and :value are always available for interpolation
- # The value :count is available when applicable. Can be used for pluralization.
- messages:
- inclusion: "is not included in the list"
- exclusion: "is reserved"
- invalid: "is invalid"
- confirmation: "doesn't match confirmation"
- accepted: "must be accepted"
- empty: "can't be empty"
- blank: "can't be blank"
- too_long: "is too long (maximum is {{count}} characters)"
- too_short: "is too short (minimum is {{count}} characters)"
- wrong_length: "is the wrong length (should be {{count}} characters)"
- taken: "has already been taken"
- not_a_number: "is not a number"
- greater_than: "must be greater than {{count}}"
- greater_than_or_equal_to: "must be greater than or equal to {{count}}"
- equal_to: "must be equal to {{count}}"
- less_than: "must be less than {{count}}"
- less_than_or_equal_to: "must be less than or equal to {{count}}"
- odd: "must be odd"
- even: "must be even"
- record_invalid: "Validation failed: {{errors}}"
- # Append your own errors here or at the model/attributes scope.
+ errors:
+ messages:
+ taken: "has already been taken"
+ record_invalid: "Validation failed: {{errors}}"
+ # Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation.
@@ -42,7 +18,14 @@ en:
# Will define custom blank validation message for User model and
# custom blank validation message for login attribute of User model.
#models:
-
+
+ # Attributes names common to most models
+ #attributes:
+ #created_at: "Created at"
+ #updated_at: "Updated at"
+
+ # ActiveRecord models configuration
+ #activerecord:
# Translate model names. Used in Model.human_name().
#models:
# For example,
@@ -55,4 +38,3 @@ en:
# user:
# login: "Handle"
# will translate User attribute "login" as "Handle"
-
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 09a558a636..0022e8cc37 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -339,6 +339,10 @@ module ActiveRecord
self.verbose = save
end
+ def connection
+ ActiveRecord::Base.connection
+ end
+
def method_missing(method, *arguments, &block)
arg_list = arguments.map(&:inspect) * ', '
@@ -346,7 +350,7 @@ module ActiveRecord
unless arguments.empty? || method == :execute
arguments[0] = Migrator.proper_table_name(arguments.first)
end
- Base.connection.send(method, *arguments, &block)
+ connection.send(method, *arguments, &block)
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index ff3a51d5c0..9038888d22 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -188,6 +188,8 @@ module ActiveRecord
# the parent model is saved. This happens inside the transaction initiated
# by the parents save method. See ActiveRecord::AutosaveAssociation.
module ClassMethods
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
+
# Defines an attributes writer for the specified association(s). If you
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
# will need to add the attribute writer to the allowed list.
@@ -229,23 +231,14 @@ module ActiveRecord
options = { :allow_destroy => false, :update_only => false }
options.update(attr_names.extract_options!)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
+ options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
attr_names.each do |association_name|
if reflection = reflect_on_association(association_name)
- type = case reflection.macro
- when :has_one, :belongs_to
- :one_to_one
- when :has_many, :has_and_belongs_to_many
- :collection
- end
-
reflection.options[:autosave] = true
add_autosave_association_callbacks(reflection)
- self.nested_attributes_options[association_name.to_sym] = options
-
- if options[:reject_if] == :all_blank
- self.nested_attributes_options[association_name.to_sym][:reject_if] = proc { |attributes| attributes.all? {|k,v| v.blank?} }
- end
+ nested_attributes_options[association_name.to_sym] = options
+ type = (reflection.collection_association? ? :collection : :one_to_one)
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
@@ -271,21 +264,11 @@ module ActiveRecord
marked_for_destruction?
end
- # Deal with deprecated _delete.
- #
- def _delete #:nodoc:
- ActiveSupport::Deprecation.warn "_delete is deprecated in nested attributes. Use _destroy instead."
- _destroy
- end
-
private
# Attribute hash keys that should not be assigned as normal attributes.
# These hash keys are nested attributes implementation details.
- #
- # TODO Remove _delete from UNASSIGNABLE_KEYS when deprecation warning are
- # removed.
- UNASSIGNABLE_KEYS = %w( id _destroy _delete )
+ UNASSIGNABLE_KEYS = %w( id _destroy )
# Assigns the given attributes to the association.
#
@@ -298,13 +281,17 @@ module ActiveRecord
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
check_existing_record = (options[:update_only] || !attributes['id'].blank?)
if check_existing_record && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
+
+ elsif attributes['id']
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
+
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
@@ -343,7 +330,7 @@ module ActiveRecord
# { :id => '2', :_destroy => true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -366,6 +353,8 @@ module ActiveRecord
end
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s }
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+ else
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
end
end
end
@@ -382,8 +371,7 @@ module ActiveRecord
# Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
- ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) ||
- ConnectionAdapters::Column.value_to_boolean(hash['_delete']) # TODO Remove after deprecation.
+ ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
# Determines if a new record should be build by checking for
@@ -394,14 +382,17 @@ module ActiveRecord
end
def call_reject_if(association_name, attributes)
- callback = self.nested_attributes_options[association_name][:reject_if]
-
- case callback
+ case callback = nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
when Proc
- callback.try(:call, attributes)
+ callback.call(attributes)
end
end
+
+ def raise_nested_attributes_record_not_found(association_name, record_id)
+ reflection = self.class.reflect_on_association(association_name)
+ raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 657ee738c0..55008271b7 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -62,10 +62,21 @@ module ActiveRecord
initializer "active_record.notifications" do
require 'active_support/notifications'
- ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload|
+ ActiveSupport::Notifications.subscribe("active_record.sql") do |name, before, after, instrumenter_id, payload|
ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000)
end
end
+ initializer "active_record.i18n_deprecation" do
+ require 'active_support/i18n'
+
+ begin
+ I18n.t(:"activerecord.errors", :raise => true)
+ warn "[DEPRECATION] \"activerecord.errors\" namespace is deprecated in I18n " <<
+ "yml files, please use just \"errors\" instead."
+ rescue Exception => e
+ # No message then.
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index a35a6c156b..03359221c3 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -46,7 +46,7 @@ namespace :db do
$stderr.puts "Couldn't create database for #{config.inspect}"
end
end
- return # Skip the else clause of begin/rescue
+ return # Skip the else clause of begin/rescue
else
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection
@@ -246,6 +246,7 @@ namespace :db do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :load => :environment do
require 'active_record/fixtures'
+
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
@@ -257,7 +258,7 @@ namespace :db do
desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :identify => :environment do
- require "active_record/fixtures"
+ require 'active_record/fixtures'
label, id = ENV["LABEL"], ENV["ID"]
raise "LABEL or ID required" if label.blank? && id.blank?
@@ -295,7 +296,7 @@ namespace :db do
if File.exists?(file)
load(file)
else
- abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
+ abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/boot.rb to limit the frameworks that will be loaded}
end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index b751c9ad68..d6fad5cf29 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -252,10 +252,33 @@ module ActiveRecord
end
end
+ # Returns whether or not this association reflection is for a collection
+ # association. Returns +true+ if the +macro+ is one of +has_many+ or
+ # +has_and_belongs_to_many+, +false+ otherwise.
+ def collection_association?
+ if @collection_association.nil?
+ @collection_association = [:has_many, :has_and_belongs_to_many].include?(macro)
+ end
+ @collection_association
+ end
+
+ # Returns whether or not the association should be validated as part of
+ # the parent's validation.
+ #
+ # Unless you explicitely disable validation with
+ # <tt>:validate => false</tt>, it will take place when:
+ #
+ # * you explicitely enable validation; <tt>:validate => true</tt>
+ # * you use autosave; <tt>:autosave => true</tt>
+ # * the association is a +has_many+ association
+ def validate?
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
- class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
+ class_name = class_name.singularize if collection_association?
class_name
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 114095b7ef..487b54f27d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,18 +1,18 @@
module ActiveRecord
class Relation
- include QueryMethods, FinderMethods, CalculationMethods
+ include QueryMethods, FinderMethods, CalculationMethods, SpawnMethods
delegate :length, :collect, :map, :each, :all?, :to => :to_a
attr_reader :relation, :klass
attr_writer :readonly, :table
- attr_accessor :preload_associations, :eager_load_associations, :include_associations
+ attr_accessor :preload_associations, :eager_load_associations, :includes_associations, :create_with_attributes
def initialize(klass, relation)
@klass, @relation = klass, relation
@preload_associations = []
@eager_load_associations = []
- @include_associations = []
+ @includes_associations = []
@loaded, @readonly = false
end
@@ -28,38 +28,6 @@ module ActiveRecord
with_create_scope { @klass.create!(*args, &block) }
end
- def merge(r)
- raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
-
- merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations)
- merged_relation.readonly = r.readonly
-
- [self.relation, r.relation].each do |arel|
- merged_relation = merged_relation.
- joins(arel.joins(arel)).
- group(arel.groupings).
- order(arel.send(:order_clauses).join(', ')).
- limit(arel.taken).
- offset(arel.skipped).
- select(arel.send(:select_clauses)).
- from(arel.sources)
- end
-
- merged_wheres = @relation.wheres
-
- r.wheres.each do |w|
- if w.is_a?(Arel::Predicates::Equality)
- merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
- end
-
- merged_wheres << w
- end
-
- merged_relation.where(*merged_wheres)
- end
-
- alias :& :merge
-
def respond_to?(method, include_private = false)
return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method)
@@ -83,13 +51,13 @@ module ActiveRecord
:select => @relation.send(:select_clauses).join(', '),
:joins => @relation.joins(relation),
:group => @relation.send(:group_clauses).join(', '),
- :order => @relation.send(:order_clauses).join(', '),
+ :order => order_clause,
:conditions => where_clause,
:limit => @relation.taken,
:offset => @relation.skipped,
:from => (@relation.send(:from_clauses) if @relation.send(:sources).present?)
},
- ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @include_associations, nil))
+ ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @includes_associations, nil))
rescue ThrowResult
[]
end
@@ -98,7 +66,7 @@ module ActiveRecord
end
preload = @preload_associations
- preload += @include_associations unless find_with_associations
+ preload += @includes_associations unless find_with_associations
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
@records.each { |record| record.readonly! } if @readonly
@@ -156,23 +124,13 @@ module ActiveRecord
end
def reset
- @first = @last = @create_scope = @to_sql = nil
+ @first = @last = @to_sql = @order_clause = @scope_for_create = nil
@records = []
self
end
- def spawn(relation = @relation)
- relation = Relation.new(@klass, relation)
- relation.readonly = @readonly
- relation.preload_associations = @preload_associations
- relation.eager_load_associations = @eager_load_associations
- relation.include_associations = @include_associations
- relation.table = table
- relation
- end
-
def table
- @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass))
+ @table ||= Arel::Table.new(@klass.table_name, :engine => @klass.active_relation_engine)
end
def primary_key
@@ -205,13 +163,15 @@ module ActiveRecord
end
def with_create_scope
- @klass.send(:with_scope, :create => create_scope) { yield }
+ @klass.send(:with_scope, :create => scope_for_create) { yield }
end
- def create_scope
- @create_scope ||= wheres.inject({}) do |hash, where|
- hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
- hash
+ def scope_for_create
+ @scope_for_create ||= begin
+ @create_with_attributes || wheres.inject({}) do |hash, where|
+ hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
+ hash
+ end
end
end
@@ -219,6 +179,10 @@ module ActiveRecord
@relation.send(:where_clauses).join(join_string)
end
+ def order_clause
+ @order_clause ||= @relation.send(:order_clauses).join(', ')
+ end
+
def references_eager_loaded_tables?
joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq
(tables_in_string(to_sql) - joined_tables).any?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index cf2cc7ba70..5d7bf0b7bc 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -6,7 +6,7 @@ module ActiveRecord
end
def includes(*associations)
- spawn.tap {|r| r.include_associations += Array.wrap(associations) }
+ spawn.tap {|r| r.includes_associations += Array.wrap(associations) }
end
def eager_load(*associations)
@@ -17,6 +17,10 @@ module ActiveRecord
spawn.tap {|r| r.readonly = status }
end
+ def create_with(attributes = {})
+ spawn.tap {|r| r.create_with_attributes = attributes }
+ end
+
def select(selects)
if selects.present?
relation = spawn(@relation.project(selects))
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
new file mode 100644
index 0000000000..4ecee8c634
--- /dev/null
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -0,0 +1,85 @@
+module ActiveRecord
+ module SpawnMethods
+ def spawn(relation = @relation)
+ relation = Relation.new(@klass, relation)
+ relation.readonly = @readonly
+ relation.preload_associations = @preload_associations
+ relation.eager_load_associations = @eager_load_associations
+ relation.includes_associations = @includes_associations
+ relation.create_with_attributes = @create_with_attributes
+ relation.table = table
+ relation
+ end
+
+ def merge(r)
+ raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
+
+ merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.includes_associations)
+ merged_relation.readonly = r.readonly
+
+ [self.relation, r.relation].each do |arel|
+ merged_relation = merged_relation.
+ joins(arel.joins(arel)).
+ group(arel.groupings).
+ limit(arel.taken).
+ offset(arel.skipped).
+ select(arel.send(:select_clauses)).
+ from(arel.sources).
+ having(arel.havings).
+ lock(arel.locked)
+ end
+
+ relation_order = r.send(:order_clause)
+ merged_order = relation_order.present? ? relation_order : order_clause
+ merged_relation = merged_relation.order(merged_order)
+
+ merged_relation.create_with_attributes = @create_with_attributes
+
+ if @create_with_attributes && r.create_with_attributes
+ merged_relation.create_with_attributes = @create_with_attributes.merge(r.create_with_attributes)
+ else
+ merged_relation.create_with_attributes = r.create_with_attributes || @create_with_attributes
+ end
+
+ merged_wheres = @relation.wheres
+
+ r.wheres.each do |w|
+ if w.is_a?(Arel::Predicates::Equality)
+ merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
+ end
+
+ merged_wheres << w
+ end
+
+ merged_relation.where(*merged_wheres)
+ end
+
+ alias :& :merge
+
+ def except(*skips)
+ result = Relation.new(@klass, table)
+ result.table = table
+
+ [:eager_load, :preload, :includes].each do |load_method|
+ result = result.send(load_method, send(:"#{load_method}_associations"))
+ end
+
+ result.readonly = self.readonly unless skips.include?(:readonly)
+ result.create_with_attributes = @create_with_attributes unless skips.include?(:create_with)
+
+ result = result.joins(@relation.joins(@relation)) unless skips.include?(:joins)
+ result = result.group(@relation.groupings) unless skips.include?(:group)
+ result = result.limit(@relation.taken) unless skips.include?(:limit)
+ result = result.offset(@relation.skipped) unless skips.include?(:offset)
+ result = result.select(@relation.send(:select_clauses)) unless skips.include?(:select)
+ result = result.from(@relation.sources) unless skips.include?(:from)
+ result = result.order(order_clause) unless skips.include?(:order)
+ result = result.where(*@relation.wheres) unless skips.include?(:where)
+ result = result.having(*@relation.havings) unless skips.include?(:having)
+ result = result.lock(@relation.locked) unless skips.include?(:lock)
+
+ result
+ end
+
+ end
+end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 2dfe2c09ea..0a77ad5fd7 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -1,5 +1,3 @@
-require "active_support/test_case"
-
module ActiveRecord
class TestCase < ActiveSupport::TestCase #:nodoc:
def assert_date_from_db(expected, actual, message = nil)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 12c1f23763..d5adcba3ba 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -11,7 +11,7 @@ module ActiveRecord
def initialize(record)
@record = record
errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
- super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
+ super(I18n.t('errors.messages.record_invalid', :errors => errors))
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index cf763d730a..116c8fc509 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -974,9 +974,9 @@ module AutosaveAssociationOnACollectionAssociationTests
end
def test_should_default_invalid_error_from_i18n
- I18n.backend.store_translations(:en, :activerecord => { :errors => { :models =>
+ I18n.backend.store_translations(:en, :errors => { :models =>
{ @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } }
- }})
+ })
@pirate.send(@association_name).build(:name => '')
@@ -985,9 +985,7 @@ module AutosaveAssociationOnACollectionAssociationTests
assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages
assert @pirate.errors[@association_name].empty?
ensure
- I18n.backend.store_translations(:en, :activerecord => { :errors => { :models =>
- { @association_name.to_s.singularize.to_sym => nil }
- }})
+ I18n.backend = I18n::Backend::Simple.new
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 730d9d8df7..47e7a4bb79 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -2138,8 +2138,11 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_type_name_with_module_should_handle_beginning
+ ActiveRecord::Base.store_full_sti_class = false
assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person')
assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person')
+ ensure
+ ActiveRecord::Base.store_full_sti_class = true
end
def test_to_param_should_return_string
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 5a084a611e..ff2322ac15 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -113,6 +113,26 @@ class ImmutableMethodDeveloper < ActiveRecord::Base
end
end
+class OnCallbacksDeveloper < ActiveRecord::Base
+ set_table_name 'developers'
+
+ before_validation { history << :before_validation }
+ before_validation(:on => :create){ history << :before_validation_on_create }
+ before_validation(:on => :update){ history << :before_validation_on_update }
+
+ validate do
+ history << :validate
+ end
+
+ after_validation { history << :after_validation }
+ after_validation(:on => :create){ history << :after_validation_on_create }
+ after_validation(:on => :update){ history << :after_validation_on_update }
+
+ def history
+ @history ||= []
+ end
+end
+
class CallbackCancellationDeveloper < ActiveRecord::Base
set_table_name 'developers'
@@ -250,7 +270,18 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
- def test_save
+ def test_validate_on_create
+ david = OnCallbacksDeveloper.create('name' => 'David', 'salary' => 1000000)
+ assert_equal [
+ :before_validation,
+ :before_validation_on_create,
+ :validate,
+ :after_validation,
+ :after_validation_on_create
+ ], david.history
+ end
+
+ def test_update
david = CallbackDeveloper.find(1)
david.save
assert_equal [
@@ -297,6 +328,18 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
+ def test_validate_on_update
+ david = OnCallbacksDeveloper.find(1)
+ david.save
+ assert_equal [
+ :before_validation,
+ :before_validation_on_update,
+ :validate,
+ :after_validation,
+ :after_validation_on_update
+ ], david.history
+ end
+
def test_destroy
david = CallbackDeveloper.find(1)
david.destroy
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 479970b2fa..fa76e2d57a 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -13,8 +13,6 @@ require 'test/unit'
require 'stringio'
require 'active_record'
-require 'active_record/test_case'
-require 'active_record/fixtures'
require 'connection'
begin
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 73e51fbd91..0672fb938b 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -241,6 +241,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
end
def test_instantiation_doesnt_try_to_require_corresponding_file
+ ActiveRecord::Base.store_full_sti_class = false
foo = Firm.find(:first).clone
foo.ruby_type = foo.type = 'FirmOnTheFly'
foo.save!
@@ -259,5 +260,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
# And instantiate will find the existing constant rather than trying
# to require firm_on_the_fly.
assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
+ ensure
+ ActiveRecord::Base.store_full_sti_class = true
end
end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 4f559bcaa5..d781a229f4 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -12,6 +12,8 @@ class ModulesTest < ActiveRecord::TestCase
[:Firm, :Client].each do |const|
@undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const)
end
+
+ ActiveRecord::Base.store_full_sti_class = false
end
def teardown
@@ -19,6 +21,8 @@ class ModulesTest < ActiveRecord::TestCase
@undefined_consts.each do |constant, value|
Object.send :const_set, constant, value unless value.nil?
end
+
+ ActiveRecord::Base.store_full_sti_class = true
end
def test_module_spanning_associations
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 7c3e0f2ca6..6155bfd50a 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/entrant'
+require 'models/bird'
# So we can test whether Course.connection survives a reload.
require_dependency 'models/course'
@@ -82,4 +83,9 @@ class MultipleDbTest < ActiveRecord::TestCase
assert_equal "Ruby Development", Course.find(1).name
assert_equal "Ruby Developer", Entrant.find(1).name
end
+
+ def test_arel_table_engines
+ assert_not_equal Entrant.active_relation_engine, Course.active_relation_engine
+ assert_equal Entrant.active_relation_engine, Bird.active_relation_engine
+ end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 8891282915..7ca9c416cb 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -34,7 +34,10 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_should_add_a_proc_to_nested_attributes_options
- [:parrots, :birds, :birds_with_reject_all_blank].each do |name|
+ assert_equal ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC,
+ Pirate.nested_attributes_options[:birds_with_reject_all_blank][:reject_if]
+
+ [:parrots, :birds].each do |name|
assert_instance_of Proc, Pirate.nested_attributes_options[name][:reject_if]
end
end
@@ -79,12 +82,6 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert ship._destroy
end
- def test_underscore_delete_is_deprecated
- ActiveSupport::Deprecation.expects(:warn)
- ship = Ship.create!(:name => 'Nights Dirty Lightning')
- ship._delete
- end
-
def test_reject_if_method_without_arguments
Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record?
@@ -176,6 +173,12 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
end
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
+ assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ @pirate.ship_attributes = { :id => 1234567890 }
+ end
+ end
+
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }
@@ -269,6 +272,8 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
+ include AssertRaiseWithMessage
+
def setup
@ship = Ship.new(:name => 'Nights Dirty Lightning')
@pirate = @ship.build_pirate(:catchphrase => 'Aye')
@@ -323,6 +328,12 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
assert_equal 'Arr', @ship.pirate.catchphrase
end
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
+ assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do
+ @ship.pirate_attributes = { :id => 1234567890 }
+ end
+ end
+
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' }
@@ -384,10 +395,6 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
assert Ship.reflect_on_association(:pirate).options[:autosave]
end
- def test_should_accept_update_only_option
- @ship.update_attribute(:update_only_pirate_attributes, { :id => @pirate.ship.id, :catchphrase => 'Arr' })
- end
-
def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
@pirate.delete
assert_difference('Pirate.count', 1) do
@@ -460,6 +467,12 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
end
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
+ assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ @pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
+ end
+ end
+
def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
@pirate.send(@association_name).destroy_all
@pirate.reload.attributes = {
@@ -565,7 +578,7 @@ module NestedAttributesOnACollectionAssociationTests
assert Pirate.reflect_on_association(@association_name).options[:autosave]
end
- def test_validate_presence_of_parent__works_with_inverse_of
+ def test_validate_presence_of_parent_works_with_inverse_of
Man.accepts_nested_attributes_for(:interests)
assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of]
assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of]
@@ -582,7 +595,7 @@ module NestedAttributesOnACollectionAssociationTests
end
end
- def test_validate_presence_of_parent__fails_without_inverse_of
+ def test_validate_presence_of_parent_fails_without_inverse_of
Man.accepts_nested_attributes_for(:interests)
Man.reflect_on_association(:interests).options.delete(:inverse_of)
Interest.reflect_on_association(:man).options.delete(:inverse_of)
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index acd214eb5a..774ab7aa4c 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -4,10 +4,13 @@ require 'models/customer'
require 'models/company'
require 'models/company_in_module'
require 'models/subscriber'
+require 'models/ship'
require 'models/pirate'
require 'models/price_estimate'
class ReflectionTest < ActiveRecord::TestCase
+ include ActiveRecord::Reflection
+
fixtures :topics, :customers, :companies, :subscribers, :price_estimates
def setup
@@ -68,22 +71,22 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_klass_for_nested_class_name
- reflection = ActiveRecord::Reflection::MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil)
+ reflection = MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil)
assert_nothing_raised do
assert_equal MyApplication::Business::Company, reflection.klass
end
end
def test_aggregation_reflection
- reflection_for_address = ActiveRecord::Reflection::AggregateReflection.new(
+ reflection_for_address = AggregateReflection.new(
:composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
)
- reflection_for_balance = ActiveRecord::Reflection::AggregateReflection.new(
+ reflection_for_balance = AggregateReflection.new(
:composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
)
- reflection_for_gps_location = ActiveRecord::Reflection::AggregateReflection.new(
+ reflection_for_gps_location = AggregateReflection.new(
:composed_of, :gps_location, { }, Customer
)
@@ -108,7 +111,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_has_many_reflection
- reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm)
+ reflection_for_clients = AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm)
assert_equal reflection_for_clients, Firm.reflect_on_association(:clients)
@@ -120,7 +123,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_has_one_reflection
- reflection_for_account = ActiveRecord::Reflection::AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
+ reflection_for_account = AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
assert_equal reflection_for_account, Firm.reflect_on_association(:account)
assert_equal Account, Firm.reflect_on_association(:account).klass
@@ -137,6 +140,8 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_association_reflection_in_modules
+ ActiveRecord::Base.store_full_sti_class = false
+
assert_reflection MyApplication::Business::Firm,
:clients_of_firm,
:klass => MyApplication::Business::Client,
@@ -172,6 +177,8 @@ class ReflectionTest < ActiveRecord::TestCase
:klass => MyApplication::Billing::Nested::Firm,
:class_name => 'Nested::Firm',
:table_name => 'companies'
+ ensure
+ ActiveRecord::Base.store_full_sti_class = true
end
def test_reflection_of_all_associations
@@ -187,7 +194,44 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_has_many_through_reflection
- assert_kind_of ActiveRecord::Reflection::ThroughReflection, Subscriber.reflect_on_association(:books)
+ assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books)
+ end
+
+ def test_collection_association
+ assert Pirate.reflect_on_association(:birds).collection_association?
+ assert Pirate.reflect_on_association(:parrots).collection_association?
+
+ assert !Pirate.reflect_on_association(:ship).collection_association?
+ assert !Ship.reflect_on_association(:pirate).collection_association?
+ end
+
+ def test_default_association_validation
+ assert AssociationReflection.new(:has_many, :clients, {}, Firm).validate?
+
+ assert !AssociationReflection.new(:has_one, :client, {}, Firm).validate?
+ assert !AssociationReflection.new(:belongs_to, :client, {}, Firm).validate?
+ assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, {}, Firm).validate?
+ end
+
+ def test_always_validate_association_if_explicit
+ assert AssociationReflection.new(:has_one, :client, { :validate => true }, Firm).validate?
+ assert AssociationReflection.new(:belongs_to, :client, { :validate => true }, Firm).validate?
+ assert AssociationReflection.new(:has_many, :clients, { :validate => true }, Firm).validate?
+ assert AssociationReflection.new(:has_and_belongs_to_many, :clients, { :validate => true }, Firm).validate?
+ end
+
+ def test_validate_association_if_autosave
+ assert AssociationReflection.new(:has_one, :client, { :autosave => true }, Firm).validate?
+ assert AssociationReflection.new(:belongs_to, :client, { :autosave => true }, Firm).validate?
+ assert AssociationReflection.new(:has_many, :clients, { :autosave => true }, Firm).validate?
+ assert AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true }, Firm).validate?
+ end
+
+ def test_never_validate_association_if_explicit
+ assert !AssociationReflection.new(:has_one, :client, { :autosave => true, :validate => false }, Firm).validate?
+ assert !AssociationReflection.new(:belongs_to, :client, { :autosave => true, :validate => false }, Firm).validate?
+ assert !AssociationReflection.new(:has_many, :clients, { :autosave => true, :validate => false }, Firm).validate?
+ assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true, :validate => false }, Firm).validate?
end
private
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 18f6152cc0..195889f1df 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -407,6 +407,11 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_relation_merging_with_locks
+ devs = Developer.lock.where("salary >= 80000").order("id DESC") & Developer.limit(2)
+ assert devs.locked.present?
+ end
+
def test_relation_merging_with_preload
[Post.scoped & Post.preload(:author), Post.preload(:author) & Post.scoped].each do |posts|
assert_queries(2) { assert posts.first.author }
@@ -540,4 +545,23 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 'hen', hen.name
end
+ def test_explicit_create_scope
+ hens = Bird.where(:name => 'hen')
+ assert_equal 'hen', hens.new.name
+
+ hens = hens.create_with(:name => 'cock')
+ assert_equal 'cock', hens.new.name
+ end
+
+ def test_except
+ relation = Post.where(:author_id => 1).order('id ASC').limit(1)
+ assert_equal [posts(:welcome)], relation.all
+
+ author_posts = relation.except(:order, :limit)
+ assert_equal Post.where(:author_id => 1).all, author_posts.all
+
+ all_posts = relation.except(:where, :order, :limit)
+ assert_equal Post.all, all_posts.all
+ end
+
end
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index 3f96d7973b..15730c2a87 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -6,15 +6,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
def setup
Topic.reset_callbacks(:validate)
@topic = Topic.new
- I18n.backend.store_translations :'en', {
- :activerecord => {
- :errors => {
- :messages => {
- :taken => "has already been taken",
- }
- }
- }
- }
+ I18n.backend = I18n::Backend::Simple.new
end
# validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index f017f24048..5dfbb1516f 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -4,13 +4,14 @@ require 'models/reply'
class I18nValidationTest < ActiveRecord::TestCase
repair_validations(Topic, Reply)
+
def setup
Reply.validates_presence_of(:title)
@topic = Topic.new
- @old_load_path, @old_backend = I18n.load_path, I18n.backend
+ @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
I18n.load_path.clear
I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations('en', :activerecord => {:errors => {:messages => {:custom => nil}}})
+ I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}})
end
def teardown
@@ -30,75 +31,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
end
- # ActiveRecord::Errors
- def test_errors_generate_message_translates_custom_model_attribute_key
- I18n.expects(:translate).with(
- :topic,
- { :count => 1,
- :default => ['Topic'],
- :scope => [:activerecord, :models]
- }
- ).returns('Topic')
-
- I18n.expects(:translate).with(
- :"topic.title",
- { :count => 1,
- :default => ['Title'],
- :scope => [:activerecord, :attributes]
- }
- ).returns('Title')
-
- I18n.expects(:translate).with(
- :"models.topic.attributes.title.invalid",
- :value => nil,
- :scope => [:activerecord, :errors],
- :default => [
- :"models.topic.invalid",
- 'default from class def error 1',
- :"messages.invalid"],
- :attribute => "Title",
- :model => "Topic"
- ).returns('default from class def error 1')
-
- @topic.errors.generate_message :title, :invalid, :default => 'default from class def error 1'
- end
-
- def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti
-
- I18n.expects(:translate).with(
- :reply,
- { :count => 1,
- :default => [:topic, 'Reply'],
- :scope => [:activerecord, :models]
- }
- ).returns('Reply')
-
- I18n.expects(:translate).with(
- :"reply.title",
- { :count => 1,
- :default => [:'topic.title', 'Title'],
- :scope => [:activerecord, :attributes]
- }
- ).returns('Title')
-
- I18n.expects(:translate).with(
- :"models.reply.attributes.title.invalid",
- :value => nil,
- :scope => [:activerecord, :errors],
- :default => [
- :"models.reply.invalid",
- :"models.topic.attributes.title.invalid",
- :"models.topic.invalid",
- 'default from class def',
- :"messages.invalid"],
- :model => 'Reply',
- :attribute => 'Title'
- ).returns("default from class def")
-
- Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def'
-
- end
-
# validates_uniqueness_of w/ mocha
def test_validates_uniqueness_of_generates_message
@@ -115,6 +47,25 @@ class I18nValidationTest < ActiveRecord::TestCase
@topic.valid?
end
+ # validates_uniqueness_of w/o mocha
+
+ def test_validates_associated_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:title => {:taken => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}}
+
+ Topic.validates_uniqueness_of :title
+ unique_topic.valid?
+ assert_equal ['custom message'], unique_topic.errors[:replies]
+ end
+
+ def test_validates_associated_finds_global_default_translation
+ I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}}
+
+ Topic.validates_uniqueness_of :title
+ unique_topic.valid?
+ assert_equal ['global message'], unique_topic.errors[:replies]
+ end
+
# validates_associated w/ mocha
def test_validates_associated_generates_message
@@ -132,8 +83,8 @@ class I18nValidationTest < ActiveRecord::TestCase
# validates_associated w/o mocha
def test_validates_associated_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
@@ -141,7 +92,7 @@ class I18nValidationTest < ActiveRecord::TestCase
end
def test_validates_associated_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?