Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save something to the database after failed ActiveRecord validations?

Basically what I want to do is to log an action on MyModel in the table of MyModelLog. Here's some pseudo code:

class MyModel < ActiveRecord::Base
  validate :something

  def something
     # test
     errors.add(:data, "bug!!")
  end
end

I also have a model looking like this:

class MyModelLog < ActiveRecord::Base

  def self.log_something
    self.create(:log => "something happened")
  end

end

In order to log I tried to :

  • Add MyModelLog.log_something in the something method of MyModel

  • Call MyModelLog.log_something on the after_validation callback of MyModel

In both cases the creation is rolled back when the validation fails because it's in the validation transaction. Of course I also want to log when validations fail. I don't really want to log in a file or somewhere else than the database because I need the relationships of log entries with other models and ability to do requests.

What are my options?

like image 633
marcgg Avatar asked Oct 07 '10 11:10

marcgg


People also ask

Can we save an object in the DB if its validations do not pass?

Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the INSERT or UPDATE operation. This helps to avoid storing an invalid object in the database.

How does Active Record transaction work?

Transactions in ActiveRecordEvery database operation that happens inside that block will be sent to the database as a transaction. If any kind of unhandled error happens inside the block, the transaction will be aborted, and no changes will be made to the DB.

What does Active Record base mean?

ActiveRecord::Base indicates that the ActiveRecord class or module has a static inner class called Base that you're extending.

Can you use Active Record without Rails?

One of the primary aspects of ActiveRecord is that there is very little to no configuration needed. It follow convention over configuration. ActiveRecord is commonly used with the Ruby-on-Rails framework but you can use it with Sinatra or without any web framework if desired.


1 Answers

Nested transactions do seem to work in MySQL.

Here is what I tried on a freshly generated rails (with MySQL) project:

./script/generate model Event title:string --skip-timestamps --skip-fixture

./script/generate model EventLog error_message:text --skip-fixture

class Event < ActiveRecord::Base                                                                                                                                       
  validates_presence_of :title                                                                                                                                         
  after_validation_on_create :log_errors                                                                                                                               

  def log_errors                                                                                                                                                       
    EventLog.log_error(self) if errors.on(:title).present?                                                                                                             
  end                                                                                                                                                                  
end  

class EventLog < ActiveRecord::Base                                                                                                                                    
  def self.log_error(event)                                                                                                                                            
    connection.execute('BEGIN') # If I do transaction do then it doesn't work.
    create :error_message => event.errors.on(:title)                                                                                            
    connection.execute('COMMIT')                                                                                                                                       
  end                                                                                                                                                                  
end 

# And then in script/console:
>> Event.new.save
=> false
>> EventLog.all
=> [#<EventLog id: 1, error_message: "can't be blank", created_at: "2010-10-22 13:17:41", updated_at: "2010-10-22 13:17:41">]
>> Event.all
=> []

Maybe I have over simplified it, or missing some point.

like image 193
Swanand Avatar answered Sep 28 '22 07:09

Swanand