Recently the following patch
was committed to rails trunk. This patch allows for associations to be flagged as :accessible => true and then hydrated from nested hashes (i.e. nested forms)
class Post < ActiveRecord::Base belongs_to :author, :accessible => true has_many :comments, :accessible => true end
post = Post.create({ :title => 'Accessible Attributes', :author => { :name => 'David Dollar' }, :comments => [ { :body => 'First Post!' }, { :body => 'Nested Hashes are great!' } ] })
post.comments << { :body => 'Another Comment' }
I have done some work on another patch to allow this same mechanism to be used for updating existing rows using nested hashes. This completes the work as far as dynamically generating forms and being able to simply and intuitively push them back into the database.
http://github.com/ddollar/rails/commit/14a16844bbb3ba9edb14269ce2d0b61c9a43637e
This allows for the following:
# create from a basic hash
p = Post.create(:title => 'Test Post', :author => { :name => 'David' })
=> #<Post id: 8, author_id: 8, title: "Test Post", created_at: "2008-07-14 15:22:53", updated_at: "2008-07-14 15:22:53">
# update a singular reference
p.author = { :name => 'Joe' }
=> {:name=>"Joe"}
# it 'updates' the row in sql, notice the id is still the same
p.author
=> #<Author id: 8, name: "Joe", created_at: "2008-07-14 15:22:53", updated_at: "2008-07-14 15:23:03">
# create an author with posts from a hash
a = Author.create(:name => 'David', :posts => [ { :title => 'Post 1' }, { :title => 'Post 2' } ])
=> #<Author id: 14, name: "David", created_at: "2008-07-14 15:38:18", updated_at: "2008-07-14 15:38:18">
# show the posts
a.posts
=> [#<Post id: 17, author_id: 14, title: "Post 1", created_at: "2008-07-14 15:38:18", updated_at: "2008-07-14 15:38:18">, #<Post id: 18, author_id: 14, title: "Post 2", created_at: "2008-07-14 15:38:18", updated_at: "2008-07-14 15:38:18">]
# use << to update existing entries (as well as add new ones, demonstrated later)
a.posts << { :id => 17, :title => 'Post 1 Updated' }
=> [#<Post id: 17, author_id: 14, title: "Post 1 Updated", created_at: "2008-07-14 15:38:18", updated_at: "2008-07-14 15:38:53">, #<Post id: 18, author_id: 14, title: "Post 2", created_at: "2008-07-14 15:38:18", updated_at: "2008-07-14 15:38:18">]
# show posts to verify the update
a.posts
=> [#<Post id: 17, author_id: 14, title: "Post 1 Updated", created_at: "2008-07-14 15:38:18", updated_at: "2008-07-14 15:38:53">, #<Post id: 18, author_id: 14, title: "Post 2", created_at: "2008-07-14 15:38:18", updated_at: "2008-07-14 15:38:18">]
# can't update posts that don't belong to the author
a.posts << { :id => 1, :title => 'Not Allowed' }
ActiveRecord::RecordNotFound: Couldn't find Post with ID=1 AND ("posts".author_id = 14) from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/base.rb:1393:in `find_one' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/base.rb:1376:in `find_from_ids' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/base.rb:537:in `find' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/associations/association_collection.rb: 47:in `find' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/associations/association_collection.rb: 103:in `<<' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/associations/association_collection.rb: 99:in `each' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/associations/association_collection.rb: 99:in `<<' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/connection_adapters/abstract/ database_statements.rb:66:in `transaction' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/transactions.rb:79:in `transaction' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/transactions.rb:98:in `transaction' from /Users/ddollar/Code/EdgeRailsApp/vendor/rails/ activerecord/lib/active_record/associations/association_collection.rb: 98:in `<<' from (irb):12
# use = to outright replace all posts
a.posts = [ { :title => 'Replace Posts' } ]
=> [#<Post id: 19, author_id: 14, title: "Replace Posts", created_at: "2008-07-14 15:40:30", updated_at: "2008-07-14 15:40:30">]
# can even 'replace' using existing posts, the post attributes will be updated
a.posts = [ { :id => 19, :title => 'Can Replace This Way Too' } ]
=> [#<Post id: 19, author_id: 14, title: "Can Replace This Way Too", created_at: "2008-07-14 15:40:30", updated_at: "2008-07-14 15:40:49">]
# use << also for adding brand new items
a.posts << { :title => 'New Post' }
=> [#<Post id: 19, author_id: 14, title: "Replace Posts", created_at: "2008-07-14
The patch can be found at
I was asked to submit this patch to the mailing list and solicit any comments. Does anyone see any obvious holes or ways this functionality should change?
Thanks, David Dollar