<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,23 @@
 *Edge*
 
+* Add :accessible option to associations for allowing (opt-in) mass assignment. #474. [David Dollar] Example :
+
+  class Post &lt; ActiveRecord::Base
+    belongs_to :author,   :accessible =&gt; true
+    has_many   :comments, :accessible =&gt; true
+  end
+
+  post = Post.create({
+    :title    =&gt; 'Accessible Attributes',
+    :author   =&gt; { :name =&gt; 'David Dollar' },
+    :comments =&gt; [
+      { :body =&gt; 'First Post!' },
+      { :body =&gt; 'Nested Hashes are great!' }
+    ]
+  })
+
+  post.comments &lt;&lt; { :body =&gt; 'Another Comment' }
+
 * Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example :
 
   # Ensure essay contains at least 100 words.</diff>
      <filename>activerecord/CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -692,6 +692,7 @@ module ActiveRecord
       # * &lt;tt&gt;:uniq&lt;/tt&gt; - If true, duplicates will be omitted from the collection. Useful in conjunction with &lt;tt&gt;:through&lt;/tt&gt;.
       # * &lt;tt&gt;:readonly&lt;/tt&gt; - If true, all the associated objects are readonly through the association.
       # * &lt;tt&gt;:validate&lt;/tt&gt; - If false, don't validate the associated objects when saving the parent object. true by default.
+      # * &lt;tt&gt;:accessible&lt;/tt&gt; - Mass assignment is allowed for this assocation (similar to &lt;tt&gt;ActiveRecord::Base#attr_accessible&lt;/tt&gt;).
       #
       # Option examples:
       #   has_many :comments, :order =&gt; &quot;posted_on&quot;
@@ -774,6 +775,7 @@ module ActiveRecord
       #   association is a polymorphic +belongs_to+.      
       # * &lt;tt&gt;:readonly&lt;/tt&gt; - If true, the associated object is readonly through the association.
       # * &lt;tt&gt;:validate&lt;/tt&gt; - If false, don't validate the associated object when saving the parent object. +false+ by default.
+      # * &lt;tt&gt;:accessible&lt;/tt&gt; - Mass assignment is allowed for this assocation (similar to &lt;tt&gt;ActiveRecord::Base#attr_accessible&lt;/tt&gt;).
       #
       # Option examples:
       #   has_one :credit_card, :dependent =&gt; :destroy  # destroys the associated credit card
@@ -863,6 +865,7 @@ module ActiveRecord
       #   to the +attr_readonly+ list in the associated classes (e.g. &lt;tt&gt;class Post; attr_readonly :comments_count; end&lt;/tt&gt;).
       # * &lt;tt&gt;:readonly&lt;/tt&gt; - If true, the associated object is readonly through the association.
       # * &lt;tt&gt;:validate&lt;/tt&gt; - If false, don't validate the associated objects when saving the parent object. +false+ by default.
+      # * &lt;tt&gt;:accessible&lt;/tt&gt; - Mass assignment is allowed for this assocation (similar to &lt;tt&gt;ActiveRecord::Base#attr_accessible&lt;/tt&gt;).
       #
       # Option examples:
       #   belongs_to :firm, :foreign_key =&gt; &quot;client_of&quot;
@@ -1034,6 +1037,7 @@ module ActiveRecord
       #   but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
       # * &lt;tt&gt;:readonly&lt;/tt&gt; - If true, all the associated objects are readonly through the association.
       # * &lt;tt&gt;:validate&lt;/tt&gt; - If false, don't validate the associated objects when saving the parent object. +true+ by default.
+      # * &lt;tt&gt;:accessible&lt;/tt&gt; - Mass assignment is allowed for this assocation (similar to &lt;tt&gt;ActiveRecord::Base#attr_accessible&lt;/tt&gt;).
       #
       # Option examples:
       #   has_and_belongs_to_many :projects
@@ -1109,6 +1113,8 @@ module ActiveRecord
               association = association_proxy_class.new(self, reflection)
             end
 
+            new_value = reflection.klass.new(new_value) if reflection.options[:accessible] &amp;&amp; new_value.is_a?(Hash)
+
             if association_proxy_class == HasOneThroughAssociation
               association.create_through_record(new_value)
               self.send(reflection.name, new_value)
@@ -1357,7 +1363,7 @@ module ActiveRecord
             :finder_sql, :counter_sql,
             :before_add, :after_add, :before_remove, :after_remove,
             :extend, :readonly,
-            :validate
+            :validate, :accessible
           )
 
           options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1367,7 +1373,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, :validate, :primary_key
+            :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate, :primary_key, :accessible
           )
 
           create_reflection(:has_one, association_id, options, self)
@@ -1383,7 +1389,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, :validate
+            :counter_cache, :extend, :polymorphic, :readonly, :validate, :accessible
           )
 
           reflection = create_reflection(:belongs_to, association_id, options, self)
