开发者

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

开发者 https://www.devze.com 2023-01-18 21:57 出处:网络
Basically what I want to do is to log an action on MyModel in the table of MyModelLog. Here\'s some pseudo code:

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_som开发者_运维百科ething
    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?


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.


Would this be a good fit for an Observer? I'm not sure, but I'm hoping that exists outside of the transaction... I have a similar need where I might want to delete a record on update...


I've solved a problem like this by taking advantage of Ruby's variable scoping. Basically I declared an error variable outside of a transaction block then catch, store log message, and raise the error again.

It looks something like this:

def something
    error = nil
    ActiveRecord::Base.transaction do
        begin
            # place codez here
        rescue ActiveRecord::Rollback => e
            error = e.message
            raise ActiveRecord::Rollback
        end
    end
    MyModelLog.log_something(error) unless error.nil?
end

By declaring the error variable outside of the transaction scope the contents of the variable persist even after the transaction has exited.


I am not sure if it applies to you, but i assume you are trying to save/create a model from your controller. In the controller it is easy to check the outcome of that action, and you most likely already do to provide the user with a useful flash; so you could easily log an appropriate message there.

I am also assuming you do not use any explicit transactions, so if you handle it in the controller, it is outside of the transaction (every save and destroy work in their own transaction).

What do you think?


MyModelLog.log_something should be done using a different connection.

You can make MyModelLog model always use a different connection by using establish_connection.

class MyModelLog < ActiveRecord::Base
  establish_connection Rails.env # Use different connection

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

Not sure if this is the right way to do logging!!


You could use a nested transaction. This way the code in your callback executes in a different transaction than the failing validation. The Rails documentations for ActiveRecord::Transactions::ClassMethods discusses how this is done.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号