I'm building an application where we're using a message queue.
New objects add their IDs to a beanstalkd queue via an after_create callback. Some daemons monitor beanstalk, and grab objects to process, as soon as they hit the queue.
We were getting errors from the daemons, saying "record with ID 'N' not found", but when we looked for object N via script/console, or the database, there it was.
It turns out that the after_create callback (along with all the other object creation callbacks) occurs *inside* a transaction. So, this is what was happening;
There are a couple of ways around this;
Call "self.class.connection.commit_db_transaction" in the model, after the after_create method. This works, but it smells really bad.
A nicer way is to add an "after_commit" callback, like this
Pat Allan has some modifications to this, to make it play nicer with Rails 2.0 and 2.1
Or, there is a version here which handles nested transactions.
Many thanks to Pat for showing me the after_commit stuff.
2008/07/01
ActiveRecord commit timing
Posted by
Anonymous
at
11:52 pm
|
Labels: activerecord, rails
2008/06/07
Using non-standard primary keys with ActiveRecord in Ruby on Rails
A Rails application I'm currently working on has a 'User' model. For the purposes of this app. a 'user' is uniquely identified by their mobile phone number - a user must have a mobile phone number, and may only have a single one. A second mobile phone number must be a second user.
I'm going to need the mobile phone number in other models - e.g. "User has_many :messages", and I'd like to have a "mobile_phone_number" attribute in my Message model, so that I can say "message.mobile_phone_number" without having to join the users table all the time, like this; "message.user.mobile_phone_number".
I can get what I want if the mobile_phone_number is the primary key of the User model. Let's try it.
Here is the migration (this is specific to mysql);
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users, :id => false do |t|
t.integer :mobile_phone_number
end
execute "alter table users modify column mobile_phone_number bigint unsigned primary key"
end
def self.down
drop_table :users
end
end
(The alter table statement is a bit of a hack. Another way to achieve the same result would be to use the mysql_bigint gem)
Here is the model;
class User < ActiveRecord::Base
set_primary_key :mobile_phone_number
end
Looks OK, but this is what happens when you try to create a User;
>> u = User.create :mobile_phone_number => 447123456789
=> #<User mobile_phone_number: 0>
Where did that '0' come from, and what happened to the mobile_phone_number?
A quick look in the logs shows this;
WARNING: Can't mass-assign these protected attributes: mobile_phone_number
SQL (0.000146) BEGIN
User Create (0.000222) INSERT INTO `users` VALUES(DEFAULT)
SQL (0.000422) COMMIT
So, Rails is not sending the primary key column value in the SQL create statement. This would make sense if, as usual, our primary key was an autoincrement column. The database would supply that value for us, so we don't want to send it. But, in our case, we want to set the primary key ourselves, so this behaviour is wrong. (The 0 comes from the default value of the column)
You can try using 'attr_accessible' to allow you to assign to the mobile_phone_number, but it doesn't work (presumably because it's the primary key that we're trying to assign to).
We need some way to override the standard Rails primary key behaviour so it does what we want. That sounds a lot like the composite_primary_keys gem.
So, after installing the gem (in my case, after installing Rick Olson's 'gems' plugin so that I can put the composite_primary_keys gem in my project's vendor directory), here is the updated model;
require 'composite_primary_keys'
class User < ActiveRecord::Base
set_primary_keys :mobile_phone_number
end
Note that 'set_primary_key' has changed to 'set_primary_keys'.
Now, we get this;
>> u = User.create :mobile_phone_number => 447123456789
=> #<User mobile_phone_number: 447123456789>
Much better. Even though we're not using a composite_primary_key, the gem is overriding the default Rails behaviour so that the key we want is being set by the create statement.
If you know a more elegant way to do this, please let me know.
Posted by
Anonymous
at
11:49 am
|
Labels: activerecord, composite_primary_keys