How to create a parent model through a child controller in rails 3? (belongs_to association)
-
27-10-2019 - |
Question
I have two resources: Topics and Posts. I am trying to figure out how I can create a Topic through the Post controller. The models look like this:
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
validates :name, :presence => true,
:length => { :maximum => 32 }
attr_accessible :name
end
class Post < ActiveRecord::Base
belongs_to :topic, :touch => true
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :topic
attr_accessible :name, :title, :content, :topic
end
posts/_form.html.erb:
<%= simple_form_for @post do |f| %>
<h1>Create a Post</h1>
<%= f.input :name, :label => false, :placeholder => "Name" %>
<%= f.input :title, :label => false, :placeholder => "Title" %>
<%= f.input :content, :label => false, :placeholder => "Content" %>
<%= f.input :topic, :label => false, :placeholder => "Topic" %>
<%= f.button :submit, "Post" %>
<% end %>
posts_controller.rb#create:
def create
@post = Post.new(params[:topic])
respond_to do |format|
if @post.save
format.html { redirect_to(@post, :notice => 'Post was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
With posts/_form.html.erb I can create posts, but an associated topic is not created along with it. Can anyone tell me why I get this behavior and possibly how to correct it? I'm using Ruby 1.9.2, Rails 3.0.7 and the simple_form gem.
Solution
Depending on what you want to do, i can see two options.
Option 1: Use a text-box to create or find an existing topic (as you had). In your controller you would write something like:
def create
topic_name = params[:post].delete(:topic)
@topic = Topic.find_or_create_by_name(topic_name)
@post = Post.new(params[:post])
@post.topic = @topic
if @post.save
format.html { redirect_to(@post, :notice => 'Post was successfully created.') }
else
format.html { render :action => "new" }
end
end
That is the quick and dirty way. It will, for each topic
you type, try to find that topic, by name, or create it and assign it. But, this is error-prone. If your sets of topics is limited, there is a much easier way.
Option 2: use a select-box, a list of available topics. In your view write:
<%= simple_form_for @post do |f| %>
<h1>Create a Post</h1>
<%= f.input :name, :label => false, :placeholder => "Name" %>
<%= f.input :title, :label => false, :placeholder => "Title" %>
<%= f.input :content, :label => false, :placeholder => "Content" %>
<%= f.association :topic %>
<%= f.button :submit, "Post" %>
<% end %>
That will render a select-box with the possible topics. And in your controller you just have to write:
def create
@post = Post.new(params[:post])
if @post.save
format.html { redirect_to(@post, :notice => 'Post was successfully created.') }
else
format.html { render :action => "new" }
end
end
While this second option is really easy, it is less easy to add topics on the fly. You could do something in between, using an autocomplete field, that will either allow looking up values if they exist, or add new values if they don exist.
Hope this helps.
OTHER TIPS
Railscasts has several episodes about this problem. episode 16(subscription needed)
Source code: https://github.com/railscasts/016-virtual-attributes-revised
And this episode http://railscasts.com/episodes/57-create-model-through-text-field
views/products/_form.rhtml
<p>
<label for="product_category_id">Category:</label><br />
<%= f.collection_select :category_id, Category.find(:all), :id, :name, :prompt => "Select a Category" %>
or create one:
<%= f.text_field :new_category_name %>
</p>
models/product.rb
belongs_to :category
attr_accessor :new_category_name
before_save :create_category_from_name
def create_category_from_name
create_category(:name => new_category_name) unless new_category_name.blank?
end
I think Ryan's solution on category is more elegant than @nathanvda 's option 1. As it deal the data in Model instead of Controller. If you need to do same work in different controllers/actions, you will see the benefits.
Are you getting mass assignment errors in the server log? You may need to add :topic_attributes to your attr_accessible list.