Introduce the :readonly option to all associations. Records from the association cannot be saved. Closes #11084.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8864 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
08a4c9979d
commit
dfa786631b
|
@ -1,5 +1,7 @@
|
|||
*SVN*
|
||||
|
||||
* Introduce the :readonly option to all associations. Records from the association cannot be saved. #11084 [miloops]
|
||||
|
||||
* Multiparameter attributes for time columns fail over to DateTime when out of range of Time [Geoff Buesing]
|
||||
|
||||
* Base#instantiate_time_object uses Time.zone.local() [Geoff Buesing]
|
||||
|
|
|
@ -669,6 +669,7 @@ module ActiveRecord
|
|||
# * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
|
||||
# association is a polymorphic +belongs_to+.
|
||||
# * <tt>:uniq</tt> - if set to +true+, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
|
||||
# * <tt>:readonly</tt> - if set to +true+, all the associated objects are readonly through the association.
|
||||
#
|
||||
# Option examples:
|
||||
# has_many :comments, :order => "posted_on"
|
||||
|
@ -677,6 +678,7 @@ module ActiveRecord
|
|||
# has_many :tracks, :order => "position", :dependent => :destroy
|
||||
# has_many :comments, :dependent => :nullify
|
||||
# has_many :tags, :as => :taggable
|
||||
# has_many :reports, :readonly => true
|
||||
# has_many :subscribers, :through => :subscriptions, :source => :user
|
||||
# has_many :subscribers, :class_name => "Person", :finder_sql =>
|
||||
# 'SELECT DISTINCT people.* ' +
|
||||
|
@ -735,13 +737,15 @@ module ActiveRecord
|
|||
# as the default +foreign_key+.
|
||||
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
|
||||
# * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
|
||||
#
|
||||
# * <tt>:readonly</tt> - if set to +true+, the associated object is readonly through the association.
|
||||
#
|
||||
# Option examples:
|
||||
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
||||
# has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it
|
||||
# has_one :last_comment, :class_name => "Comment", :order => "posted_on"
|
||||
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
|
||||
# has_one :attachment, :as => :attachable
|
||||
# has_one :boss, :readonly => :true
|
||||
def has_one(association_id, options = {})
|
||||
reflection = create_has_one_reflection(association_id, options)
|
||||
|
||||
|
@ -811,6 +815,7 @@ module ActiveRecord
|
|||
# * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing +true+.
|
||||
# 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. class Post; attr_readonly :comments_count; end).
|
||||
# * <tt>:readonly</tt> - if set to +true+, the associated object is readonly through the association.
|
||||
#
|
||||
# Option examples:
|
||||
# belongs_to :firm, :foreign_key => "client_of"
|
||||
|
@ -818,6 +823,7 @@ module ActiveRecord
|
|||
# belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
|
||||
# :conditions => 'discounts > #{payments_count}'
|
||||
# belongs_to :attachable, :polymorphic => true
|
||||
# belongs_to :project, :readonly => true
|
||||
def belongs_to(association_id, options = {})
|
||||
reflection = create_belongs_to_reflection(association_id, options)
|
||||
|
||||
|
@ -970,12 +976,14 @@ module ActiveRecord
|
|||
# * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
|
||||
# * <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.
|
||||
# * <tt>:readonly</tt> - if set to +true+, all the associated objects are readonly through the association.
|
||||
#
|
||||
# Option examples:
|
||||
# has_and_belongs_to_many :projects
|
||||
# has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
|
||||
# has_and_belongs_to_many :nations, :class_name => "Country"
|
||||
# has_and_belongs_to_many :categories, :join_table => "prods_cats"
|
||||
# has_and_belongs_to_many :categories, :readonly => true
|
||||
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
|
||||
# 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
|
||||
def has_and_belongs_to_many(association_id, options = {}, &extension)
|
||||
|
@ -1234,7 +1242,7 @@ module ActiveRecord
|
|||
:uniq,
|
||||
:finder_sql, :counter_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
:extend
|
||||
:extend, :readonly
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
|
@ -1244,7 +1252,7 @@ module ActiveRecord
|
|||
|
||||
def create_has_one_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
|
||||
:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
|
||||
)
|
||||
|
||||
create_reflection(:has_one, association_id, options, self)
|
||||
|
@ -1253,7 +1261,7 @@ module ActiveRecord
|
|||
def create_belongs_to_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
|
||||
:counter_cache, :extend, :polymorphic
|
||||
:counter_cache, :extend, :polymorphic, :readonly
|
||||
)
|
||||
|
||||
reflection = create_reflection(:belongs_to, association_id, options, self)
|
||||
|
@ -1272,7 +1280,7 @@ module ActiveRecord
|
|||
:uniq,
|
||||
:finder_sql, :delete_sql, :insert_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
:extend
|
||||
:extend, :readonly
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
|
|
|
@ -114,7 +114,8 @@ module ActiveRecord
|
|||
:offset => @reflection.options[:offset],
|
||||
:joins => @reflection.options[:joins],
|
||||
:include => @reflection.options[:include],
|
||||
:select => @reflection.options[:select]
|
||||
:select => @reflection.options[:select],
|
||||
:readonly => @reflection.options[:readonly]
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -44,7 +44,8 @@ module ActiveRecord
|
|||
@reflection.klass.find(
|
||||
@owner[@reflection.primary_key_name],
|
||||
:conditions => conditions,
|
||||
:include => @reflection.options[:include]
|
||||
:include => @reflection.options[:include],
|
||||
:readonly => @reflection.options[:readonly]
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -53,7 +53,8 @@ module ActiveRecord
|
|||
@reflection.klass.find(:first,
|
||||
:conditions => @finder_sql,
|
||||
:order => @reflection.options[:order],
|
||||
:include => @reflection.options[:include]
|
||||
:include => @reflection.options[:include],
|
||||
:readonly => @reflection.options[:readonly]
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -469,6 +469,11 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_cant_save_readonly_association
|
||||
assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save! }
|
||||
assert companies(:first_firm).readonly_account.readonly?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
@ -544,6 +549,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length
|
||||
end
|
||||
|
||||
def test_dynamic_find_all_should_respect_readonly_access
|
||||
companies(:first_firm).readonly_clients.find(:all).each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
|
||||
companies(:first_firm).readonly_clients.find(:all).each { |c| assert c.readonly? }
|
||||
end
|
||||
|
||||
def test_triple_equality
|
||||
assert !(Array === Firm.find(:first).clients)
|
||||
assert Firm.find(:first).clients === Array
|
||||
|
@ -1581,6 +1591,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal post.author_id, author2.id
|
||||
end
|
||||
|
||||
def test_cant_save_readonly_association
|
||||
assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! }
|
||||
assert companies(:first_client).readonly_firm.readonly?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
@ -1987,6 +2002,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 2, projects(:active_record).limited_developers.find_all_by_name('Jamis', :limit => 9_000).length
|
||||
end
|
||||
|
||||
def test_dynamic_find_all_should_respect_readonly_access
|
||||
projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?}
|
||||
projects(:active_record).readonly_developers.each { |d| d.readonly? }
|
||||
end
|
||||
|
||||
def test_new_with_values_in_collection
|
||||
jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis')
|
||||
david = DeveloperForProjectWithAfterCreateHook.find_by_name('David')
|
||||
|
|
|
@ -159,9 +159,9 @@ class ReflectionTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_reflection_of_all_associations
|
||||
assert_equal 17, Firm.reflect_on_all_associations.size
|
||||
assert_equal 15, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 2, Firm.reflect_on_all_associations(:has_one).size
|
||||
assert_equal 19, Firm.reflect_on_all_associations.size
|
||||
assert_equal 16, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 3, Firm.reflect_on_all_associations(:has_one).size
|
||||
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
|
||||
end
|
||||
|
||||
|
|
|
@ -40,8 +40,10 @@ class Firm < Company
|
|||
:counter_sql => 'SELECT COUNT(*) FROM companies WHERE client_of = 1000'
|
||||
has_many :clients_using_finder_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE 1=1'
|
||||
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 :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true
|
||||
end
|
||||
|
||||
class DependentFirm < Company
|
||||
|
@ -60,6 +62,7 @@ class Client < Company
|
|||
belongs_to :firm_with_basic_id, :class_name => "Firm", :foreign_key => "firm_id"
|
||||
belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of"
|
||||
belongs_to :firm_with_condition, :class_name => "Firm", :foreign_key => "client_of", :conditions => ["1 = ?", 1]
|
||||
belongs_to :readonly_firm, :class_name => "Firm", :foreign_key => "firm_id", :readonly => true
|
||||
|
||||
# Record destruction so we can test whether firm.clients.clear has
|
||||
# is calling client.destroy, deleting from the database, or setting
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class Project < ActiveRecord::Base
|
||||
has_and_belongs_to_many :developers, :uniq => true, :order => 'developers.name desc, developers.id desc'
|
||||
has_and_belongs_to_many :readonly_developers, :class_name => "Developer", :readonly => true
|
||||
has_and_belongs_to_many :selected_developers, :class_name => "Developer", :select => "developers.*", :uniq => true
|
||||
has_and_belongs_to_many :non_unique_developers, :order => 'developers.name desc, developers.id desc', :class_name => 'Developer'
|
||||
has_and_belongs_to_many :limited_developers, :class_name => "Developer", :limit => 1
|
||||
|
|
Loading…
Reference in New Issue