Reputation: 43
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
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
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
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