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.

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top