Updating Class Table Inheritance Tables
My last post covered querying class table inheritance tables; this one presents a method for updating them. Having set up our ActiveRecord models using composition, we can use a standard rails method accepts_nested_attributes_for to allow easy one-form updating of the relationship.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Item < ActiveRecord::Base validates_numericality_of :quantity SUBCLASSES = [:dvd, :car] SUBCLASSES.each do |class_name| has_one class_name end accepts_nested_attributes_for *SUBCLASSES end @item = Dvd.create!( :title => 'The Matix', :item => Item.create!(:quantity => 1)) @item.update_attributes( :quantity => 2, :dvd_attributes => { :id => @item.dvd.id, :title => 'The Matrix'}) |
This issues the following SQL to the database:
1 2 |
UPDATE "items" SET "quantity" = 10 WHERE ("items"."id" = 12)
UPDATE "dvds" SET "title" = 'The Matrix' WHERE ("dvds"."id" = 12)
|
Note that depending on your application, you may need some extra locking to ensure this method is concurrent, for example if you allow items to change type. Be sure to read the accepts_nested_attributes_for documentation for the full API.
I talk about this sort of thing in my “Your Database Is Your Friend” training sessions. They are happening throughout the US and UK in the coming months. One is likely coming to a city near you. Head on over to www.dbisyourfriend.com for more information and free screencasts
Class Table Inheritance and Eager Loading
Consider a typical class table inheritance table structure with items as the base class and dvds and cars as two subclasses. In addition to what is strictly required, items also has an item_type parameter. This denormalization is usually a good idea, I will save the justification for another post so please take it for granted for now.
The easiest way to map this relationship with Rails and ActiveRecord is to use composition, rather than trying to hook into the class loading code. Something akin to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
class Item < ActiveRecord::Base SUBCLASSES = [:dvd, :car] SUBCLASSES.each do |class_name| has_one class_name end def description send(item_type).description end end class Dvd < ActiveRecord::Base belongs_to :item validates_presence_of :title, :running_time validates_numericality_of :running_time def description title end end class Car < ActiveRecord::Base belongs_to :item validates_presence_of :make, :registration def description make end end |
A naive way to fetch all the items might look like this:
1 |
Item.all(:include => Item::SUBCLASSES) |
This will issue one initial query, then one for each subclass. (Since Rails 2.1, eager loading is done like this rather than joining.) This is inefficient, since at the point we preload the associations we already know which subclass tables we should be querying. There is no need to query all of them. A better way is to hook into the Rails eager loading ourselves to ensure that only the tables required are loaded:
1 2 3 |
Item.all(opts).tap do |items| preload_associations(items, items.map(&:item_type).uniq) end |
Wrapping that up in a class method on items is neat because we can then use it as a kicker at the end of named scopes or associations – person.items.preloaded, for instance.
Here are some tests demonstrating this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
require 'test/test_helper' class PersonTest < ActiveRecord::TestCase setup do item = Item.create!(:item_type => 'dvd') dvd = Dvd.create!(:item => item, :title => 'Food Inc.') end test 'naive eager load' do items = [] assert_queries(3) { items = Item.all(:include => Item::SUBCLASSES) } assert_equal 1, items.size assert_queries(0) { items.map(&:description) } end test 'smart eager load' do items = [] assert_queries(2) { items = Item.preloaded } assert_equal 1, items.size assert_queries(0) { items.map(&:description) } end end # Monkey patch stolen from activerecord/test/cases/helper.rb ActiveRecord::Base.connection.class.class_eval do IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/] def execute_with_query_record(sql, name = nil, &block) $queries_executed ||= [] $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r } execute_without_query_record(sql, name, &block) end alias_method_chain :execute, :query_record end |
I talk about this sort of thing in my “Your Database Is Your Friend” training sessions. They are happening throughout the US and UK in the coming months. One is likely coming to a city near you. Head on over to www.dbisyourfriend.com for more information and free screencasts
Concurrency with AASM, Isolation Levels
I’ve posted two guest articles over on the Engine Yard blog this week on database related topics:
They’re in the same vein as what I’ve been posting here, so worth a read if you’ve been digging it.
The US tour kicks off this Saturday in San Francisco, and there’s still a couple of spots available. You can still register over at www.dbisyourfriend.com
“Your Database Is Your Friend” training sessions are happening throughout the US and UK in the coming months. One is likely coming to a city near you. For more information and free screencasts, head on over to www.dbisyourfriend.com
Five Tips For Adding Foreign Keys To Existing Apps
You’re convinced foreign keys are a good idea, but how should you retroactively add them to your production application? Here are some tips to help you out.
Identify and fix orphan records. If orphan records exist, creating a foreign key will fail. Use the following SQL to identify children that reference a parent that doesn’t exist:
1 |
SELECT * FROM children LEFT JOIN parents ON parent_id = parents.id WHERE parents.id IS NULL |
Begin with new or unimportant relationships. With any new change, it’s best to walk before you run. Targeting the most important relationships in your application head on can quickly turn into a black hole. Adding foreign keys to new or low value relationships first means you have a smaller code base that is affected, and allows you to test your test suite and plugins for compatibility over a smaller area. Get this running in production early, so any issues will crop up early on low value code where they’ll be easier to fix. Be agile in your approach and iterate.
Move away from fixtures and mocking in your tests. Rails fixture code is not designed to work well with foreign keys. (Fixtures are generally not a good idea regardless.) Also, the intense stubbing of models that was in vogue back when rspec first came on the scene doesn’t play nice either. The current best practice is to use object factories (such as Machinist) to create your test data, and this works well with foreign keys.
Use restrict rather than cascade for ON DELETE. You still want to keep on_destroy logic in your models, so even if conceptually a cascading delete makes sense, implement it using the :dependent => :destroy option to has_many, with a restrict option at the database level to ensure all cascading deletes run through your callbacks.
Be pragmatic. Ideally every relationship will have a foreign key, but for that model filled with weird hacks and supported by a massive old school test suite, it may be just too much effort to get everything working smoothly with database constraints. In this case, set up a test suite that runs over your production data regularly to quickly identify any data problems that arise (see the SQL above).
Foreign keys give you confidence and piece of mind about your data and your application. Rails may be afraid of them, but that doesn’t mean you have to be.
July through September I am running full day training sessions in the US and UK on how to make use of your database and write solid Rails code, increasing your quality without compromising your velocity. Chances are I’m coming to your city, so check it out at http://www.dbisyourfriend.com
acts_as_state_machine is not concurrent
Here is a short 4 minute screencast in which I show you how the acts as state machine (AASM) gem fails in a concurrent environment, and also how to fix it.
(If embedding doesn’t work or the text is too small to read, you can grab a high resolution version direct from Vimeo)
It’s a pretty safe bet that you want to obtain a lock before all state transitions, so you can use a bit of method aliasing to do just that. This gives you much neater code than the quick fix I show in the screencast, just make sure you understand what it is doing!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class ActiveRecord::Base def self.obtain_lock_before_transitions AASM::StateMachine[self].events.keys.each do |t| define_method("#{t}_with_lock!") do transaction do lock! send("#{t}_without_lock!") end end alias_method_chain "#{t}!", :lock end end end class Tractor # ... aasm_event :buy do transitions :to => :bought, :from => [:for_sale] end obtain_lock_before_transitions end |
This is a small taste of my DB is your friend training course, that helps you build solid rails applications by finding the sweet spot between stored procedures and treating your database as a hash. July through September I am running full day sessions in the US and UK. Chances are I’m coming to your city. Check it out at http://www.dbisyourfriend.com
Three Reasons Why You Shouldn't Use Single Table Inheritance
It creates a cluttered data model. Why don’t we just have one table called objects and store everything as STI? STI tables have a tendency to grow and expand as an application develops, and become intimidating and unweildy as it isn’t clear which columns belong to which models.
It forces you to use nullable columns. A comic book must have an illustrator, but regular books don’t have an illustrator. Subclassing Book with Comic using STI forces you to allow illustrator to be null at the database level (for books that aren’t comics), and pushes your data integrity up into the application layer, which is not ideal.
It prevents you from efficiently indexing your data. Every index has to reference the type column, and you end up with indexes that are only relevant for a certain type.
The only time STI is the right answer is when you have models with exactly the same data, but different behaviour. You don’t compromise your data model, and everything stays neat and tidy. I have yet to see a case in the wild where this rule holds, though.
If you are using STI (or inheritance in general) to share code, you’re doing it wrong. Having many tables does not conflict with the Don’t-Repeat-Yourself principle. Ruby has modules, use them. (I once had a project where a 20 line hash drove the creation of migrations, models, data loaders and test blueprints.)
What you should be doing is using Class Table Inheritance. Rails doesn’t “support it natively”, but that doesn’t particularly mean much since it’s a simple pattern to implement yourself, especially if you take advantage of named scopes and delegators. Your data model will be much easier to work with, easier to understand, and more performant.
I expand on this topic and guide you through a sample implementation in my DB is your friend training course. July through September I am running full day sessions in the US and UK. Chances are I’m coming to your city. Check it out at http://www.dbisyourfriend.com
acts_as_list will break in production
acts_as_list doesn’t work in a typical production deployment. It pretends to for a while, but every application will eventually have issues with it that result in real problems for your users. Here is a short 4 minute long screencast showing you how it breaks, and also a quick fix which will prevent your data from becoming corrupted.
(View it over at Vimeo if embedding doesn’t work for you)
Here is the “quick fix” I apply in the screencast. It’s ugly, but it will work.
1 2 3 4 5 6 7 8 |
def move_down Tractor.transaction do Tractor.connection.execute("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE") @tractor = Tractor.find(params[:id]) @tractor.move_to_bottom end redirect_to(tractors_path) end |
Some things to note when fixing your application in a nicer way:
- This is not MySQL specific, all databases will exhibit this behaviour.
- The isolation level needs to be set as the first statement in the transaction (or globally, but you don’t want serializable globally!)
- For bonus points, add a unique index to the position column, though you’ll have to re-implement most of
acts_as_listto make it work. - It’s possible to do this under read committed, but it’s pretty complicated and optimised for concurrent access rather than individual performance.
- Obtaining a row lock before moving will fix this specific issue, but won’t address all the edge cases.
_This is a small taste of the type of thing I cover in my DB is your friend training course. July through September I am running full day sessions in the US and UK. Chances are I’m coming to your city. Check it out at http://www.dbisyourfriend.com _
Acts_as_state_machine locking
consider the following!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Door < ActiveRecord::Base acts_as_state_machine :initial => :closed state :closed state :open, :enter => :say_hello event :open do transitions :from => :closed, :to => :open end def say_hello puts "hello" end end door = Door.create! fork do transaction do door.open! end end door.open! # >> hello # >> hello |
It’s broken, you can only open a door once. This is a classic double-update problem. One way to solve is with pessimistic locking. I made some codes that automatically lock any object when you call an event on it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class ActiveRecord::Base # Forces all state transition events to obtain a DB lock def self.obtain_lock_before_all_state_transitions event_table.keys.each do |transition| define_method("#{transition}_with_lock!") do self.class.transaction do lock! send("#{transition}_without_lock!") end end alias_method_chain "#{transition}!", :lock end end end class Door < ActiveRecord::Base # ... as before obtain_lock_before_all_state_transitions end |
beware! Your state transitions can now throw ActiveRecord::RecordNotFound errors (from lock!), since the object may have been deleted before you got a chance to play with it.
If you’re not using any locking in your web app, you’re probably doing it wrong. Just sayin’.
Logging SQL statistics in rails
When your sysadmin comes to you whinging with a valid concern that your app is reading 60 gazillion records from the DB, you kinda wish you had a bit more information than % time spent in the DB. So I wrote a plugin that counts both the number of selects/updates/inserts/deletes and also the number of records affected.
1 |
git clone git://github.com/xaviershay/sql-counter.git vendor/plugins/sql_counter |
That does the counting, you need to decide how to log it. I am personally quite partial to adding it to the request log line, thus getting stats per request:
1 2 3 4 5 |
# vendor/rails/actionpack/lib/action_controller/benchmarking.rb:75 log_message << " | Select Records: #{ActiveRecord::Base.connection.select_record_count}" log_message << " | Selects: #{ActiveRecord::Base.connection.select_count}" ActiveRecord::Base.connection.reset_counters! |
Don’t forget the last line, otherwise you get cumulative numbers. That may be handy, but I doubt it. We’re only logging selects because that’s all we care about at the moment. I am sure this will change in time.
UPDATE: Moved to github, bzr repo is no longer available
Counting ActiveRecord associations: count, size or length?
Short answer: size. Here’s why.
length will fall through to the underlying array, which will force a load of the association
1 2 3 |
>> user.posts.length Post Load (0.620579) SELECT * FROM posts WHERE (posts.user_id = 1321) => 162 |
This is bad. You loaded 162 objects into memory, just to count them. The DB can do this for us! That’s what count does.
1 2 3 |
>> user.posts.count SQL (0.060506) SELECT count(*) AS count_all FROM posts WHERE (posts.user_id = 1321) => 162 |
Now we’re on to something. The problem is, count will always issue a count to the DB, which is kind of redundant if you’ve already loaded the association. That’s were size comes in. It’s got smarts. Observe!
1 2 3 4 5 6 7 |
>> User.find(1321).posts.size User Load (0.003610) SELECT * FROM users WHERE (users.id = 1321) SQL (0.000544) SELECT count(*) AS count_all FROM posts WHERE (posts.user_id = 1321) => 162 >> User.find(1321, :include => :posts).posts.size User Load Including Associations (0.124950) SELECT ... => 162 |
Notice it uses count, but if the association is already loaded (i.e. we already know how many objects there are), it uses length, for optimum DB usage.
But know that’s not all. There’s always more. If you also store the number of posts on the user object, as is common for performance reasons, size will use that also. Just make sure the column is named _association__count (i.e. posts_count).
1 2 3 4 5 |
>> User.columns.collect(&:name).include?("posts_count") => true >> User.find(1321).posts.size User Load (0.003869) SELECT * FROM users WHERE (users.id = 1321) => 162 |
The bad news
So now you’re all excited, I better tell you why this is only fantastic until you start using has_many :through.
Now, the situation is slightly different between 1.2.x (r4605) and edge (r7639), so I’ll start with stable. Now, they may look the same but a normal has_many association and one with the :through option are actually implememted by two entirely separate classes under the hood. And it so happens that the has_many :through version kind of, well, doesn’t have quite the same smarts. It loads up the association just as length does (then falls through to Array#size). Edge is sharp enough to use a This patch was added to edge in 7692count, but still doesn’t know about any caches you may be using. This was commited in r7237, so it’s pretty easy to patch in to stable. Or you can use this extension (on either branch – here is the trac ticket):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
module CoreExtensions::HasManyThroughAssociation def size return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter? return @target.size if loaded? return count end def has_cached_counter? @owner.attribute_present?(cached_counter_attribute_name) end def cached_counter_attribute_name "#{@reflection.name}_count" end end ActiveRecord::Associations::HasManyThroughAssociation.send(:include, CoreExtensions::HasManyThroughAssociation) |
How it doesn’t work
1 |
user.posts.find(:all, :conditions => ["reply_count > ?", 50]).size |
size normally works because assocations use a proxy – when I call user.posts it won’t actually load any posts until I call a method that requires them. So user.posts.size can work without ever loading the posts because they aren’t required for the operation. The above code won’t work well because find does not use a proxy – it will straight away load the requested posts from the DB, without size getting a chance to send a COUNT instead. You may be better off moving this finder logic into an association so that size will work as expected. This also has the benefit that if you decide to add a counter cache later on you won’t have to change any code to use it.
1 |
has_many :popular_posts, :class_name => "Post", :foreign_key => "post_id", :conditions => ["reply_count > ?", 50] |
So use size when counting associations unless you have a good reason not to. Most importantly thought, ensure you’re watching your development log so to be aware what SQL your app is generating.
UPDATE: Added link to my patch on trac
UPDATE 2: ... which is now closed, see r7692