@@ -1403,7 +1409,7 @@ module ActiveRecord
             :finder_sql, :delete_sql, :insert_sql,
             :before_add, :after_add, :before_remove, :after_remove,
             :extend, :readonly,
-            :validate
+            :validate, :accessible
           )
 
           options[:extend] = create_extension_modules(association_id, extension, options[:extend])</diff>
      <filename>activerecord/lib/active_record/associations.rb</filename>
    </modified>
    <modified>
      <diff>@@ -97,6 +97,8 @@ module ActiveRecord
 
         @owner.transaction do
           flatten_deeper(records).each do |record|
+            record = @reflection.klass.new(record) if @reflection.options[:accessible] &amp;&amp; record.is_a?(Hash)
+
             raise_on_type_mismatch(record)
             add_record_to_target_with_callbacks(record) do |r|
               result &amp;&amp;= insert_record(record) unless @owner.new_record?
@@ -229,6 +231,10 @@ module ActiveRecord
       # Replace this collection with +other_array+
       # This will perform a diff and delete/add only records that have changed.
       def replace(other_array)
+        other_array.map! do |val|
+          val.is_a?(Hash) ? @reflection.klass.new(val) : val
+        end if @reflection.options[:accessible]
+
         other_array.each { |val| raise_on_type_mismatch(val) }
 
         load_target</diff>
      <filename>activerecord/lib/active_record/associations/association_collection.rb</filename>
    </modified>
    <modified>
      <diff>@@ -189,6 +189,114 @@ class AssociationProxyTest &lt; ActiveRecord::TestCase
     end
   end
 
+  def test_belongs_to_mass_assignment
+    post_attributes   = { :title =&gt; 'Associations', :body =&gt; 'Are They Accessible?' }
+    author_attributes = { :name  =&gt; 'David Dollar' }
+
+    assert_no_difference 'Author.count' do
+      assert_raise(ActiveRecord::AssociationTypeMismatch) do
+        Post.create(post_attributes.merge({:author =&gt; author_attributes}))
+      end
+    end
+
+    assert_difference 'Author.count' do
+      post = Post.create(post_attributes.merge({:creatable_author =&gt; author_attributes}))
+      assert_equal post.creatable_author.name, author_attributes[:name]
+    end
+  end
+
+  def test_has_one_mass_assignment
+    post_attributes    = { :title =&gt; 'Associations', :body =&gt; 'Are They Accessible?' }
+    comment_attributes = { :body  =&gt; 'Setter Takes Hash' }
+
+    assert_no_difference 'Comment.count' do
+      assert_raise(ActiveRecord::AssociationTypeMismatch) do
+        Post.create(post_attributes.merge({:uncreatable_comment =&gt; comment_attributes}))
+      end
+    end
+
+    assert_difference 'Comment.count' do
+      post = Post.create(post_attributes.merge({:creatable_comment =&gt; comment_attributes}))
+      assert_equal post.creatable_comment.body, comment_attributes[:body]
+    end
+  end
+
+  def test_has_many_mass_assignment
+    post               = posts(:welcome)
+    post_attributes    = { :title =&gt; 'Associations', :body =&gt; 'Are They Accessible?' }
+    comment_attributes = { :body  =&gt; 'Setter Takes Hash' }
+
+    assert_no_difference 'Comment.count' do
+      assert_raise(ActiveRecord::AssociationTypeMismatch) do
+        Post.create(post_attributes.merge({:comments =&gt; [comment_attributes]}))
+      end
+      assert_raise(ActiveRecord::AssociationTypeMismatch) do
+        post.comments &lt;&lt; comment_attributes
+      end
+    end
+
+    assert_difference 'Comment.count' do
+      post = Post.create(post_attributes.merge({:creatable_comments =&gt; [comment_attributes]}))
+      assert_equal post.creatable_comments.last.body, comment_attributes[:body]
+    end
+
+    assert_difference 'Comment.count' do
+      post.creatable_comments &lt;&lt; comment_attributes
+      assert_equal post.comments.last.body, comment_attributes[:body]
+    end
+
+    post.creatable_comments = [comment_attributes, comment_attributes]
+    assert_equal post.creatable_comments.count, 2
+  end
+
+  def test_has_and_belongs_to_many_mass_assignment
+    post                = posts(:welcome)
+    post_attributes     = { :title =&gt; 'Associations', :body =&gt; 'Are They Accessible?' }
+    category_attributes = { :name  =&gt; 'Accessible Association', :type =&gt; 'Category' }
+
+    assert_no_difference 'Category.count' do
+      assert_raise(ActiveRecord::AssociationTypeMismatch) do
+        Post.create(post_attributes.merge({:categories =&gt; [category_attributes]}))
+      end
+      assert_raise(ActiveRecord::AssociationTypeMismatch) do
+        post.categories &lt;&lt; category_attributes
+      end
+    end
+
+    assert_difference 'Category.count' do
+      post = Post.create(post_attributes.merge({:creatable_categories =&gt; [category_attributes]}))
+      assert_equal post.creatable_categories.last.name, category_attributes[:name]
+    end
+
+    assert_difference 'Category.count' do
+      post.creatable_categories &lt;&lt; category_attributes
+      assert_equal post.creatable_categories.last.name, category_attributes[:name]
+    end
+
+    post.creatable_categories = [category_attributes, category_attributes]
+    assert_equal post.creatable_categories.count, 2
+  end
+
+  def test_association_proxy_setter_can_take_hash
+    special_comment_attributes = { :body =&gt; 'Setter Takes Hash' }
+
+    post = posts(:welcome)
+    post.creatable_comment = { :body =&gt; 'Setter Takes Hash' }
+
+    assert_equal post.creatable_comment.body, special_comment_attributes[:body]
+  end
+
+  def test_association_collection_can_take_hash
+    post_attributes = { :title =&gt; 'Setter Takes', :body =&gt; 'Hash' }
+    david = authors(:david)
+
+    post = (david.posts &lt;&lt; post_attributes).last
+    assert_equal post.title, post_attributes[:title]
+
+    david.posts = [post_attributes, post_attributes]
+    assert_equal david.posts.count, 2
+  end
+
   def setup_dangling_association
     josh = Author.create(:name =&gt; &quot;Josh&quot;)
     p = Post.create(:title =&gt; &quot;New on Edge&quot;, :body =&gt; &quot;More cool stuff!&quot;, :author =&gt; josh)</diff>
      <filename>activerecord/test/cases/associations_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 class Author &lt; ActiveRecord::Base
