eric2025
eric2025

Reputation: 43

Rails: first_or_create not saving

My goal for my application is to only show a form page with existing data or a blank form if new. I've accomplished this by using a callback that created a blank record when the user is created.

User model:

before_create :build_health_profile

However, if for whatever reason a users "health_profile" were to be destroyed or non-existant, it breaks my entire app with:

"undefined method `health_profile' for nil:NilClass"

It was mentioned to me that the "first_or_create" method could solve this by show a new form or finding the existing one, but I can't get it to save the fields. It directs to my root with my save alert like it saved, but nothing gets actually saved.

Controller:

class HealthProfilesController < ApplicationController

  def new
    @health_profile = current_user.build_health_profile
  end

  def create
    @health_profile = HealthProfile.where(user_id: current_user).first_or_create(health_profile_params)
    if @health_profile.save
      flash[:success] = "Health profile saved."
      redirect_to root_path
    else
      render 'new'
    end
  end

  private

  def health_profile_params
    params.require(:health_profile).permit(
       :age,
       :weight,
       :height,
       :gender
    )
  end
end

I've seen where I could use a block for "first_or_create", but no luck getting that to work.

View:

<%= link_to "Health Profile", new_health_profile_path %>

Models:

class User < ActiveRecord::Base
  has_one :health_profile, dependent: :destroy
end

class HealthProfile < ActiveRecord::Base
   belongs_to :user
end

Upvotes: 3

Views: 2473

Answers (3)

eric2025
eric2025

Reputation: 43

I was able to fix the problem of the record resetting itself when going back to the form by adjusting the new action. Thats everyone for the help.

def new
  @health_profile = current_user.health_profile || HealthProfile.new
end

def create
   @health_profile = HealthProfile.where(user_id: current_user).first_or_initialize(health_profile_params)
   if @health_profile.save
     flash[:success] = "Health profile saved."
       redirect_to root_path
   else
       render 'new'
   end
end

Upvotes: 0

K M Rakibul Islam
K M Rakibul Islam

Reputation: 34318

If you use first_or_create then that calls the save method as part of it on the record and tries to save that in the database. If it can't save the record, then the transaction is rolled back. So, you want to use: first_or_initialize here which is like new and does not save the record in the database immediately. It just loads the data. So, you can call save on it in the next line of your code.

So, in your code, where you have:

 @health_profile = HealthProfile.where(user_id: current_user).first_or_create(health_profile_params)

Here you are not controlling the save part, that's already being done by the first_or_create method.

So, you actually want to just load the object (NOT save yet) by using first_or_initialize:

 @health_profile = HealthProfile.where(user_id: current_user).first_or_initialize(health_profile_params)

and then, in the next line, you can call the save and based on it's return value you can take the decision:

if @health_profile.save
  # do stuff if successfully saved health_profile
else
 # otherwise
 render 'new'
end

Upvotes: 7

Jay-Ar Polidario
Jay-Ar Polidario

Reputation: 6603

Because you have @health_profile.save,

You should change first_or_create into first_or_initialize

first_or_create immediately trigger save, whereas first_or_initialize would just assign the values to a New record or to an already existing record if record exists already

Upvotes: 1

Related Questions