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

@ -65,6 +65,12 @@ module ActiveRecord
end end
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: class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc:
def initialize(reflection) def initialize(reflection)
super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).") super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).")

View file

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

View file

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

View file

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

View file

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

View file

@ -364,6 +364,48 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase
assert !scope.where("comments.type" => "SubSpecialComment").empty? assert !scope.where("comments.type" => "SubSpecialComment").empty?
end 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 private
def assert_includes_and_joins_equal(query, expected, association) def assert_includes_and_joins_equal(query, expected, association)