Skip to content
Andrew Geweke edited this page Nov 2, 2013 · 5 revisions

Migrating tables under low_card_tables works almost identically to without low_card_tables. (Obviously, the table structure is different, but migrations themselves change almost not at all.)

Key Hint: Keep any VARCHAR fields (or other such fields) in your low-card table limited to relatively short lengths — e.g., :limit => 20 or whatever. Many databases impose a restriction on the maximum width of an index key (in MySQL InnoDB, it's 767 bytes); if you simply say t.string :foo in your migrations, this creates a VARCHAR(255), and it only takes three of these to exceed that limit. Generally speaking, this isn't much of an issue, since you're storing only strings with programmatic meaning in a low-card table, anyway.

There are a couple of key exceptions:

  • Low-card tables require a unique index across all attribute columns (and in fact the low_card_tables gem tests for this index, and will blow up and refuse to run if it's not present), and so there's support for automatically creating and maintaining this index.
  • Removing columns from a low-card table is slightly more complex, exactly because of the requirement that there always be a unique index and thus exactly one row in the low-card table for each unique combination of attribute values. There's automatic support for this, too.

The Unique Index

As mentioned previously, there must always be a unique index on all columns in a low-card table. Fortunately, low_card_tables will create and maintain this index for you automatically — all it has to do is know that a table is a low-card table. It can do this in one of two ways:

  • Explicitly: if you add :low_card => true to the options Hash of migration statements like create_table, add_column, remove_column, or change_table, it will know it's a low-card table.
  • Implicitly: if there is a model defined for a given table, and it declares is_low_card_table, then low_card_tables knows that it's a low-card table.

(Hint: it never hurts to be too explicit. ;)

Either way, when columns are changed on the table in question, low_card_tables will first drop the unique index, then run the migration code, then recreate the unique index automatically. low_card_tables always names the index something like index_user_statuses_lc_on_all; it will truncate the embedded table name as necessary to maintain an index name that's not too long for your database system. However, the index can be called anything at all — the code simply verifies that there is a unique index over all columns in the low-card table, not that it is named anything in particular.

(See also Options — the unique index will not cover, and is not required to cover, columns like created_at or updated_at that are not conceptually attributes on the low-card table.)

Explicit Index Support

If you need to perform several migration operations at once, or run any code at all, and want the unique index dropped and created only once, simply do the following in a migration:

change_low_card_table(:user_statuses) do
  ...any code at all...
end

The index will be dropped before executing the block and added after it, and automatic dropping/adding of the index disabled for the duration of the block.

Adding Columns

Adding columns to a live system is always slightly tricky, but it is no more tricky using low_card_tables than it would be without it:

  • When you add a new column, currently-running production code will not know about it. Thus, you must always add new columns as nullable (no :null => false in your migration) or with a default (:default => ...) — otherwise, any production code that tries to insert into the table will fail, since it won't know about the new column or what it should put there.
  • It is perfectly safe to add a nullable new column to a low-card table and then run an UPDATE statement (e.g., UserStatus.update_all(...)) to fill it in — low_card_tables will pick up the new values the next time it flushes its cache. (See Caching for more information.)
  • If you need to add a new column whose values may be different for different referring rows — for example, imagine you decide to add a key_customer column that's computed based on that customer's history — then you simply have to do the same thing you would without low_card_tables. Add the column and either make it nullable or give it a default, and then bulk-update referring rows as necessary.

Removing Columns

Clone this wiki locally