Simple question that used to puzzle me about Rails:
Is it possible to describe a Model's structure from within the model rb file?
From what I understand a model's data structure is kept within the migration, and the model.rb file is supposed to contain only the business logic.
Why is it so? Why does it make more sense to migrate the database with a rake task than to extract it from the class?
The reason migrations are stored separately is so that you can version your database. This would be unwieldy if done inline in the model.
Other ORMs (like DataMapper) do store the schema in the model definition. I think it's really convenient to be able to see model attributes right there, but it is unfortunate to not have the history of your database structure.
What I really wish is that running the migrations would just insert some comments at the top of the model file detailing the schema. That should be a simple hack.
Migrations do not simply show the state of the database schema.
They define the transitions from one state to another.
In a comment to cam's post, you said having the schema in the model would do the same thing, if you had the model's source stored in a VCS, you could look up the previous versions of the schema.
Here is why that is not equivalent to migrations:
Schema Version 1
string :name
string :password
string :token
Schema Version 2
string :username
string :displayname
string :password
string :token
So, what did I do here? What happened to "name"? Did I rename it to username? Or maybe I renamed it to displayname? Or did I drop it entirely?
You don't know. There's no way to tell. You only see the "before" and "after" of the schema. You don't see the transition.
Let's instead look at what I really did with this migration:
class UpdateNameFields < ActiveRecord::Migration
def self.up
rename_column :users, :name, :username
add_column :users, :displayname
User.update_all("displayname = username")
end
def self.down
remove_column :users, :displayname
rename_column :users, :username, :name
end
end
See, I had been using "name" for usernames. But you wouldn't be able to tell that without the migration here. Plus, in an effort to not have my new displayname column be blank on all my existing records, I have seeded it with everyone's existing usernames. This lets me gently introduce this new feature - I can use it and know that existing records aren't going to just see a blank field.
Note that this is a trivial example. Because it was so trivial, you could take a guess that it was one of a couple possible options. But had it been a much more complex transition, you simply would not know.
Transitions from one schema to another can involve a more than just adding/deleting/renaming columns. I gave a little example above in my User.update_all. Whatever code you might need to execute to migrate data to the new schema, you can put in the migration.
When people say migrations are about "versioning the database", they don't just mean versioning the snapshot of the schema. They mean the ability to move between those schema states, and triggering all of the actions involved in going from one state to another.
Related
I'm attempting to write my own timestamps method that gets run during the migration. The one that is in place now adds a NOT_NULL constraint on the field, and I really really don't want that.
The problem I have is that I have a multi schema'd database. Where each major client gets their own schema. When we on-board a new client we create a new tenant record then run a migration for a newly minted schema.
The new schema is supposed to be an exact copy of the tables in the other schemas, except of course with no data.
The last migration I ran was using a slightly older version of rails. Still in the 3's but a smidge older. When it created the timestamps they were NULLable.
When I ran migration the other day (on a new rails)... Well all the fields are now NOT_NULL
I have code that was developed with the idea that updated_at was only populated when the record was updated... not when it was created. (third party apps and database "functions" create the records)..
The third party apps and database functions that create records are falling down on the new schema...
I've gone in and removed all the NOT_NULL constraints on all the tables manually, but I don't want to have to write the cleanup right into my migration task, so that all future tables are corrected..
I figured the best thing to do was to override the timestamps method that was changed, back to one that didn't break existing code.
So there's the reason I need to revert/override..
My question now is... How do I override the method. I can't see a clear class path to it and I'm not exactly sure how to override it..
Put this in a monkey patch... Easy as!
class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::TableDefinition
def timestamps(*args)
options = args.extract_options!
column(:created_at, :datetime, options)
column(:updated_at, :datetime, options)
end
end
As Maniek said. Updates to rails will be ignored because of this "fix".
But his initial offering does the same. Also to accommodate his fix, you'd need to go back through ol' migrations and replace "timestamps" with the new code. Add to that that you'd have to replace all future auto-generated migration too.
I don't think that fits well with DRY.. Nor does it fit in SPOT.
Just B carefulllll!
what's wrong with:
create_table :foo do |t|
t.text :bar
t.datetime :created_at
t.datetime :updated_at
end
?
To my knowledge, you specify your model field data types within db/migrations . This is new to me as in Django, you can directly specify your model field data types in the Model class. Am I correct in thinking about this? Is this a common practice in rails or am I just using a work around?
Also, how do you specify table relationships in this db/migrations file. For instance if I have a model that is called class A.
I have another model called class B and I want a one to many relationship with class A. Do I just do
class ClassA < ActiveRecord::Migration
def change
create_table :projects do |t|
t.classB :name
end
end
end
How do I validate that my migration file and my model file don't have any syntax errors. To my knowledge I just run rake db:migrate, but what if I don't want my migration file to be replaced as I specified my field datatypes in the file?
Ok, so all in all you seem to have three questions:
1.: To my knowledge, you specify your model field data types within db/migrations. [...] Am I correct in thinking about this? Is this a common practice in rails or am I just using a work around?
Yes, you are correct about this. Field data types do not show inside the model, only in your migration.
By the way: I rarely find myself writing migration files manually. If you use the rails command, it will generate migration files automatically. For example, if you use
rails g model User first_name:string last_name:string
this will create a model called User, and a migration that will create a users table with the fields id, first_name, last_name, and timestamp fields. If you want to add or remove columns later, there is a nifty trick for that; just run
rails g migration add_fields_to_users field_name:field_type
or
rails g migration remove_fields_from_users field_name.
Replace field_name, field_type and users as you think fit. This command will create a migration for you to add or remove fields, so you don't have to write those manually.
2.: Also, how do you specify table relationships in this db/migrations file.
You don't. Rails handles this for you through association methods like has_many, belongs_to, has_and_belongs_to, etc. Have a look at this rails guide to active record associations. The one thing you need to do on the database side is add foreign_id columns for a one to many relationship or create join tables for a many to many relationship. For example, if you have a users table and a pictures table, and each picture belongs to a user, in your user model you would write has_many :pictures, in your picture model you would write belongs_to :user, and in your pictures table you need a field called user_id with a type of integer.
3.: How do I validate that my migration file and my model file don't have any syntax errors.
You don't either. You just run rake db:migrate, and if something fails, it will tell you where and why. If your model has syntax errors, it will tell you when you start your server, or when you run your tests, or at least when you use it somewhere (e.g., when you call a model's method). If you mean how you validate your model's data, this is a whole other question - refer to this guide to active record validations and callbacks, which explains validations to check for presence, uniqueness, length, etc. in detail.
You have asked several questions, let's go one by one:
To my knowledge, you specify your model field data types within
db/migrations . This is new to me as in Django, you can directly
specify your model field data types in the Model class. Am I correct
in thinking about this? Is this a common practice in rails or am I
just using a work around?
The migrations are used to alter the database. Example of a migration:
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
end
end
end
When running this, you would be creating a table products with a string field called name. So, yes, you specify your model field data types in the migrations.
Also, how do you specify table relationships in this db/migrations
file. For instance if I have a model that is called class A.
You need to specify your relationships (or associations) in your models. Read this, because it is really well explained. But take into account that in the migrations somehow you have to do some work to create the associations because you might need to create join tables for many to many associations, or create a column that references another table for a has_many association.
How do I validate that my migration file and my model file don't have
any syntax errors. To my knowledge I just run rake db:migrate, but
what if I don't want my migration file to be replaced as I specified
my field datatypes in the file?
I am not sure what you mean in this question.
When you create a new model rails creates your shema migration files and your model.
In the migration file you specify your columns. It is possible to add some code here but you should do as less as possible. For up and down you add/remove columns here, add db indexes and so on.
In your model you define your relations belongs_to, has_many, etc. and your scopes for your tables and ofc the methods for your model. Your model inherit your table columns so you can access them directly.
I don't know Django, this is the common practice in rails.
Relations and other good infos you can check here: http://www.tutorialspoint.com/ruby-on-rails/rails-models.htm
In Django, you fully describe your models in models.py. In Rails with ActiveRecord, you describe part of a model in in the /models directory, and part of it in migrations. Then ActiveRecord introspects model properties from the existing database tables.
But I find migrations, columns, and tables to be a headache.
How can I do like Django -- just declare all model properties instead of introspecting them from the database tables?
And for extra credit, explain where and why this would be a bad idea. :)
If you hate on Migrations, try going NoSQL. No migrations!
So you'd just add properties to your document when you need them. In your code, handle the fact that they may not exist and bam!
I took the following model definition (notice you don't inherit form activerecord) from a blog about tekpub Also recommend the Herding Code podcast
class Production
include MongoMapper::Document
key :title, String, :required => true
key :slug, String, :unique => true, :required => true, :index => true
key :description, String
key :notes, String
key :price, BigDecimal, :numeric => true
key :released_at, Date, :default => Date.today
key :default_height, String, :default => '600'
key :default_width, String, :default => '1000'
key :quotes, String
#royalty info
key :producers, String
timestamps!
end
Try the auto_migrations plugin. I don't think it's a bad idea for development, but I would switch to migrations after going to production when there is critical data in the database.
You may also be interested in replacing ActiveRecord with DataMapper, which works in Rails 3. It has the style you are talking about, with the description of the data fields of a model in the model code instead of a separate database schema file.
I think DataMapper is what you are asking for. Once set up, you'd either use DataMapper.auto_migrate! or DataMapper.auto_upgrade!. The former will drop tables if they exists before creating them, thus destroying any data. That would be bad for production. The latter is how you avoid losing data, and should be just fine for production.
Without knowing exactly what its doing, I'd guess it's inspecting tables during startup to determine whether to make database changes. That can drag down start up time, especially with a lot of models/tables. Which is actually one of the good reasons to consider NoSQL - specifically Mongo as mentioned above. It's fast. Really fast, and thus the start up cost is much, much less. MongoMapper is the way to go. The tekpub blog post is a must read.
I first heard about DataMapper in reading about Merb, so it makes sense that it's in rails 3. I don't know whether you may be able to get it working in rails 2.x.
When is it acceptable to raise an ActiveRecord::IrreversibleMigration exception in the self.down method of a migration? When should you take the effort to actually implement the reverse of the migration?
If you are dealing with production-grade systems then yes, it is very bad. If it is your own pet project, then anything is allowed (if nothing else, it will be a learning experience :) though chances are that sooner rather than later, even in a pet project, you will find yourself having put a cross over a reverse migration only to have to undo that migration a few days later, be it via rake or manually.)
In a production scenario, you should always make the effort to write and test a reversible migration in the eventuality that you go through it in production, then discover a bug which forces you to roll back (code and schema) to some previous revision (pending some non-trivial fix -- and an otherwise unusable production system.)
Reverse migrations range from mostly trivial (removing columns or tables that were added during migration, and/or changing column types, etc.) to somewhat more involved (execute of JOINed INSERTs or UPDATEs), but nothing is so complex as to justify "sweeping it under the rug". If nothing else, forcing yourself to think of ways to achieve reverse migrations can give you new insight into the very problem that your forward migration is fixing.
You might occasionally run into a situation where a forward migration removes a feature, resulting in data being discarded from the database. For obvious reasons, the reverse migration cannot resuscitate discarded data. Although one could, in such cases, recommend having the forward migration automatically save the data or keep it around in the eventuality of rollback as an alternative to outright failure (save to yml, copy/move to a special table, etc.), you don't have to, as the time required to test such an automated procedure could exceed the time required to restore the data manually (should the need arise.) But even in such cases, instead of just failing, you can always make the reverse migration conditionally and temporarily fail pending some user action (i.e. test for the existence of some required table that has to be restored manually; if missing, output "I have failed because I cannot recreate table XYZ from nothingness; manually restore table XYZ from backup then run me again, and I will not fail you!")
If you are destroying data, you can make a backup of it first.
e.g.
def self.up
# create a backup table before destroying data
execute %Q[create table backup_users select * from users]
remove_column :users, :timezone
end
def self.down
add_column :users, :timezone, :string
execute %Q[update users U left join backup_users B on (B.id=U.id) set U.timezone = B.timezone]
execute %Q[drop table backup_users]
end
In a production scenario, you should always make the effort to write and test a reversible migration in the eventuality that you go through it in production, then discover a bug which forces you to roll back (code and schema) to some previous revision (pending some non-trivial fix -- and an otherwise unusable production system.)
Having a reversible migration is fine for development and staging, but assuming well tested code it should be extremely rare that you would ever want to migrate down in production. I build into my migrations an automatic IrreversibleMigration in production mode. If I really needed to reverse a change, I could use another "up" migration or remove the exception. That seems sketchy though. Any bug that would cause a scenario this dire is a sign that the QA process is seriously screwed up.
Feeling like you need an irreversible migration is probably a sign you've got bigger problems looming. Maybe some specifics would help?
As for your second question: I always take the 'effort' to write the reverse of migrations. Of course, I don't actually write the .down, TextMate inserts it automatically when creating the .up.
Reversible Data Migration makes it easy to create reversable data migrations using yaml files.
class RemoveStateFromProduct < ActiveRecord::Migration
def self.up
backup_data = []
Product.all.each do |product|
backup_data << {:id => product.id, :state => product.state}
end
backup backup_data
remove_column :products, :state
end
def self.down
add_column :products, :state, :string
restore Product
end
end
IIRC, you'll have the IrreversibleMigration when changing a datatype in the migration.
I think another situation when it's ok is when you have a consolidated migration. In that case a "down" doesn't really make sense, as it would drop all the tables (except tables added after the consolidation). That's probably not what you'd want.
I don't have a Rails environment set up and this is actually quite hard to find a quick answer for, so I'll ask the experts.
When Rails creates a table based on your "model" that you have set up, does Rails create a table that mirrors this model exactly, or does it add in more fields to the table to help it work its magic? If so, what other fields does it add and why? Perhaps you could cut and paste the table structure, or simply point me to a doc or tutorial section that addresses this.
If you're building a completely new application, including a new database, then you can build the whole back end with migrations. Running
ruby script/generate model User name:string
produces both a user.rb file for the model and a migration:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.timestamps
end
end
def self.down
drop_table :users
end
end
You can see that by default the generate script adds "timestamps" for (created and last updated) and they're managed automatically if allowed to remain present.
Not visible, but important, is that an extra column, "id", is created to be the single primary key. It's not complusory, though - you can specify your own primary key in the model, which is useful if you're working with a legacy schema. Assuming you retain id as the key, then Rails will use whatever RDBMS-specific features are available for new key values.
In ActiveRecord, models are created from database tables, not the other way around.
You may also want to look into Migrations, which is a way of describing and creating the database from Ruby code. However, the migration is not related to the model; the model is still created at runtime based on the shape of the database.
There are screencasts related to ActiveRecord and Migrations on the Rails site: http://www.rubyonrails.org/screencasts
Here's the official documentation for ActiveRecord. It agrees with Brad. You might have seen either a different access method or a migration (which alters the tables and thus the model)
I have had a little experience moving legacy databases into Rails and accessing Rails databases from outside scripts. That sounds like what you're trying to do. My experience is in Rails databases built on top of MySQL, so your mileage may vary.
The one hidden field is the most obvious --- the "id" field (an integer) that Rails uses as its default primary key. Unless you specify otherwise, each model in Rails has an "id" field that is a unique, incremented integer primary key. This "id" field will appear automatically in any model generated within Rails through a migration, unless you tell Rails not to do so (by specifying a different field to be the primary key). If you work with Rails databases from outside Rails itself, you should be careful about this value.
The "id" field is a key part of the Rails magic because it is used to define Rails' associations. Say you relate two tables together --- Group and Person. The Group model will have an "id" field, and the Person model should have both its own "id" field and a "group_id" field for the relationship. The value in "group_id" will refer back to the unique id of the associated Group. If you have built your models in a way that follows those conventions of Rails, you can take advantage of Rails' associations by saying that the Group model "has_many :people" and that the Person model "belongs_to :group".
Rails migrations also, by default, want to add "created_at" and "updated_at" fields (the so-called "timestamps"), which are datetime fields. By default, these take advantage of the "magic" in the database --- not in Rails itself --- to automatically update whenever a record is created or modified. I don't think these columns will trip you up, because they should be taken care of at the database level, not by any special Rails magic.