I'm using RoR version 4.2.3, and I understand I can set the isolation level of my transactions. However, where do I define setting the isolation level of all transactions? so I only have to define it once and then not worry about it?
I'm using postgreSQL as my database
There does not seem to be a global isolation option, thus you are left with four options:
Monkeypatch existing transaction implementation, so that it picks
your desired isolation level. (Monkeypatching is not desirable)
Use correct isolation level throughout your application.
SomeModel.transaction(isolation: :read_committed)
Extend ActiveRecord and create your own custom transaction method.
As commented - you may be able to edit the default isolation level in DB configuration. For postgres it's this one
Example code:
#lib/activerecord_extension.rb
module ActiveRecordExtension
extend ActiveSupport::Concern
module ClassMethods
def custom_transaction
transaction(isolation: :read_committed) do
yield
end
end
end
end
ActiveRecord::Base.send(:include, ActiveRecordExtension)
Then in your initializers:
#config/initializers/activerecord_extension.rb
require "active_record_extension"
Afterwards you can use:
MyModel.custom_transaction do
<...>
end
And in the future, this will allow you to change the isolation level in one place.
Rails doesn't support setting a global isolation level, but Postgres lets you set one for a session. You can hook into Rails' connection establishment to run a command every time a connection is made, thought the techniques for this all rely on monkeypatching and may be questionable.
Run Raw SQL in Rails after connecting to Database
Can I hook into ActiveRecord connection establishment?
Then configure your isolation level with:
SET SESSION CHARACTERISTICS AS TRANSACTION transaction_mode
Though this is interesting, I'd go with something more like Magnuss's answer for maintainability and sanity.
Related
I have a model called SystemSettings with a name on and a value. It is where I store the majority of my configuration for my app. I need to be able to access it in my production.rb inside my rails 3.2 app. How would you go about doing this?
Since the Rails config such as production.rbis read before ActiveRecord is initialised you would need to use a callback:
Rails.application.configure do
ActiveSupport.on_load(:active_record) do
config.custom_variable = SystemSettings.find_by(name: "Foo").value
end
end
But since the callback executes later when ActiveRecord is ready you can't immediately use its value which is why your approach may be flawed due to race conditions.
Unless you are building something like a CMS where you need to provide a user interface to edit system settings you will be better off using environmental variables. They are immediately available from memory and do not have the overhead of a database query.
http://guides.rubyonrails.org/v3.2.9/initialization.html
I have a rails application with a dynamically configured time zone. It is stored in a database table containing other options, and the rails application itself is configured to UTC (default).
I've made the application itself aware of the timezone with a simple around filter using Time.use_zone(..., &block).
I would like to do something similar for my Sidekiq workers. Some of them process data that has timezone relevance, so they need it. I don't see any filtering options available in Sidekiq itself, no callbacks, before/after type things I can hook into. My current solution is to a prepend a module, like so:
module TimeZoneAwareWorker
def perform(*args)
Time.use_zone(Options.time_zone) do
super
end
end
end
and mixed in:
class MyWorker
include Sidekiq::Worker
prepend TimeZoneAwareWorker
...
end
This works fine for simple workers, but breaks down if the prepend occurs in the same class as the include Sidekiq::Worker. If the worker is subclassed, the hierarchy doesn't work out for the prepended perform to wrap the implementation.
Is there a better way? Ultimately it seems what I really want is a foolproof method of wrapping a single method with another method, and yielding the wrapped implementation.
I know my other option is monkeypatching before/after/around type callbacks into Sidekiq's implementation, but I'd like to only go there if forced.
Sidekiq has its own middleware solution:
Sidekiq has a similar notion of middleware to Rack: these are small
bits of code that can implement functionality. Sidekiq breaks
middleware into client-side and server-side.
Client-side middleware runs before the pushing of the job to Redis and allows you to modify/stop the job before it gets pushed. Client
middleware may receive the class argument as a Class object or a
String containing the name of the class.
Server-side middleware runs 'around' job processing. Sidekiq's retry feature is implemented as a simple middleware.
You can easily create your own middleware agent to add the timezone awareness code.
I need to manage transaction isolation level on a per-transaction basis in a way portable across databases (SQLite, PostgreSQL, MySQL at least).
I know I can do it manually, like that:
User.connection.execute('SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE')
...but I would expect something like:
User.isolation_level( :serializable ) do
# ...
end
This functionality is supported by ActiveRecord itself:
MyRecord.transaction(isolation: :read_committed) do
# do your transaction work
end
It supports the ANSI SQL isolation levels:
:read_uncommitted
:read_committed
:repeatable_read
:serializable
This method is available since Rails 4, it was unavailable when the OP asked the question. But for any decently modern Rails application this should be the way to go.
There was no gem available so I developed one (MIT): https://github.com/qertoip/transaction_isolation
Looks Rails4 would have the feature out of box:
https://github.com/rails/rails/commit/392eeecc11a291e406db927a18b75f41b2658253
I have a somewhat special use case, where I'd like to create a method that accepts a block, such that anything that happens inside that block is not written to the DB.
The obvious answer is to use transactions like so:
def no_db
ActiveRecord::Base.transaction do
yield
raise ActiveRecord::Rollback
end
end
But the trouble is that if my no_db method is used inside of another transaction block, then I'll ned up in the case of nested transactions. The drawback here is that nested transactions are only supported by MySQL, and I need support for PG, but more importantly SQLite (for tests). (I understand that PG is supported via savepoints, how reliable is that? performance hit?).
The other problem with this type of approach is that it seems really inefficient, writing things to a DB, and then rolling them back. It would be better if I could do something like this:
def no_db_2
# ActiveRecord::Base.turn_off_database
yield
# ActiveRecord::Base.turn_on_database
end
Is there such a method? Or a similar approach to what I'm looking for? I think it needs to be fairly low level..
(Rails version is 3.0.5, but I would be happy if there were an elegant solution for Rails 3.1)
This might be one way to do it:
class Book < ActiveRecord::Base
# the usual stuff
end
# Seems like a hack but you'll get the
# transaction behavior this way...
class ReadOnly < ActiveRecord::Base
establish_connection "#{Rails.env}_readonly"
end
I would think that this...
ReadOnly.transaction do
Book.delete_all
end
...should fail.
Finally, add another connection to config/database.yml
development:
username: fullaccess
development_readonly:
username: readonly
One downside is the lack of support for a read-only mode in the sqlite3-ruby driver. You'll notice that the mode parameter doesn't do anything yet according to the documentation. http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html#M000071
In my rails application, I have a background process runner, model name Worker, that checks for new tasks to run every 10 seconds. This check generates two SQL queries each time - one to look for new jobs, one to delete old completed ones.
The problem with this - the main log file gets spammed for each of those queries.
Can I direct the SQL queries spawned by the Worker model into a separate log file, or at least silence them? Overwriting Worker.logger does not work - it redirects only the messages that explicitly call logger.debug("something").
The simplest and most idiomatic solution
logger.silence do
do_something
end
See Logger#silence
Queries are logged at Adapter level as I demonstrated here.
How do I get the last SQL query performed by ActiveRecord in Ruby on Rails?
You can't change the behavior unless tweaking the Adapter behavior with some really really horrible hacks.
class Worker < ActiveRecord::Base
def run
old_level, self.class.logger.level = self.class.logger.level, Logger::WARN
run_outstanding_jobs
remove_obsolete_jobs
ensure
self.class.logger.level = old_level
end
end
This is a fairly familiar idiom. I've seen it many times, in different situations. Of course, if you didn't know that ActiveRecord::Base.logger can be changed like that, it would have been hard to guess.
One caveat of this solution: this changes the logger level for all of ActiveRecord, ActionController, ActionView, ActionMailer and ActiveResource. This is because there is a single Logger instance shared by all modules.