Object is all nil in Rails 4+? - ruby-on-rails

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.

Related

When #User is activated create an Object that belongs to that User

I have a User model that has many galleries and categories(both separate models that belong_to :user). I would like to create a default category named "Everything" and a default gallery entitled "Everything".
I am adapting Mike Hartl's tutorial for my app, specifically the User creation and activation part. So, after a user is created using this code:
User.rb
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = 'Please check your email to activate your account.'
redirect_to root_url
else
render 'new'
end
end
An activation email is sent to the User and if they click on the link it will activate their account. At the point of activation, so the activated column switches from false to true, I would like the "Everything" gallery and category to be created. In my mind that is the most logical place for this to happen because it is likely to only be triggered once. That way I dont' have to worry about an "Everything" category and gallery trying to be created everytime someone logs on to their account.
The way I am trying to create these is by inserting the following code immediately after the account is activated:
user.categories = user.categories.build(name: 'Everything')
So it looks like, this:
account_activations_controller.rb
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.activate
user.categories = user.categories.build(name: 'Everything')
log_in user
flash[:success] = 'Account activated!'
redirect_to user
else
flash[:danger] = 'Invalid activation link'
redirect_to root_url
end
end
end
My problem is that while the User is created, the "Everything" category is not and I get the following error, which I can't reconcile:
NoMethodError at /account_activations/0hbg0T33tjQlleiwKqvUDg/edit
undefined method `each' for #<Category:0x00000004f95b60>
I don't know where 'each' would be called in this process.
Though I mentioned creating a gallery and category and user activation, the above code only address category. It seems to me that if I can get a category to work I should be able to get the gallery to work using a similar solution. This is what my create action looks like for category:
def new
#category = current_user.categories.build
end
def create
#category = current_user.categories.create(category_params)
if #category.save!
respond_to do |format|
format.html
format.js
end
end
end
I have a couple questions related to my issue:
The first is the the most obvious, why am I getting the nomethod error, there doesn't appear to be an 'each' method associated with categories?
Am I approaching this issue correctly? Should I trigger the creation of my default category and gallery from somewhere else in the code? I've read a bit about a .change? that I tried to use on the activated column on User, but I couln't figure out how to use it.
Any help you can offer is greatly appreciated.
You are trying to assign the result of user.categories.build(name: 'Everything') to user.categories. user.categories.build(name: 'Everything') creates the new record on it's own. You do not need to further assign it to user.categories. Trying to do so is causing Rails to iterate on the result of user.categories.build(name: 'Everything'), which is a single Category, not a collection.
So the solution to your question 1 is to replace:
user.categories = user.categories.build(name: 'Everything')
with:
user.categories.build(name: 'Everything')
The answer to question 2 depends on how many different places and/or ways a user can be activated. If the controller action for activating is the only place, your solution is probably fine. But if you ever want to activate a user another way (from the console, for example), you would probably be better off using a an ActiveRecord callback on User, such as :after_save and checking for user.activated_changed? && user.activated?, then generating the generic category and gallery there.
In either case, you may want to consider checking that the generic category and gallery don't already exist for the user (in case they were once activated and then deactivated), before creating them.
I figured out what I was doing wrong. I was trying to create categories/an array instead of a category/a single object. I turned this:
user.categories = user.categories.build(name: 'Everything')
to this:
user.categories << user.categories.create(name: 'Everything')

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
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

Dealing with a unique error

So I have got a database called Awards.
Users are able to 'award' a recipe but they can only do this once. The award database consists of recipe_id and user_id. I have made both of these columns unique so it wont allow you to award the recipe more than once. This works fine and if you attempt to award the recipe a second time I get this error:
columns user_id, recipe_id are not unique
Is there some code I can add into th create action to check for this error and then render a flash error message such as "already awarded recipe" instead of showing the error console?
this is my create method:
def create
#award = current_user.awards.build(award_params)
if #award.save
flash[:success] = "Award Given!"
redirect_to recipe_path(params[:recipe_id])
else
render 'static_pages/home'
end
end
Thanks,
Mike
There is a whole section of rails called validations that you're hitting on. The documentation is here: link. To get you basically set up, you could:
# award.rb
class Award < ActiveRecord::Base
validates_uniqueness_of :user_id, :recipe_id
end
# awards_controller.rb
def create
#award = current_user.awards.build(award_params)
if #award.save
flash[:success] = 'Award Given!'
redirect_to recipe_path(params[:recipe_id])
else
flash[:error] = 'There was an error awarding this award!'
render 'static_pages/home'
end
end

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

Getting "undefined method for Nil Class" error when calling the reciprocal model method

I currently have two models School and Course where School has_many courses, and Course belongs_to school. Additionally, School and Course are nested resources, where School is the parent resource, and Course the child.
I have created several test records in the Rails Console so that a query such as when the child calls upon the parent Course.first.school successfully executes and returns all the relevant information of the school Course.first is associated with.
However, when put into a controller function, I would instead get an error "undefined method `school' for nil:NilClass" for the following line:
redirect_to school_course_path(#course.school, #course)
.. as if the .school part wasn't recognized (where as it was in the console). Why is this the case, and how do I get past this error? Thanks!
Edit - as suggested, it could be that my #course instance variable isn't passed from method to method in the controller. I have attempted at passing them through via a private method, but its still giving me the same error. Here is my code (background: the model Question belongs_to Course, with Course having many questions. Course isn't part of the nested routes)
class QuestionsController < ApplicationController
def new
#course = Course.find(params[:course]) #confirmed working
self.current_course = #course #I attempt to set current_course, a private method
#question = Question.new
end
def create
#question = Question.new(params[:question]) #also works, in rails console all the questions confirms to have rails id
if #question.save
redirect_to school_course_path(current_course.school, current_course) #source of my frustrations - continues to returns same error message
else
render 'new'
end
end
private
def current_course=(course)
#current_school = course
end
def current_course
#current_course
end
end
Should work if your relationships are set up the way I think they are:
def create
#question = Question.new(params[:question])
#course = #question.course
if #question.save
redirect_to school_course_path(#course.school, #course)
else
render 'new'
end
end
Make sure you have something like this in your create action:
#course = Course.new(params[:course])
your code is okay, it seems there is problem in your redirect.. redirect it to root_path and check whether it is working??

Resources