I am hoping to get some help solving a problem that I'm sure many of you could avoid in your sleep.
I have two models in a habtm relationship. A package can have many locations, and a location can have many packages. If my location model fails validation (due to an empty location address, for example), I get anActiveRecord:RecordInvalid exception. I understand that I'm getting this error because when I call package.save, rails automatically calls save! on the location association.
I'm not sure how to avoid the error or at least rescue the error. Do any of you have any good advice, both on how to solve the problem and on Rails best practices?
Here is the code:
def create @package = current_user.package.build(params[:package]) package_location if @package.save flash[:success] = "Package created!" redirect_to root_path else render 'pages/home' end end def package_location gps_processing if !session[:gps_aware] @package.locations.build(:address => session[:address]) end def gps_processing session[:address] = [params[:story][:street_address], params[:story][:city], params[:story][:state], params[:story][:country]].compact.join(', ') end class Package< ActiveRecord::Base belongs_to :user has_and_belongs_to_many :locations validates :content, :presence => true, :length => {:maximum => 140} validates :user_id, :presence => true default_scope :order => 'package.created_at DESC' end class Location < ActiveRecord::Base attr_accessible :lng, :lat, :address validates :lng, :presence => true validates :lat, :presence => true validates :address, :presence => true geocoded_by :full_street_address, :latitude => :lat, :longitude => :lng before_validation :geocode has_and_belongs_to_many :packages def full_street_address address end end ` Thanks in advance for your help!
3 Answers
The selected answer is not accurate. According to documentation here there's a simple way to catch rescue this exception:
begin complex_operation_that_calls_save!_internally rescue ActiveRecord::RecordInvalid => invalid puts invalid.record.errors end You can access the messages instance variable of errors and get the field and error message associated.
A couple ideas off the top of my head:
Use @package.save! and a rescue block:
def create @package = current_user.package.build(params[:package]) package_location @package.save! flash[:success] = "Package created!" redirect_to root_path rescue render 'pages/home' end Use validates_associated in your Package model, and only save if it's valid:
def create @package = current_user.package.build(params[:package]) package_location # You might be able to just use if(@package.save), but I'm not positive. if(@package.valid?) @package.save! flash[:success] = "Package created!" redirect_to root_path else render 'pages/home' end end And I'm sure there are a couple more ways, too, as you're working in Ruby...
Hope that helps!
1Here's the code that I used to solve the problem while giving the user good feedback on the why the save failed. Please forgive my inelegant ruby code.
One small problem remains . . . if the package and the location both fail validation, only the location error message is displayed on reload. If the user then corrects the location error but not the package error, he is shown the package error message. I'm working on how to show all of the errors on the first reload
def create @package= current_user.package.build(params[:package]) if package_location && @package.save flash[:success] = "Package created!" redirect_to root_path else render 'pages/home' end end def package_location gps_processing if !session[:gps_aware] location = @package.locations.build(:address => session[:address]) if !location.valid? @package.errors.add(:address, "You have entered an invalid address") return false else return true end end def gps_processing session[:address] = [params[:story][:street_address], params[:story][:city], params[:story][:state], params[:story][:country]].compact.join(', ') end