Active Record Migrations Models and Associations
11 Apr 2013
As I am circling back to basics, I am finding that this second pass has already been greatly rewarding. As a beginner, when everything is new, there is only so much material one can absorb and in that newness, it can be difficult to know what parts to keep and what parts to put aside until you need them. It has also happened for me that would be able to pick up on patterns enough to cobble something together without knowing what is really going on underneath. Here are some of the a -ha moments I had on my second pass and some other interesting tidbits I found along the way.
Active Record. -
I found it very interesting that Active Record is not just a Ruby library, it is actually the Ruby Active Record Library that borrowed its name from an architectural pattern in software called Active Record. This pattern is based in storing data in a relational database. Martin Fowler gave name to this pattern in his book called "Patterns of Enterprise Application Architecture" in 2003. In this pattern, objects conform to an interface, which include methods that allow a user to get at and manipulate data in a database through the databases columns. Think of the common CRUD actions.(create, read,update, and delete). When you use these actions, you are accessing and manipulating data in the database based on the columns. Here is an example. Let's assume we have created a User table that has a column called name.
1 bob = User.create(:name = "Bob") # here we Create a User named Bob and add the record or "row" to the database where the name Bob will go into the name column. 2 3 #Then let's go ahead and "read" all the users who have the name Bob. 4 5 all_users_named_Bob = User.where(:name => "Bob") # this Reads the database and checks the name column, then gets all the Users named Bob.
Part of the abstraction of this pattern is that the table is wrapped in a class. When you load up an object, it gets loaded or "hydrated" with data from the database. You can see how each object is tied to a single row in a table. In the Rails Active Record library, there are also two additions to the general Active Record pattern that solve some of it's major limitations. These are inheritance and associations. Let's talk about those things but before we do that, let's talk a little bit about the confusions that I had about some things.
When working in rails, it seems models and migrations go hand in hand. I was really not clear on this and in my confusion, it seemed to me that I was doing a lot of things twice. Once in the migration and once in the model. I wasn't quite understanding why, if you where creating a schema, with associations in the migrations, you needed to declare this again in the models, written in a different way. This is where the "magic" of rails was a double edged sword for me using "convention over configuration" and abstracting the details. Alas! I have peeked behind the curtain and can't wait to tell you of the magical adventures.
Active Record Migrations -
From what I've read, in the "old days" every time you wanted to change your database schema, you would have to drop tables or drop entire databases and recreate them to reflect your changes. This is where migrations come in. With migrations, you can create the database schema in a file/files and then run the migration(using the command "rake db:migrate") in order to build your database. If you notice,typically, the migration class is a class that is inheriting from ActiveRecord::Migration. Inside the migration class, there are methods that wrap migration information. (You will likely see def change, up, or, down.) That being said, you can see that the migration is just a class inheriting from ActiveRecord::Migration. If you are thinking that some "magical" things are happening in your file, this is the wonderful work of inheritance and you can always look to the parent class for some more answers to demystify the magical spells. Now, if you need to change something about your database, the best practice is to write a new migration to reflect this change and then to run it. Every time you run the command "rake db:migrate", the migrations that have not yet executed will be. That is why it is a best practice to create a new migration and not edit the old. If you rub rake db:migrate and you only changed a file, the inner workings of the system see that the migration has previouly been ran, will not run it again, and will not reflect your changes. There are ways around this but keepin the convention over configuration you preserve the history of the schema along the way. Since you have the history, you have the ability to rollback to a different state of the schema if you need to. Here is an example of a migration and then another migration that will apply changes to our previous schema.
1 #This migration creates the users table with a firstname and lastname column. 2 3 class CreateUsers < ActiveRecord::Migration 4 def change 5 create_table :users do |t| 6 t.string :firstname 7 t.string :lastname 8 9 t.timestamps 10 end 11 end 12 end
1 #This migration adds an email to the users table which we had just created. It uses the add_column method which takes arguments of a table name(users in our case), the column name, the type(string, boolean, integer, etc.), and options if any. 2 3 class AddEmailToUsers < ActiveRecord::Migration 4 def change 5 add_column :users, :email, :string 6 end 7 end
Let's also create a Goals table which we will use in the next section.
1 class CreateGoals < ActiveRecord::Migration 2 def change 3 create_table :goals do |t| 4 t.string :goal 5 6 t.timestamps 7 end 8 end 9 end
Okay, so now that we have a Users table with a firstname, lastname, and email column and a Goals table with a goals column, we can talk about associations.
Active Record Models and Associations. -
So what is the point of the Model?
If you take a look at a model in rails, you will notice that it inherits from ActiveRecord::Base.
1 class User < ActiveRecord::Base # <--- it inherits from ActiveRecord::Base 2 3 end
You may also notice that the Model usually corresponds to a migration or a table and that the table is a pluralized version of the name of the model. i.e. Goal model and a migration that refers to the Goals table. This is a suggested "convention over configuration" in rails that helps to sprinkle a little magic over your code and give you some benefits. Each instance of a class that has inherited from ActiveRecord::Base maps to one row of the backing database table.
What about the associations? Don't we have that in the schema? I notice that people are always writing things like Users.goals . That is precisely the point of associations. Associations give you the ability to call .goals on User . When you see relationships like has_many and belongs_to when creating associations, it is vital to understand that these are methods. These methods convert the arguments passed to them into methods that you can call on instances of your model class. This confused me greatly at first due to the choice of the community usually writing these methods without parens() but not stating that they are implied. Once I read that in The Rails 3 Way it made a lot more sense. Let's take a look.
1 #This is how you usually see this. Without parens... 2 3 class User < ActiveRecord::Base 4 has_many :goals 5 end
1 #This is what it would look like with parens... 2 3 class User < ActiveRecord::Base 4 has_many(:goals) 5 end
So let's look at the models below...
1 class User < ActiveRecord::Base 2 has_many :goals 3 end
1 class Goal < ActiveRecord::Base 2 belongs_to :user 3 end
1 goal_1 = Goal.create(:goal => "to try to explain the way this works.") 2 goal_2 = Goal.create(:goal => "I hope it it's working.") 3 angeleah = User.create(:firstname => "Angeleah", :lastname => "Daidone", :goals => [goal_1, goal_2]) 4 5 #Then I can say... 6 puts angeleah.goals 7 8 #I will get this back... 9 [#<Goal id: 1, goal: "to try to explain the way this works.", created_at: "2013-04-09 22:09:33", updated_at: "2013-04-09 22:09:33">, #<Goal id: 2, goal: "I hope it it's working.", created_at: "2013-04-09 22:09:33", updated_at: "2013-04-09 22:09:33">]
In this case, the use of has_many and belongs_to in the User and Goal models are what draw the "associations" and allow you to be able to call the goals method on an instance of user. They are convenience methods that abstract the fact that these database tables are being tied together with much larger SQL queries under the hood. Active Record uses a DSL(domain specific language) to set up "associations", allowing you, in the end, to simply type Users.goals . Active Record uses the associations that we create in the models to exectute meta-programming which, in the end adds methods that we can then use on our objects.
This is just the beginning of how all of these things work together. I would highly suggest The Rails 3 Way, The doc for ActiveRecord, and theRuby on Rails Guides to look at things more closely if you are interested.
I totally just meta-programmed a .active_record_migrations_models_and_associations_knowledge method that you can now call on your brain. Boo-ya!!!! :)