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:
parent
d619e39938
commit
edc176d33b
6 changed files with 82 additions and 9 deletions
|
@ -65,6 +65,12 @@ module ActiveRecord
|
|||
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)
|
||||
super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).")
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -396,6 +396,10 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def nested?
|
||||
through_reflection_chain.length > 2
|
||||
end
|
||||
|
||||
# Gets an array of possible <tt>:through</tt> source reflection names:
|
||||
#
|
||||
# [:singularized, :pluralized]
|
||||
|
|
|
@ -364,6 +364,48 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase
|
|||
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
|
||||
|
||||
def assert_includes_and_joins_equal(query, expected, association)
|
||||
|
|
Loading…
Reference in a new issue