I'm playing with Rails 5's find_or_initialize
in my Blog app that has Posts and Categories entered into input text fields.
Post model
# == Schema Information
#
# Table name: posts
#
# id :integer not null, primary key
# title :string default(""), not null
# body :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# category_id :integer
class Post < ApplicationRecord
validates :title, :body, :category, presence: true
has_one :category
accepts_nested_attributes_for :category
end
Category model
# == Schema Information
#
# Table name: category
#
# id :integer not null, primary key
# name :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
class Category < ApplicationRecord
validates :name, presence: true
validates :name, length: { in: 3..80 }
has_many :posts
end
In the console I can get the following to work:
post = Post.new(title: "Hello Stack Overflow", body: "How are you today?")
post.category = Category.find_or_initialize_by(name: "Greetings")
post.save
post.last
=> #<Post:0x007fd34fa21a23
id: 42,
title: "Hello Stack Overflow",
body: "How are you today?",
created_at: Mon, 25 Jul 2016 12:56:39 UTC +00:00,
updated_at: Mon, 25 Jul 2016 12:56:39 UTC +00:00,
category_id: 3>
The Post form looks like this:
<%= form_for @post do |f| %>
<fieldset>
<%= f.label "Title" %>
<%= f.text_field :body %>
</fieldset>
<fieldset>
<%= f.fields_for :category do |category_fields|
<%= f.label "Category" %>
<%= category_fields.text_field :name %>
<% end %>
</fieldset>
<% end %>
My trouble is trying to get what is entered in the category fields_for input into the Post model to use the find_or_initialize
.
I tried the following:
class Post < ApplicationRecord
before_save :generate_category?
has_one :category
accepts_nested_attributes_for :category
private
def generate_category?
self.category = Category.find_or_initialize_by(name: name)
end
end
This fails and I'm getting an NameError
error:
NameError in PostController#create
undefined local variable or method `name' for #<Listing:0x007fc3d9b48a48>
My question is:
nested_attributes
in the model? before_save
Any tips on how I can code this would be much appreciated.
name
is not a method on the post model, but on the category one. Since category is not defined yet, you can't really rely on calling: self.category.name
either. You'll need to define the name value in some other form. Now if you're planning to use accepts_nested_attributes_for
you should be able to forgo that method entirely, and just have the hash of data contain the category name:
{ title: 'Post Title', category: { name: 'blog' } }
If you're not going that route, you should probably setup a traditional public method to create the category by passing arguments to this method, instead of using an active record callback. Also since find_or_initialize()
will return an object, using a predicate method doesn't make a lot of sense. generate_category?
should become generate_category