Active Record Migrations (Edge): local models?

In the Edge version of “Active Record Migrations,” section 6, “Using Models in Your Migrations,” we’re advised to create a local model. I see the sample code, but I don’t entirely understand what it’s doing, or how it achieves that. I can’t really suggest wording that would be clearer, since I’m not sure I get the point yet, but I can at least describe what I think I grok, and where it all falls off the rails (as it were):

The example “creates a local model,” declaring a class in-line that does nothing but extend ActiveRecord::Base

class Product < ActiveRecord::Base
end

Because it’s actually subclassed off ActiveRecord::Base, this definition does not extend, modify, or reuse our “real” Product model; it’s a completely unrelated model that happens to have the same name. Because it has no content, all manner of things that the real model would do don’t happen here.

Within the syntactic scope of the migration method, references to the Product model such as these:

add_column :products, :flag, :boolean
Product.reset_column_information

actually operate on our local model, not our real model. But migration methods “operate” by generating SQL statements, and the SQL statements refer to tables and columns by name, and the standard Rails name legerdemain applies, so the SQL statements generated by migration operations on the local model “just happen” to look exactly the same as they would have if generated against our real model. The database “does what I say, not what I mean,” and in this case that’s what’s wanted. Meanwhile, our strikingly rudimentary local model has no validators, so it doesn’t trip up on unsatisfiable validations.

Yes?

It might be clearer with some addition like “This ensures that migration operations, operating on the local model, aren’t subject to the real model’s validators; at the same time, the actual generated SQL is the same, because the local model has the same name as the real one.”

Places where I’m still confused:

Sadly, our actual models by no means follow standard Rails name legerdemain. We’re grafting a Rails app onto a preexisting database, with non-Rails-y naming conventions, and we have a very substantial assortment of alternative name legerdemain. Do I have to reproduce that in the local model? I suppose so….

Name shell-games aside, I’m worried there might be cases where the Rails migration-to-SQL logic would actually need to know the current table definition, and would end up missing important info. For example, if I delete a column from the real model but, say, misspell its name, I seem to get a Ruby/Rails exception about my error. But with a local model, this would have to fall through to SQL’s “no such column” detection. Is that really the same thing? Is there something I should handle differently here? Am I risking more-obscure surprises?

Update: I think I found a case of “migration logic needs to see the real model” complications. Am I understanding this right?

I built a migration like this:

class FixAddonParamsQuotes < ActiveRecord::Migration
class Addon < ActiveRecord::Base
end
def up
change_table :Addons do |t|
t.timestamps
end
Addon.all.each do |addon|
addon.update_attributes!(:params => addon[:params].gsub(/’/,’"’))
addon.update_attributes!(:updated_at => DateTime.now)
end
end
end

But migration never generates or executes the SQL to update :updated_at. If I don’t create the local class, it works fine. I take this to be because the code is looking at the object structure as of the creation of the local class (which has no timestamps), seeing no such column, and silently ignoring the update_attributes! call.

The only way I could find to make this work was to use execute(“UPDATE …”).

Is there a way to get update_attributes to work on columns added in the same transaction, when using this “local model” trick? Is there a more coherent, explainable explanation of why this didn’t work, that deserves to be in the Guide?

N/B: as mentioned, we do some monkeyshines about table names, due to legacy schema. I’ve cut that out here, might possibly have botched the surgery. You should ignore any anomalies around the table/class name.

In the Edge version of “Active Record Migrations,” section 6, “Using Models in Your Migrations,” we’re advised to create a local model. I see the sample code, but I don’t entirely understand what it’s doing, or how it achieves that. I can’t really suggest wording that would be clearer, since I’m not sure I get the point yet, but I can at least describe what I think I grok, and where it all falls off the rails (as it were):

The example “creates a local model,” declaring a class in-line that does nothing but extend ActiveRecord::Base

class Product < ActiveRecord::Base
end

Because it’s actually subclassed off ActiveRecord::Base, this definition does not extend, modify, or reuse our “real” Product model; it’s a completely unrelated model that happens to have the same name. Because it has no content, all manner of things that the real model would do don’t happen here.

Within the syntactic scope of the migration method, references to the Product model such as these:

add_column :products, :flag, :boolean
Product.reset_column_information

actually operate on our local model, not our real model. But migration methods “operate” by generating SQL statements, and the SQL statements refer to tables and columns by name, and the standard Rails name legerdemain applies, so the SQL statements generated by migration operations on the local model “just happen” to look exactly the same as they would have if generated against our real model. The database “does what I say, not what I mean,” and in this case that’s what’s wanted. Meanwhile, our strikingly rudimentary local model has no validators, so it doesn’t trip up on unsatisfiable validations.

Yes?

Yes, your understanding is correct

It might be clearer with some addition like “This ensures that migration operations, operating on the local model, aren’t subject to the real model’s validators; at the same time, the actual generated SQL is the same, because the local model has the same name as the real one.”

Places where I’m still confused:

Sadly, our actual models by no means follow standard Rails name legerdemain. We’re grafting a Rails app onto a preexisting database, with non-Rails-y naming conventions, and we have a very substantial assortment of alternative name legerdemain. Do I have to reproduce that in the local model? I suppose so….

Name shell-games aside, I’m worried there might be cases where the Rails migration-to-SQL logic would actually need to know the current table definition, and would end up missing important info. For example, if I delete a column from the real model but, say, misspell its name, I seem to get a Ruby/Rails exception about my error. But with a local model, this would have to fall through to SQL’s “no such column” detection. Is that really the same thing? Is there something I should handle differently here? Am I risking more-obscure surprises?

I don’t believe you will run into any issues. Rails models can be configured to use a non-standard table name (I assume you have done this, right?). Pretty much all the Rails conventions are based on the model naming, so it’s a good plan to stick to the Rails model naming convention (It just means camel-case, first letter capitalized, plural). You could run into problems if you don’t do this.

it’s a good plan to stick to the Rails model naming convention (It just means camel-case, first letter capitalized, plural).

Actually the model names need to be singular, not plural. Examples:

Product

Customer

ShipmentStatus

TransactionEntry

Hope this helps,

Jeff