class Model < ActiveRecord::Base before_destroy :beforeDestroyMethod def beforeDestroyMethod return false end end
All operations on the model instances are contained within transactions. If any of the before_* callbacks return false, the transaction is rolled back. This is all well and good, provided you don't want to insert a record in a different table in the before_destroy method. I happened to want to log any attempt to destroy records from the database, and I wanted to use the database to store the log entry:
class Model < ActiveRecord::Base before_destroy :beforeDestroyMethod def beforeDestroyMethod log = LogEntry.new log.action_type = "Destroy Object" log.save return false end end
Unfortunately, when the before_destroy method returned false, it also rolled back the log entry insertion.
After looking around, I found a partial answer. If you call 'establish_connection' in your Rails model, you will get a new database connection for that class and all classes that inherit from it:
class LogEntry < ActiveRecord::Base establish_connection ENV['RAILS_ENV'] end
I am using ENV['RAILS_ENV'] so that it creates a connection for the development or the production database, depending on which environment it's in. You can also specify a different database entirely.
My other models can now work as I had intended before:
class Model < ActiveRecord::Base before_destroy :beforeDestroyMethod def beforeDestroyMethod log = LogEntry.new log.action_type = "Destroy Object" log.save return false end endSo, that seems to have solved the problem. Well, mostly. What happens if you want to add a log entry if someone tries to destroy a log entry? You have the exact same problem as before. This time, I used a different trick for this one case:
class LogEntry < ActiveRecord::Base establish_connection ENV['RAILS_ENV'] before_destroy :beforeDestroyMethod def beforeDestroyMethod query = "insert into log_entries(action_type) values ("Destroy Log Entry");" ActiveRecord::Base.connection_pool.with_connection do |conn| conn.execute(query) end return false end end
This time, I just grab a database connection from the connection pool. Unfortunately, you have to utilize raw queries to use the connection, but it gets the job done.
Alternatively, I could have also got a connection by using the checkout method:
class LogEntry < ActiveRecord::Base establish_connection ENV['RAILS_ENV'] before_destroy :beforeDestroyMethod def beforeDestroyMethod query = "insert into log_entries(action_type) values ("Destroy Log Entry");" conn = ActiveRecord::Base.connection_pool.checkout conn.execute(query) ActiveRecord::Base.connection_pool.checkin(conn) return false end end
Thus, my problem was solved.
Lee, you are amazing! Who would of thought that would be a problem in the first place?... That was a way tough bug to figure out, since it seems common sense that the before_filter would work normally.
ReplyDelete