views:

48

answers:

3

I have a form that lets me create new blog posts and I'd like to be able to create new categories from the same form.

I have a habtm relationship between posts and categories, which is why I'm having trouble with this.

I have the following 2 models:

class Post < ActiveRecord::Base
  has_and_belongs_to_many :categories
  attr_accessible :title, :body, :category_ids

  accepts_nested_attributes_for :categories # should this be singular? 
end

class Category < ActiveRecord::Base
  has_and_belongs_to_many :posts
  attr_accessible :name
end

My form lets me pick from a bunch of existing categories or create a brand new one. My form is as follows.

# using simple_form gem
.inputs
  = f.input :title
  = f.input :body

  # the line below lets me choose from existing categories
  = f.association :categories, :label => 'Filed Under'

  # I was hoping that the code below would let me create new categories
  = f.fields_for :category do |builder|
    = builder.label :content, "Name"
    = builder.text_field :content

When I submit my form, it gets processed but the new category is not created. My command prompt output tells me:

WARNING: Can't mass-assign protected attributes: category

But, if I add attr_accessible :category, I get a big fat crash with error message "unknown attribute: category".

If I change the fields_for target to :categories (instead of category) then my form doesn't even display.

I've spent a while trying to figure this out, and watched the recent railscasts on nested_models and simple_form but couldn't get my problem fixed.

Would this be easier if I was using a has_many :through relationship (with a join model) instead of a habtm?

A: 

Maybe you should try it with (not testet):

attr_accessible :category_attributes

And HBTM relations arent really recommened... But I use them on my own :P

Lichtamberg
I've tried this, and also using categories_attributes (both in the model and in the form) and neither work. Would you recommend using has_many :through instead? I think I will give that a go now
stephenmurdoch
Baybe you are faster in solving this problem with non-hbtm :P
Lichtamberg
A: 

In your form you probably should render the fields_for once per category (you can have multiple categories per post, hence the habtm relation). Try something like:

- for category in @post.categories
  = fields_for "post[categories_attributes][#{category.new_record? ? category.object_id : category.id}]", category do |builder|
    = builder.hidden_field :id unless category.new_record?
    = builder.label :content, "Name"
    = builder.text_field :content
Yannis
nice idea, but no-worky - i got the problem fixed, will post an answer in a few minutes once I've stopped wooting
stephenmurdoch
Ah, ok. I'm looking forward to see your solution for this…
Yannis
A: 

Thanks to everyone who answered. After much trial and error, I managed to come up with a fix.

First of all, I switched from a HABTM to a has_many :through relationship, calling my join model categorization.rb (instead of categorizations_posts.rb) - NB: the fix detailed below will likely work with a HABTM too:

Step 1: I changed my models to look like this:

# post.rb
class Post < ActiveRecord::Base
  has_many :categorizations
  has_many :categories, :through => :categorizations
  attr_accessible :title, :body, :category_ids
  accepts_nested_attributes_for :categories
end

#category.rb
class Category < ActiveRecord::Base
  has_many :categorizations
  has_many :posts, :through => :categorizations
  attr_accessible :name, :post_ids
end

#categorization.rb
class Categorization < ActiveRecord::Base
  belongs_to :post
  belongs_to :category
end

From the post model above: obviously, the accessor named :category_ids must be present if you want to enable selecting multiple existing categories, but you do not need an accessor method for creating new categories... I didn't know that.

Step 2: I changed my view to look like this:

-# just showing the relevent parts
= fields_for :category do |builder|
  = builder.label :name, "Name"
  = builder.text_field :name

From the view code above, it's important to note the use of fields_for :category as opposed to the somewhat unintuitive fields_for :categories_attributes

Step 3 Finally, I added some code to my controller:

# POST /posts
# POST /posts.xml
def create
  @post = Post.new(params[:post])
  @category = @post.categories.build(params[:category]) unless params[:category][:name].blank?
  # stuff removed
end


def update
  @post = Post.find(params[:id])
  @category = @post.categories.build(params[:category]) unless params[:category][:name].blank?
  # stuff removed
end

Now, when I create a new post, I can simultaneously choose multiple existing categories from the select menu and create a brand new category at the same time - it's not a case of one-or-the-other

There is one tiny bug which only occurs when editing and updating existing posts; in this case it won't let me simultaneously create a new category and select multiple existing categories - if I try to do both at the same time, then only the existing categories are associated with the post, and the brand-new one is rejected (with no error message). But I can get round this by editing the post twice, once to create the new category (which automagically associates it with the post) and then a second time to select some additional existing categories from the menu - like I said this is not a big deal because it all works really well otherwise and my users can adapt to these limits

Anyway, I hope this helps someone.

Amen.

stephenmurdoch