mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Add :validate option to associations. [#301 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
This commit is contained in:
committed by
Pratik Naik
parent
f728e57d22
commit
7f140bbdda
@@ -1,5 +1,7 @@
|
||||
*Edge*
|
||||
|
||||
* Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter]
|
||||
|
||||
* PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. [Jeremy Kemper]
|
||||
|
||||
* Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess]
|
||||
|
||||
@@ -690,6 +690,7 @@ module ActiveRecord
|
||||
# association is a polymorphic +belongs_to+.
|
||||
# * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
|
||||
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
|
||||
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_many :comments, :order => "posted_on"
|
||||
@@ -710,7 +711,7 @@ module ActiveRecord
|
||||
|
||||
configure_dependency_for_has_many(reflection)
|
||||
|
||||
add_multiple_associated_save_callbacks(reflection.name)
|
||||
add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false
|
||||
add_association_callbacks(reflection.name, reflection.options)
|
||||
|
||||
if options[:through]
|
||||
@@ -769,6 +770,7 @@ module ActiveRecord
|
||||
# * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
|
||||
# association is a polymorphic +belongs_to+.
|
||||
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
|
||||
# * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
||||
@@ -799,7 +801,7 @@ module ActiveRecord
|
||||
end
|
||||
after_save method_name
|
||||
|
||||
add_single_associated_save_callbacks(reflection.name)
|
||||
add_single_associated_save_callbacks(reflection.name) if options[:validate] == true
|
||||
association_accessor_methods(reflection, HasOneAssociation)
|
||||
association_constructor_method(:build, reflection, HasOneAssociation)
|
||||
association_constructor_method(:create, reflection, HasOneAssociation)
|
||||
@@ -857,6 +859,7 @@ module ActiveRecord
|
||||
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
|
||||
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
|
||||
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
|
||||
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
|
||||
#
|
||||
# Option examples:
|
||||
# belongs_to :firm, :foreign_key => "client_of"
|
||||
@@ -937,6 +940,8 @@ module ActiveRecord
|
||||
)
|
||||
end
|
||||
|
||||
add_single_associated_save_callbacks(reflection.name) unless options[:validate] == false
|
||||
|
||||
configure_dependency_for_belongs_to(reflection)
|
||||
end
|
||||
|
||||
@@ -1025,6 +1030,7 @@ module ActiveRecord
|
||||
# * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
|
||||
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
|
||||
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
|
||||
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_and_belongs_to_many :projects
|
||||
@@ -1037,7 +1043,7 @@ module ActiveRecord
|
||||
def has_and_belongs_to_many(association_id, options = {}, &extension)
|
||||
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
||||
|
||||
add_multiple_associated_save_callbacks(reflection.name)
|
||||
add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false
|
||||
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
|
||||
|
||||
# Don't use a before_destroy callback since users' before_destroy
|
||||
@@ -1343,7 +1349,8 @@ module ActiveRecord
|
||||
:uniq,
|
||||
:finder_sql, :counter_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
:extend, :readonly
|
||||
:extend, :readonly,
|
||||
:validate
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
@@ -1353,7 +1360,7 @@ module ActiveRecord
|
||||
|
||||
def create_has_one_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate
|
||||
)
|
||||
|
||||
create_reflection(:has_one, association_id, options, self)
|
||||
@@ -1361,7 +1368,7 @@ module ActiveRecord
|
||||
|
||||
def create_has_one_through_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
|
||||
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
|
||||
)
|
||||
create_reflection(:has_one, association_id, options, self)
|
||||
end
|
||||
@@ -1369,7 +1376,7 @@ module ActiveRecord
|
||||
def create_belongs_to_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
|
||||
:counter_cache, :extend, :polymorphic, :readonly
|
||||
:counter_cache, :extend, :polymorphic, :readonly, :validate
|
||||
)
|
||||
|
||||
reflection = create_reflection(:belongs_to, association_id, options, self)
|
||||
@@ -1388,7 +1395,8 @@ module ActiveRecord
|
||||
:uniq,
|
||||
:finder_sql, :delete_sql, :insert_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
:extend, :readonly
|
||||
:extend, :readonly,
|
||||
:validate
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
|
||||
@@ -409,4 +409,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
||||
sponsor.sponsorable = new_member
|
||||
assert_equal nil, sponsor.sponsorable_id
|
||||
end
|
||||
|
||||
def test_save_fails_for_invalid_belongs_to
|
||||
assert log = AuditLog.create(:developer_id=>0,:message=>"")
|
||||
|
||||
log.developer = Developer.new
|
||||
assert !log.developer.valid?
|
||||
assert !log.valid?
|
||||
assert !log.save
|
||||
assert_equal "is invalid", log.errors.on("developer")
|
||||
end
|
||||
|
||||
def test_save_succeeds_for_invalid_belongs_to_with_validate_false
|
||||
assert log = AuditLog.create(:developer_id=>0,:message=>"")
|
||||
|
||||
log.unvalidated_developer = Developer.new
|
||||
assert !log.unvalidated_developer.valid?
|
||||
assert log.valid?
|
||||
assert log.save
|
||||
end
|
||||
end
|
||||
|
||||
@@ -342,6 +342,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
assert new_firm.new_record?
|
||||
end
|
||||
|
||||
def test_invalid_adding_with_validate_false
|
||||
firm = Firm.find(:first)
|
||||
client = Client.new
|
||||
firm.unvalidated_clients_of_firm << Client.new
|
||||
|
||||
assert firm.valid?
|
||||
assert !client.valid?
|
||||
assert firm.save
|
||||
assert client.new_record?
|
||||
end
|
||||
|
||||
def test_build
|
||||
company = companies(:first_firm)
|
||||
new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
|
||||
|
||||
@@ -275,6 +275,18 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
||||
assert_equal "is invalid", firm.errors.on("account")
|
||||
end
|
||||
|
||||
|
||||
def test_save_succeeds_for_invalid_has_one_with_validate_false
|
||||
firm = Firm.find(:first)
|
||||
assert firm.valid?
|
||||
|
||||
firm.unvalidated_account = Account.new
|
||||
|
||||
assert !firm.unvalidated_account.valid?
|
||||
assert firm.valid?
|
||||
assert firm.save
|
||||
end
|
||||
|
||||
def test_assignment_before_either_saved
|
||||
firm = Firm.new("name" => "GlobalMegaCorp")
|
||||
firm.account = a = Account.new("credit_limit" => 1000)
|
||||
|
||||
@@ -160,9 +160,9 @@ class ReflectionTest < ActiveRecord::TestCase
|
||||
|
||||
def test_reflection_of_all_associations
|
||||
# FIXME these assertions bust a lot
|
||||
assert_equal 20, Firm.reflect_on_all_associations.size
|
||||
assert_equal 16, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 4, Firm.reflect_on_all_associations(:has_one).size
|
||||
assert_equal 22, Firm.reflect_on_all_associations.size
|
||||
assert_equal 17, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 5, Firm.reflect_on_all_associations(:has_one).size
|
||||
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
|
||||
end
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class Firm < Company
|
||||
"AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )"
|
||||
has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
|
||||
has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
|
||||
has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
|
||||
has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
|
||||
has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
|
||||
has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1
|
||||
@@ -46,7 +47,8 @@ class Firm < Company
|
||||
has_many :plain_clients, :class_name => 'Client'
|
||||
has_many :readonly_clients, :class_name => 'Client', :readonly => true
|
||||
|
||||
has_one :account, :foreign_key => "firm_id", :dependent => :destroy
|
||||
has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true
|
||||
has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false
|
||||
has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account'
|
||||
has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true
|
||||
end
|
||||
|
||||
@@ -57,6 +57,7 @@ end
|
||||
|
||||
class AuditLog < ActiveRecord::Base
|
||||
belongs_to :developer
|
||||
belongs_to :unvalidated_developer, :class_name => 'Developer', :validate => false
|
||||
end
|
||||
|
||||
DeveloperSalary = Struct.new(:amount)
|
||||
|
||||
Reference in New Issue
Block a user