-  has_many :posts
+  has_many :posts, :accessible =&gt; true
   has_many :posts_with_comments, :include =&gt; :comments, :class_name =&gt; &quot;Post&quot;
   has_many :posts_with_comments_sorted_by_comment_id, :include =&gt; :comments, :class_name =&gt; &quot;Post&quot;, :order =&gt; 'comments.id'
   has_many :posts_with_categories, :include =&gt; :categories, :class_name =&gt; &quot;Post&quot;</diff>
      <filename>activerecord/test/models/author.rb</filename>
    </modified>
    <modified>
      <diff>@@ -33,6 +33,12 @@ class Post &lt; ActiveRecord::Base
   has_and_belongs_to_many :categories
   has_and_belongs_to_many :special_categories, :join_table =&gt; &quot;categories_posts&quot;, :association_foreign_key =&gt; 'category_id'
 
+  belongs_to              :creatable_author,     :class_name =&gt; 'Author',   :accessible =&gt; true
+  has_one                 :uncreatable_comment,  :class_name =&gt; 'Comment',  :accessible =&gt; false, :order =&gt; 'id desc'
+  has_one                 :creatable_comment,    :class_name =&gt; 'Comment',  :accessible =&gt; true,  :order =&gt; 'id desc'
+  has_many                :creatable_comments,   :class_name =&gt; 'Comment',  :accessible =&gt; true,  :dependent =&gt; :destroy
+  has_and_belongs_to_many :creatable_categories, :class_name =&gt; 'Category', :accessible =&gt; true
+
   has_many :taggings, :as =&gt; :taggable
   has_many :tags, :through =&gt; :taggings do
     def add_joins_and_select</diff>
      <filename>activerecord/test/models/post.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>c6f397c5cecf183680c191dd2128c0a96c5b9399</id>
    </parent>
  </parents>
  <author>
    <name>David Dollar</name>
    <email>ddollar@gmail.com</email>
  </author>
  <url>http://github.com/rails/rails/commit/e0750d6a5c7f621e4ca12205137c0b135cab444a</url>
  <id>e0750d6a5c7f621e4ca12205137c0b135cab444a</id>
  <committed-date>2008-07-13T18:53:21-07:00</committed-date>
  <authored-date>2008-07-13T18:13:50-07:00</authored-date>
  <message>Add :accessible option to Associations for allowing mass assignments using hash. [#474 state:resolved]

Allows nested Hashes (i.e. from nested forms) to hydrate the appropriate
ActiveRecord models.

class Post &lt; ActiveRecord::Base
  belongs_to :author,   :accessible =&gt; true
  has_many   :comments, :accessible =&gt; true
end

post = Post.create({
  :title    =&gt; 'Accessible Attributes',
  :author   =&gt; { :name =&gt; 'David Dollar' },
  :comments =&gt; [
    { :body =&gt; 'First Post!' },
    { :body =&gt; 'Nested Hashes are great!' }
  ]
})

post.comments &lt;&lt; { :body =&gt; 'Another Comment' }

Signed-off-by: Pratik Naik &lt;pratiknaik@gmail.com&gt;</message>
  <tree>442e795385f4114b8270e9d5d325b307bbb1bc13</tree>
  <committer>
    <name>Pratik Naik</name>
    <email>pratiknaik@gmail.com</email>
  </committer>
</commit>
