Dealing with a unique error - ruby-on-rails

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

Related

What is the proper way to make an increment function work?

I'm creating a website that let people read short stories in several chapters.
For this, I nested a chapter scaffold inside a novel scaffold, and linked (novel has_many :chapters, chapters belongs_to :novel) them both together.
However I'm trying to get the chapter's number inside the URL (instead of the id, which never decreases). Setting the chapter as is is not a problem, but I'd like to automate the chapter number without my users needing to add it themselves.
To do so, I figured that all I needed was to get how many chapter the current novel had by checking self.class.where(:novel_id => #novel).count. At this point, I have no issues, however it gets complicated when I try to increment this number, I get the error: undefined method 'anoter_one' for 0:Fixnum
Here is my "another_one" function inside my model (I tried some things)
def another_one
#number = self.class.where(:novel => #novel).count.to_i
#number.increment
end
Here is the controller
def create
#novel = Novel.find(params[:novel_id])
#chapter = Chapter.new(chapter_params)
#chapter.chapter_number.another_one
#chapter.novel = #novel
if #chapter.save
redirect_to novel_chapter_path(#novel, #chapter), notice: 'Chapter was successfully created.'
else
render :new
end
end
What am I doing wrong ?
Thank you in advance
Your calling anoter_one - which is a misspelling of another on the value of #chapter.chapter_number - not the model.
One way to solve this is by using an association callback:
class Novel
has_many :chapters, before_add: :set_chapter_number
def set_chapter_number(chapter)
if chapter.chapter_number.blank?
chapter.chapter_number = self.chapters.size + 1
end
end
end
In order for the callback to be called properly you want to build the associated items off the parent:
def new
#novel = Novel.find(params[:novel_id])
#chapter = #novel.chapters.new
end
def create
#novel = Novel.find(params[:novel_id])
#chapter = #novel.chapters.new(chapter_params)
if #chapter.save
redirect_to [#novel, #chapter], notice: 'Chapter was successfully created.'
else
render :new
end
end

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.

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

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

Can I access information from one associated AR object in another when both are unsaved?

Say I open a Rails (2.3.8) script console and try this:
a = Account.new(:first_name) = 'foo'
i = a.invoices.build
p i.account.first_name
Account.rb is a model object and contains:
has_many :invoices
and Invoice.rb is a model as well containing:
belongs_to :account, :validate => true
In console line 3 above, i.account is nil. I realize that i.account would not be nil if account had been saved, but I do not wish to save an account unless I can create a valid invoice for the account. And, just for kicks, the invoice validation depends on some properties of the unsaved account.
Any ideas how to make this work?
Best,
Will
I typically do this with transactions. With rails transactions you can perform db interactions and roll them back at any time if something fails to validate. For example:
in your model:
def save_and_create_invoice
Account.transaction do
#first let's save the account, this will give us an account_id to work with
return false unless self.save
invoice = self.invoices.build
#setup your invoice here and then save it
if invoice.save
#nothing wrong? return true so we know it was ok
return true
else
#add the errors so we know what happened
invoice.errors.full_messages.each{|err| errors.add_to_base(err)}
#rollback the db transaction so the account isn't saved
raise ActiveRecord::Rollback
#return false so we know it failed
return false
end
end
end
And in your controller you would call it like so:
def create
#account = Account.new(params[:account])
respond_to do |format|
if #account.save_and_create_invoice
format.html
else
format.html {render :action => "new"}
end
end
end
Note that I didn't run this code to test it, just whipped it out real quick to show an example.

Resources