Rémi Doolaeghe
Rémi Doolaeghe

Reputation: 2322

Ruby on rails: array at initialize

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

Answers (2)

m. simon borg
m. simon borg

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

Vlad
Vlad

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

Related Questions