I have two models, Character and Initiative, and their relationship is Character has_one Initiative and Initiative belogns_to Character. I'm working on validation for Initiative, and I have it working, but the issue is all of my validation errors appear when creating a new Initiative record for a Character, before entering any information. Any ideas? Here's my code from Initiatives controller:
def new
#character = Character.find(params[:character_id])
#initiative = #character.create_initiative(params[:initiative])
end
def edit
#character = Character.find(params[:character_id])
#initiative = #character.initiative
end
def create
#character = Character.find(params[:character_id])
#initiative = #character.create_initiative(params[:initiative])
if #initiative.save
redirect_to character_path(#character), :notice => "initiative successfully created!"
else
render :action => "new"
end
end
def update
#character = Character.find(params[:character_id])
#initiative = #character.initiative
if #initiative.update_attributes(params[:initiative])
redirect_to character_path(#character), :notice => 'Initiative information was successfully updated.'
else
render :action => "edit"
end
end
And here's the validation itself from my model:
validates_presence_of :dex, :misc, :speed
validates_numericality_of :dex, :misc, :speed
I'm pretty sure the problem lies in the create or new methods, but I'm not sure why it's triggering the validation before a user enters any information. Any help? Maybe not a huge concern, since the code IS working, but I'd rather not display an error message before actually getting an error. Thanks!
shouldn't you be using build_initiative instead of create_initiative in your new action ? no need to save an object when sending to the user a form that intends to create it. Moreover, if your character has_one initiative, he can only have one so i doubt AR appreciates that you try to create another.
see http://guides.rubyonrails.org/association_basics.html#has_one-association-reference
Related
I have a form used to create clients, and in one of the fields I have to choose the language of the client. In the model I have a validation to check the field is not null, but the validation error is getting displayed even when a language is provided.
View:
<%= f.input :locale, as: :select, collection: locale_for_select, prompt: false %>
Model:
validates :locale, presence: true
Controller:
def new
end
def edit
end
def create
if #client.save
redirect_to #client, notice: t_notice('controllers.successfully_created', Client)
else
render action: "new"
end
end
def update
if #client.update_attributes(params[:client])
redirect_to #client, notice: t_notice('controllers.successfully_updated', Client)
else
render action: "edit"
end
end
I have used the browser's developer tools to check the value is actually being send, although the validation at the model fails.
Any idea about what's going on?
EDIT:
I have noticed this error only happens when creating a new client, not while editing an existing one. However, when I edit a client this new value is not being persisted to database
EDIT 2:
Using rails 3.2.22
Using ruby 2.1.6
EDIT 3:
This is strange because in the same form I have some other select inputs that are working properly, and which are treated in a similar way.
def create
#client = Client.new(params[:client])
if #client.save
redirect_to #client, notice: t_notice('controllers.successfully_created', Client)
else
render action: "new"
end
end
def update
#client = Client.find(params[:id])
if #client.update_attributes(params[:client])
redirect_to #client, notice: t_notice('controllers.successfully_updated', Client)
else
render action: "edit"
end
end
The problem was that I had added a localized field using Globalize, and I did not take this into account:
Because globalize uses the :locale key to specify the locale during
mass-assignment, you should avoid having a locale attribute on the
parent model.
What I did to solve this issue is renaming my :locale attribute to :client-locale and execute the corresponding migration to rename the column.
I want to save two models in one controller action, or save neither, and return with the validation errors.
Is there a better way than this?
def update
#job = Job.find(params[:id])
#location = #job.location
#job.assign_attributes(job_params)
#location.assign_attributes(location_params)
#job.save unless #job.valid? # gets validation errors
#location.save unless #location.valid? # gets validation errors
if #job.valid? && #location.valid?
#job.save
#location.save
flash[:success] = "Changes saved."
redirect_to edit_job_path(#job)
else
render 'edit'
end
end
New version:
def update
#job = Job.find(params[:id])
#location = #job.location
begin
Job.transaction do
#job.assign_attributes(job_params)
#job.save!(job_params)
#location.assign_attributes(location_params)
#location.save!(location_params)
end
flash[:success] = "Changes saved."
redirect_to edit_job_path(#job)
rescue ActiveRecord::RecordInvalid => invalid
render 'edit'
end
end
Have a look at Active Record Nested Attributes.
Using Nested attributes, you can save associated record attributes through parent.If parent record fails, associated records won't be saved.!
the first thing you'd want to do is delete these two lines
#job.save unless #job.valid? # gets validation errors
#location.save unless #location.valid? # gets validation errors
and only keep the #save in the if statement. because if one of them is valid, but the other isn't, you'll still save the valid one to the db.
To answer your second question, is there a better way to do this? At first blush, it looks like a job for #accepts_nested_attributes_for. However, accepts_nested_attributes_for is somewhat notorious for being difficult to get working (really it just takes a fare amount of tinkering) and what you're currently doing should get you where you're trying to go, so it's up to you.
You can use validates_associated rails helper:
class Model < ActiveRecord::Base
has_one :location
validates_associated :location
end
Then:
if #job.save
#blah
else
#blah
end
Is enough without having to mess with ActiveRecord#Nested_attributes. It's fastest, but less cleaner. Your choice.
Reference:
http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_associated
Rails 3.2. I am using the following code to associate user_id to the record:
# review.rb
class Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => true, :counter_cache => true
end
# reviews_controller.rb
def create
#review = #reviewable.reviews.new(params[:review])
#review.user_id = current_user.id
if #review.save
flash[:success] = 'Thanks for adding your review.'
redirect_to #reviewable
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #reviewable
end
end
I want to find a way to use this, but it keeps saying that the current_user is not defined, but I could find the current_user object:
def create
#review = #reviewable.current_user.reviews.create(params[:review]
if #review.save
flash[:success] = 'Thanks for adding your review.'
redirect_to #reviewable
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #reviewable
end
end
If you can post your code to what the #reviewable object is, it might help to give a more specific answer. But if you want a one liner, you can do something like this:
#review = #reviewable.reviews.build(params[:review].merge({:user_id => current_user.id})
But personally, i think your original looks better as it's easier to read.
As an additional note, your second example also calls create and save. You don't need to call both, as create saves the object when accepting a hash of parameters. Save is nice to use if you want to initialize an object, modify it in some way, then save it later.
I think that the reason this does not work is because current_user is a method that is not defined on a reviewable.
The reviewable may belong to a user, in which case #reviewable.user.reviews.create... may be valid.
I have something like issue tracking system where there are issues and they have some comments.
Now on one page I want to give user an option to edit some stuff of "issue" as well as add a comment. Editing of and issue is a standard stuff like in /edit but also I want to create a comment and validate if it's not blank.
I've figured out that I can build a comment and make a form for it, but how should I check simultaneously that both issue attributes and comment attributes are valid? Because each update should be followed by a new comment, but I don't want to create a new comment if the issue attributes are no valid.
I would approach this by first adding fails_validation? methods to both your Issues and Comments models to check for problems.
Second, you will have to manually load the #issue form data from params[] and validate it BEFORE you save it (can't use update_attributes(params[:issue]).) Create a new Comment and load it via params[]. Then you can test the validation on both models and go back to the edit action if either fails.
If both pass you can save #issue and then #comment as normal.
def update
#issue = Issue.find(params[:id])
# manually transfer form data to the issue model
#issue.title = params[:issue][:title]
#issue.body = params[:issue][:body]
#...
#comment = #issue.comments.new(params[:comment])
# validate both #issue and #comment
if #issue.fails_validation? || #comment.fails_validation?
flash[:error] = "Your edits or your comment did not pass validation."
render :action => "edit",
end
# validation passed, save #issue then #comment
respond_to do |format|
if #issue.save
#comment.save
format.html { redirect_to #issue, notice: 'Issue successfully updated. Comment created' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: #issue.errors, status: :unprocessable_entity }
end
end
end
Not the most elegant solution, but it should work.
You can validate the comment model and the issue model in their respective classes.
It is not clear to me whether you are using 'accepts_nested_attributes_for' in Issue for comments. If you are, then the standard IssueController#update will not save the record if issue is invalid and consequently, it will not create the comment records as well.
Here is the standard IssueController#update:
class IssueController < ApplicationController
def update
#issue = Issue.find(params[:id])
if #issue.update_attributes(params[:issue])
redirect_to issues_path, notice: 'issue updated'
else
render action: 'edit'
end
end
I'm having a very difficult rails problem and i thought to ask for some help. The situation is like this :
I'm using restful authentication for my User model. Now, user has a field named 'gold' that is a numeric value. There is another model named Book that has been created using scaffolding.
What i want to do is simple, yet i cannot see a way of doing it. I want to add some validation where if the user's gold is not, let's say 100, they cannot create a new book entry(from the scaffolding standard view).
Now the problem is that i need current_user information in order to validate this from my model. I need that in order to get the user id and therefore get their gold amount as well. I cannot find a good way (if any) to do that.
Another thought was doing that from a controller. However, the standard "if #book.save" block does not really allow me to insert my own error messages (inside scaffold create) :
if not User.has_the_needed_gold(current_user, 100)
flash[:goldError] = 'You do not have the needed gold to create this book.'
#end
respond_to do |format|
if #book.save
flash[:notice] = 'Book was successfully created.'
format.html { redirect_to(#book) }
format.xml { render :xml => #book, :status => :created, :location => #book }
else
format.html { render :action => "new" }
format.xml { render :xml => #book.errors, :status => :unprocessable_entity }
end
end
Now, i cannot output that message and abort the save of the new book as well. I've tried adding my own error to base, but it was cleared out(after save i guess). I'm quite confused with the situation and i've been searching around for a couple of hours with no results.
If anybody can help with that, please do so, you would spare me lots of trouble :)
Thanx for reading !
You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.
Look the user up when validating. It's likely the user lookup will have been cached by ActiveRecord, so it's not a performance hit to do so. Try something like this:
class Book
validate :check_gold
def check_gold
user = User.find(self.user_id)
self.errors.add(:user_id => "#{user.name} doesn't have enough gold!") if user.nil? or (user.gold < 100)
end
end