Rails: first_or_create not saving - ruby-on-rails

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

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

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

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

Related

Object is all nil in Rails 4+?

I have a 'landing.html.erb' under a folder 'Welcome' in my Views. In the WelcomeController, I have the following:
def landing
#quiz = Quiz.new
end
to create a new Quiz. When the User clicks the create button,
def create
#quiz = current_user.quiz.build(quiz_params)
if #quiz.save
flash[:success] = "Updated"
redirect_to #quiz
else
render 'new'
end
end
def quiz_params
params.require(:quiz).permit(:q1)
end
is the code I have for the QuizzesController. However, this gives the error:
undefined method `build' for nil:NilClass
specifically at the line
#quiz = current_user.quiz.build(quiz_params)
I ran the rails console and typed Quiz.new and it displayed every field as 'nil' and I'm not entirely sure what I need to modify and would appreciate guidance.
The Quiz model belongs_to User.
The User has_one Quiz.
Thank you very much.
Here I am assuming you are having has_one association among user and quiz.
So for build the associated object you need to use the following code.
current_user.build_quiz(quiz_params)
This will build the quiz object with the user_id field contains the id of the current user and other fields according to quiz_params.

Rails4 - Edit parameters of an Object before remotely saving it in the database

Beginner with some dev experience here.
I have an app with multiple models and I have managed to work everything out but i am stuck here.
I have a model, called CartEntries
class CartEntry < ActiveRecord::Base
acts_as_paranoid
belongs_to :cart
belongs_to :sign
With a create method in the Cart Entry controller
def create
#entry = #cart.entries.create(entry_params)
if #entry.save
flash[:notice] = translate 'flash.notice'
else
flash[:error] = translate 'flash.error'
end
support_ajax_flashes!
respond_to do |format|
format.html # renders view
format.json { render json: #entry }
end
end
And a Model Sign with static signs inputed in the database and no create method.
class Sign < ActiveRecord::Base
has_many :cart_entries
accepts_nested_attributes_for :cart_entries
And from a Sign's view I initialize a new instance of a CartEntry and succsessfuly create a new Cart Entry after clicking the link, generating a notification.
<% #entry = CartEntry.new(sign: #sign)%>
<%= link_to t('.add_to_cart'), user_cart_entries_path(:entry => #entry.attributes),method: :post, remote: true, "data-type" => :json%>
The Cart Entry has another field called Count with a default value of 1. Im looking for a way for the user to input this number in a text field and when creating the Cart Entry , pass the Count the user inputed instead of the default 1.
What ever I try passes the default value.
While
<% #entry = CartEntry.new(sign: #sign, count: 5)%>
Does the trick properly, and passes 5 as the value , but I want the user to input this number since its clearly a variable.
While I understand that
<% #entry = CartEntry.new(sign: #sign)%>
Initializes the entry object on page load and that i must move it, I'm asking you kind people, where?
UPDATE
Entry Params:
private
def entry_params
params.require(:entry).permit(:sign_id, :count)
end
Answering in reverse order from your question:
Initializing the new CartEntry object should probably be in the new action of your controller. Rails controllers often have both new and create, new being tied to rendering the form to receive input and create being the action tied to the 'Submit' button. Your new action is often just something like:
def new
#entry = CartEntry.new(sign: #sign)
end
Your view to prompt the user for data should be named new.html.erb and have the form in it.
For getting the data from the form to your create method, you are half way there I think. If you moved the example you gave:
<% #entry = CartEntry.new(sign: #sign, count: 5)%>
to the create action in the controller, it would be
#entry = CartEntry.new(sign: #sign, count: params[:count])
#entry.save
Remember 'params' is just a hash that contains the form input data.
Hope that helps!

Rails has_one build_association deletes record before save

So this has been asked previously, but with no satisfying answers.
Consider two models, User, and Subscription associated as such:
class User < ActiveRecord::Base
has_one :subscription, dependent: :destroy
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
Inside of SubscriptionsController, I have a new action that looks like this
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
Given that a subscription already exists for a user record, I'm faced with the following problem:
user.build_subscription is destructive, meaning that simply visiting the new action actually destroys the association, thereby losing the current subscription record.
Now, I could simply check for the subscription's existence and redirect like this:
def new
user = User.find(params[:user_id])
if user.subscription.present?
redirect_to root_path
else
#subscription = user.build_subscription
end
end
But that doesn't seem all that elegant.
Here's my question
Shouldn't just building a tentative record for an association not be destructive?
Doesn't that violate RESTful routing, since new is accessed with a GET request, which should not modify the record?
Or perhaps I'm doing something wrong. Should I be building the record differently? Maybe via Subscription.new(user_id: user.id)? Doesn't seem to make much sense.
Would much appreciate an explanation as to why this is implemented this way and how you'd go about dealing with this.
Thanks!
It depends on what you want to do
Thoughts
From what you've posted, it seems the RESTful structure is still valid for you. You're calling the new action on the subscriptions controller, which, by definition, means you're making a new subscription (not loading a current subscription)?
You have to remember that Rails is basically just a group of Ruby classes, with instance methods. This means that you don't need to keep entirely to the RESTful structure if it doesn't suit
I think your issue is how you're handling the request / action:
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
#subscription is building a new ActiveRecord object, but doesn't need to be that way. You presumably want to change the subscription (if they have one), or create an association if they don't
Logic
Perhaps you could include some logic in an instance method:
#app/models/user.rb
Class User < ActiveRecord::Base
def build
if subscription
subscription
else
build_subscription
end
end
end
#app/controllers/subscriptions_controller.rb
def new
user = User.find(params[:user_id])
#subscription = user.build
end
This will give you a populated ActiveRecord, either with data from the subscription, or the new ActiveRecord object.
View
In the view, you can then use a select box like this:
#app/views/subscriptions/new.html.erb
<%= form_for #subscription do |f| %>
<%= "User #{params[:user_id]}'s subscription: %>
<%= f.collection_select :subscription_id, Subscription.all,:id , :name %>
<% end %>
They are my thoughts, but I think you want to do something else with your code. If you give me some comments on this answer, we can fix it accordingly!
I also always thought, that a user.build_foobar would only be written to the db, if afterwards a user.save is called. One question: After calling user.build_subscription, is the old subscription still in the database?
What is the output user.persisted? and user.subscription.persisted?, after calling user.build_subscription?
Your method to check if a subscription is present, is IMHO absolutely ok and valid.
I came across this today and agree that deleting something from the db when you call build is a very unexpected outcome (caused us to have bad data). As you suggested, you can work around if very easily by simply doing Subscription.new(user: user). I personally don't think that is much less readable then user.build_subscription.
As of 2018 Richard Peck's solution worked for me:
#app/models/user.rb
Class User < ActiveRecord::Base
def build_a_subscription
if subscription
subscription
else
build_subscription
end
end
end
My issue was that a user controller didn't have a new method, because users came from an api or from a seed file.
So mine looked like:
#app/controllers/subscriptions_controller.rb
def update
#user = User.find(params[:id])
#user.build_a_subscription
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user), notice: 'User was successfully updated.'
else
render :edit
end
end
And I was finally able to have the correct singular version of subscriptions in my fields_for, so :subscription verses :subscriptions
#app/views
<%= f.fields_for :subscription do |sub| %>
<%= render 'subscription', f: sub %>
<% end %>
Before I could only get the fields_for to show in the view if I made subscriptions plural. And then it wouldn't save.
But now, everything works.

updating user review in Ruby on Rails

Hi i was wondering if there was a way a user can update a review they have already written, i tried using cancan but ran into a few problems so i rather find out if there is an easier way. This is code from the 'new' method in the reviews controller
def new
if logged_in?
#review = Review.new(:film_id => params[:id], :name =>
User.find(session[:user_id]).name)
session[:return_to] = nil
else
session[:return_to] = request.url
redirect_to login_path, alert: "You must be logged in to write a review"
end
end
and the 'create' method
def create
# use the class method 'new' with the parameter 'review', populated
# with values from a form
#review = Review.new(params[:review])
# attempt to save to the database, the new review instance variable
if #review.save
# use the class method 'find' with the id of the product of the
# saved review and assign this product object to the variable 'product'
film = Film.find(#review.film.id)
# redirect the reviewer to the show page of the product they reviewed,
# using the product variable, and send a notice indicating the review
# was successfully added
redirect_to film, notice: "Your review was successfully added"
else
# if the review could not be saved, return / render the new form
render action: "new"
end
end
i want the user to edit their review if they have already written a review for a product. Instead of having two reviews from the same user for the same product.
You could potentially sub something like this into the create method:
# Assumes that your user names are unique
#review = Review.find_or_create_by_film_id_and_name(params[:review][:film_id], User.find(session[:user_id]).name)
#review.update_attributes(params[:review])
This does the following
Checks whether the user has created a review for the film
If yes, assigns the existing review to the #review instance variable
If not, creates a new Review object and assigns it to #review
Updates #review with params[:review]
Alternatively, the following statements will accomplish the same without using the Rails find_or_create convenience method:
user_name = User.find(session[:user_id]).name # To avoid two DB lookups below
#review = Review.find_by_film_id_and_name(params[:review][:film_id], user_name) || Review.new(:film_id => params[:review][:film_id], :name => user_name)
#review.update_attributes(params[:review])
To update a record, you should use the update action, which is requested after the user submitted the edit form.
Make your User model have has_many/has_one :reviews. And Review model belongs_to :user. And then if you have any kind of authorization(and you should have, for ex: devise) you'll know if user of review is currently logged user. If so then render edit button, otherwise not render.
Also according to CRUD conventions, there are 2 actions you need. First its edit and other one update. You can read about it on railsguides.com

Has_one association should be a has "only" one association

I have a User and a Gallery model and the following associations:
gallery.rb
attr_accessible :name, :description
belongs_to :user
user.rb
has_one :gallery
A gallery is created through a form and it is not build on the user creation
(I do this because some user won't be allowed to create a gallery)
Here is the gallery controller with the create action:
galleries_controller.rb
def create
#gallery = Gallery.new(params[:gallery])
#gallery.user_id = current_user.id # save user_id to gallery
if #gallery.save
redirect_to #gallery, :notice => "Your gallery has been successfully created."
else
render :action => 'new'
end
end
1.) My first question is:
when I set up a 1-to-1 association like this one, A user can create as many gallery as he wants. So is it not truly a has "only" one association? (I don't think I get the concept on this. Why is there no error raised?)
2.) My second question:
In order to have only one gallery per user I had a validation on the user_id in the gallery model
validates :user_id, :uniqueness => true
Is it the correct way to avoid many gallery records associated to one user?
EDIT
Thanks to Reuben, I dit it like this:
controller
def new
if current_user.gallery == nil
#gallery = current_user.build_gallery
else
flash[:error] = "You already have a gallery"
end
end
def create
#gallery = current_user.build_gallery(params[:gallery])
if #gallery.save
redirect_to #gallery, :notice => "Your gallery has been successfully created."
else
render :action => 'new'
end
end
In the view (new.html.erb)
<% if current_user.gallery == nil %>
<%= form ... %>
<% end %>
No user_id validation needed
Re your first question: What has_one really does is that it appends the LIMIT 1 clause to the corresponding sql query, and nothing more. That's why, in your case, the user can create as many galleries as they want.
You can put a check in the new action to see if the user already has a gallery and either redirect the user saying they already have a gallery or alert the user that creating a new gallery will destroy their existing one. In the latter case you would also need to check for the existing gallery in the create action and delete it before saving the new one otherwise you will have ownerless galleries filling your database.
In your views you could check the same and only show the new link if the user does not have a gallery.
To answer your second question, you could set unique to true for your migration that creates the foreign key:
add_index :gallery, :user_id, unique: true

Resources