0

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.

2 Answers 2

4

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
Sign up to request clarification or add additional context in comments.

6 Comments

Sounds like a good idea. Now, I'm facing another issue: undefined method `build_discussion' for #<Idea:0x00000011128bb0>.
You may also need to add accept_nested_attributes_for :idea in your Discussion model. See pluralsight.com/guides/ruby-ruby-on-rails/… for more details.
Sorry, I didn't pay attention to your association. See my updated answer. @StephanePaquet indeed, you are right!
@Vlad: I haven't seen this @idea.discussions.new in the rails guide, but it does the job! Other point, you told I don't need to define initialize in a Rails context. Could you explain or give me any pointer to understand why? Thanks.
Because behind the scenes, Rails already does that initialization for you. That's why you can call .new on any Model in your app, because it's inherited from ActiveRecord::Base. Same goes for .create, .update, .edit and so on. That's the beauty of Rails, it'll do most of the tedious tasks for you already. As an experiment, if you want, remove the < ActiveRecord::Base inheritance and you'll see that new, create and many other useful features are removed from your model.
|
3

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.

7 Comments

Very good approach if you want a new Discussion created with every Idea. This way is also very good for when you create a new Idea using a console or a task.
I wanted to avoid doing it in the controller, and your proposition fits still better my hopes. Thanks for the advice, I'll apply it. I just have one question: why in the build_default_discussion is the variable discussions without an @?
For anyone interested in active record callback such as before_create, let's have a look at this guide
@RémiDoolaeghe In this context discussions is a method that abstracts the attribute. Same as if you define attr_accessor :name in a Ruby class you can access that name attribute by calling the name method. You shouldn't attempt to access ActiveRecord attributes and associations by directly calling an instance variable, instead use the methods provided to you by ActiveRecord
@m.simonborg Is this even true from inside the class itself?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.