Reputation: 2322
I'm working with rails 5.1.2 and ruby 2.2.6
I have the following classes:
class Idea < ApplicationRecord
validates :title, presence: true
validates :description, presence: false
has_many :discussions
end
class Discussion < ApplicationRecord
validates :title, presence: true
belongs_to :idea
def initialize(title)
@title = title
end
end
At idea
creation, I'd like to add a default discussion
in the attribute discussions
. As I'm a newbie at ruby and rails, I don't know which is the best approach to do this. Here is what I tried unsuccessfully.
In the idea controller, I tried to create the default discussion
at the idea
creation, as follows:
class IdeasController < ApplicationController
def create
discussion = Discussion.new "Main thread"
@idea = Idea.new(idea_params)
@idea.discussions << discussion
@idea.save
redirect_to ideas_path
end
private
def idea_params
params.require(:idea).permit(:title)
end
end
This drives me to an error in the controller:
undefined method `<<' for nil:NilClass
on the line
@idea.discussions << discussion
I think this is due to an uninitialized discussions
array in my idea
. However, the guide states that any class that has the declaration has_many
would inherit the method <<
, as stated in this guide. But maybe this is only true after the idea has been saved at least one time?
I tried manually initialize the array in my controller.
@idea.discussions = []
This helps removing the error, but I'm surprised this is not done automatically. Furthermore, the discussion
is not saved in database. I tried adding the declaration autosave
in Idea class, with no effect:
has_many :discussions, autosave: true
I'm a little bit lost. At the end, I'd just like to add a discussion
in an idea
between its creation and save, and persist it. What is the best approach?
Thanks for any help.
Upvotes: 0
Views: 1153
Reputation: 2575
First off, don't override initialize
in an ActiveRecord model unless you know what you're doing. Your object already has an initialize
method defined, you just can't see it because it's inherited. If you override without accepting the right set of parameters and calling super
you will introduce bugs.
ActiveRecord gives you an easy hash syntax for setting attributes at initialize already. You can do Discussion.new(title: 'Title')
right out of the box.
If you always want your ideas
to be created with a default discussion
you can move this down to the model in a before_create
callback.
class Idea < ApplicationRecord
validates :title, presence: true
validates :description, presence: false
has_many :discussions
before_create :build_default_discussion
private
def build_default_discussion
discussions.build(title: 'Main Thread')
end
end
Here you're calling the private method build_default_discussion
before every new idea
is persisted. This will happen automatically when you create a new Idea
either with Idea.new.save
or Idea.create
or any other proxy method that creates a new Idea
, anywhere in your application.
Upvotes: 3
Reputation: 901
Discussion
is already an ActiveRecord object, so you don't need the initialize
method. Simply calling Discussion.new
should work out of the box.
To build a default Discussion
when creating an Idea
just do this:
@idea.build_discussion
. This is will instantiate a new Discussion
association on your Idea
model. When you save Idea
, it will automatically save the Discussion
object as well and automatically associate it to that Idea
.
Edit: To simplify the answer, here's the code:
def create
@idea = Idea.new
@idea.build_discussion(title: 'Main Thread')
if @idea.save
redirect_to ideas_path
else
redirect_to :new
end
end
Edit 2: And because you build the Discussion
through Idea
, you need to add this to your IdeaController
strong_params
:
def idea_params
params.require(:idea).permit(
...
discussion_attributes: [
:id,
:title,
..
]
)
end
Edit 3: Sorry, I didn't pay attention to your association type. Update to this:
def create
@idea = Idea.new
@idea.discussions.new(title: 'Main Thread')
if @idea.save
redirect_to ideas_path
else
redirect_to :new
end
end
Upvotes: 4