1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Make sure nested through associations are read only

This commit is contained in:
Jon Leighton 2010-10-15 17:46:09 +01:00
parent d619e39938
commit edc176d33b
6 changed files with 82 additions and 9 deletions

View file

@ -64,6 +64,12 @@ module ActiveRecord
super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
end
end
class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
end
end
class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc:
def initialize(reflection)

View file

@ -8,6 +8,11 @@ module ActiveRecord
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
include ThroughAssociationScope
def build(attributes = {}, &block)
ensure_not_nested
super
end
alias_method :new, :build
def create!(attrs = nil)
@ -37,6 +42,7 @@ module ActiveRecord
protected
def create_record(attrs, force = true)
ensure_not_nested
ensure_owner_is_not_new
transaction do
@ -60,6 +66,8 @@ module ActiveRecord
end
def insert_record(record, force = true, validate = true)
ensure_not_nested
if record.new_record?
if force
record.save!
@ -75,6 +83,8 @@ module ActiveRecord
# TODO - add dependent option support
def delete_records(records)
ensure_not_nested
klass = @reflection.through_reflection.klass
records.each do |associate|
klass.delete_all(construct_join_attributes(associate))

View file

@ -14,6 +14,8 @@ module ActiveRecord
private
def create_through_record(new_value) #nodoc:
ensure_not_nested
klass = @reflection.through_reflection.klass
current_object = @owner.send(@reflection.through_reflection.name)

View file

@ -8,15 +8,18 @@ module ActiveRecord
protected
def construct_scope
{ :create => construct_owner_attributes(@reflection),
:find => { :conditions => construct_conditions,
:joins => construct_joins,
:include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
:select => construct_select,
:order => @reflection.options[:order],
:limit => @reflection.options[:limit],
:readonly => @reflection.options[:readonly],
} }
scope = {}
scope[:find] = {
:conditions => construct_conditions,
:joins => construct_joins,
:include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
:select => construct_select,
:order => @reflection.options[:order],
:limit => @reflection.options[:limit],
:readonly => @reflection.options[:readonly]
}
scope[:create] = construct_owner_attributes(@reflection) unless @reflection.nested?
scope
end
# Build SQL conditions from attributes, qualified by table name.
@ -299,6 +302,12 @@ module ActiveRecord
end
alias_method :sql_conditions, :conditions
def ensure_not_nested
if @reflection.nested?
raise HasManyThroughNestedAssociationsAreReadonly.new(@owner, @reflection)
end
end
end
end
end

View file

@ -395,6 +395,10 @@ module ActiveRecord
chain
end
end
def nested?
through_reflection_chain.length > 2
end
# Gets an array of possible <tt>:through</tt> source reflection names:
#

View file

@ -363,6 +363,48 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase
assert !scope.where("comments.type" => "SpecialComment").empty?
assert !scope.where("comments.type" => "SubSpecialComment").empty?
end
def test_nested_has_many_through_writers_should_raise_error
david = authors(:david)
subscriber = subscribers(:first)
assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
david.subscribers = [subscriber]
end
assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
david.subscriber_ids = [subscriber.id]
end
assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
david.subscribers << subscriber
end
assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
david.subscribers.delete(subscriber)
end
assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
david.subscribers.clear
end
assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
david.subscribers.build
end
assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
david.subscribers.create
end
end
def test_nested_has_one_through_writers_should_raise_error
groucho = members(:groucho)
founding = member_types(:founding)
assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
groucho.nested_member_type = founding
end
end
private