This workshop is important because:
ActiveRecord allows us to interact with a database by writing declarative Ruby instead of imperative SQL. It is a major component of Rails.
After this workshop, developers will be able to:
- Create a model that inherits from ActiveRecord class
- CRUD data in the database using our model
- Write a migration to define a database schema
- Update our database schema with another migration
Before this workshop, developers should already be able to:
- Explain MVC
- Write object oriented Ruby
- Set and get data from a SQL database
##Models - Intro
We can apply the design pattern of MVC to make more complex applications. Models literally "model" or describe the form of an object that we will represent in our application. This model object will contain methods that set and get its data in our database. Using a model, to a large extent, abstracts the complex SQL statements of a database away from the developer.
##ORM
So how does the model talk to the database?
Well, that's where ORMs come in. ORM stands for: Object Relational Mapping, and it's a technique that connects the rich objects of an application to tables in a relational database management system. Let's draw on the board how a user object, instantiated from the User class, could map to a Users table in our database.
####Example
Let's pretend we have a User class with the attributes id
, name
, age
, and address
:
class User
attr_accessor :id, :name, :age, :address
end
And let's pretend that we create a new user, Rob Stark, whose object is shown below:
=> #<User:0x007fc8b18c5718 @address="1 Winterfell Lane", @age=16, @id=1, @name="Rob Stark", @king?=true>
With an ORM, we're able to take that instance of class User and map it to our relational database:
id | name | age | address | king?
----+-----------+-----+----------------------------------------------------+-------
1 | Rob Stark | 16 | 1 Winterfell Lane | true
(1 row)
Using ORMs, the properties and relationships of the objects in an application can be easily stored and retrieved from a database without writing SQL statements directly and with less database access code, overall.
##ActiveRecord
Rails Guides: Active Record Basics:
ActiveRecord, as an ORM Framework, gives us several mechanisms, the most important being the ability to:
- represent models and their data
- represent associations between these models
- represent inheritance hierarchies through related models
- validate models before they get persisted to the database
- perform database operations in an object-oriented fashion
ActiveRecord is the Model in MVC. In other words it is the layer in the system responsible for representing business data and logic. We require it in our project by adding the gem activerecord
ActiveRecord methods will write the SQL for us, and once we've connected our database, we can pull any associated model data from it easily. Before we do that, let's set up our project to use ActiveRecord and configure which database we'll talk to:
Artist.all
Artist.create(params[:artist])
Artist.find(params[:id])
As noted earlier, ActiveRecord is a gem and since we're building an app with a bunch of gems using Bundler, take a look at the Gemfile and don't forget to bundle install
!
####Setup
cd
into starter-code
and run bundle
.
Note: ensure the version of ruby being run is at least
ruby-2.2.2
.
In config/database.yml
, the name of the database is up to you, but <app_name>_development
is a good pattern to get into. A typical application would have three databases, the other two being <app_name>_production
and <app_name>_test
for production and test environments respectively.
Now that we're almost configured, let's make a class that uses all this fancy stuff. Under the models directory, note the file artist.rb
.
class Artist < ActiveRecord::Base
end
Our new model will inherit all the code from the ActiveRecord class, which has a bunch of handy methods already on it. For example Artist.create
will create a new Artist in the database or Artist.first
will grab the first one in the database, etc.
This is where Rake comes in.
Rake technically stands for "ruby make", which is a tool we're going to use to run predefined tasks for us. You can program your own rake tasks, but ActiveRecord comes with a bunch of preset ones. We can use these to set up our Postgres database. By including active record in our Rakefile, we get access to it's built-in rake tasks.
Here's a pretty comprehensive list of rake commands you can run on the database.
$ rake -T
<List of rake commands here>
If we did this using SQL:
$ psql
psql (9.4.1, server 9.3.5)
Type "help" for help.
username=# create database tunr;
CREATE DATABASE
Instead, let's use a rake task.
Note: Before you do this make sure your Postgres application/server is open and running.
rake db:create
Boom, database created.
Now that we have a database, we'll need to create some tables inside of it to store certain models we want to persist. Each table will have have columns that correspond to an attribute of one of our model instances.
"Migrations are a convenient way to alter your database schema over time in a consistent and easy way. They use a Ruby DSL [Domain Specific Language] so that you don't have to write SQL by hand, allowing your schema and changes to be database independent. You can think of each migration as being a new 'version' of the database." Source
Migrations are instructions to change the database's architecture. They can be automatically generated with active record. Migrations can always be rerun to recreate a consistent database state.
So let's build a new version of our database that has an artists table:
Note: we're using the gem
standalone_migrations
to do this as we're doing it outside a Rails project.
rake db:create_migration NAME=create_artists
A file is generated in db/migrate/
. The string of numbers at the beginning is a Unix timestamp, while the remainder is what you just named it.
Now let's open the generated file and put the finishing touches on our table:
class CreateArtists < ActiveRecord::Migration
def change
end
end
Let's add some ruby code which will add columns to our table in order to allow for our model to have specific attributes. Let's say we want each artist to have a name, photo_url, and nationality.
class CreateArtists < ActiveRecord::Migration
def change
create_table :artists do |t|
t.string :name #add a name attribute of type string to the table
t.string :photo_url #also add a photo_url attribute of type string
t.string :nationality # finally add a nationality attribute of type string
t.timestamps #this will add timestamps for time created and time updated automagically!
end
end
end
Run the migration with rake db:migrate
. That'll run any migrations that haven't been run yet and make the appropriate changes to the database.
== 20150710152405 CreateArtistsTable: migrating ===============================
== 20150710152405 CreateArtistsTable: migrated (0.0000s) ======================
###Sacred Cows
And we have a table! Nice work! And now you've got a schema.rb
file that was generated for you—this file is sacred. Not to be touched, only to be admired. It's a snapshot of the current state of your database, and rake is the only one who should be modifying it, ever. Similarly, never change a migration file after it has been run. Repeat after me: "I shall never change a migration file after it has been run." Again! You can get a quick view of which files have been run by entering rake db:migrate:status
. The files that have been run have a status of up
, while those that have not have a status of down
. Your file should have an up
status now.
Peep your schema.rb
to see what your database looks like!
ActiveRecord::Schema.define(version: 20150710152405) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "artists", force: :cascade do |t|
t.string "name"
t.string "photo_url"
t.string "nationality"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Success, now let's change it again!
Just like we can write migrations to create tables, we can write migrations to add, change or delete attributes, update data types, change table names, and even delete tables. The entire purpose of migrations are to make architectural changes to the database.
We have also decided we want to collect data about the instruments the artists play, so we need to add a column to house that data. Let's create another migration:
rake db:create_migration NAME=add_instrument_to_artists
db/migrate/20150710154423_add_instrument_to_artists.rb
In the new migration:
class AddInstrumentToArtists < ActiveRecord::Migration
def change
add_column :artists, :instruments, :string
end
end
You can probably guess what this line - add_column :artists, :instruments, :string
—says: "add a column to the artists table called 'instruments' of type string". Run rake db:migrate:status
to see two migration files, one up
and one down
. Once rake db:migrate
is run, both will be down
, and a new column will exist.
Make a migration every time a change to the database is needed—whether it's adding or removing something! Changing the database in any way means making a new migration, and telling the computer what you need done. Think of it as an assistant; you don't do the tedious Postgres work, you just tell it what needs doing and tell it to go do it. If you make changes in other ways to your local database, a team member who has cloned the same project will lose state with you. Running migrations of your other team members is how both of your separate local databases stay in state!
By now, you've seen the pattern:
- create a migration
- add the appropriate code to the migration
- run the migration.
Same pattern applies for each time you want to modify your database. For example in the case of a updating a column, you would write something along the lines of:
rake db:create_migration NAME=change_column_in_artists_to_new_column_name
Note the NAME
doesn't matter, but it's a good idea to be descriptive.
In the migration add:
def change
rename_column :table, :old_column_name, :new_column_name
end
Then:
rake db:migrate
According to the official ActiveRecord docs, these are all the migration methods that can be run inside change
:
- add_column
- add_index
- add_reference
- add_timestamps
- add_foreign_key
- create_table
- create_join_table
- drop_table (must supply a block)
- drop_join_table (must supply a block)
- remove_timestamps
- rename_column
- rename_index
- remove_reference
- rename_table
As always, if you can't remember the exact syntax, reference the rails guides!
##Playing with our Data
Run app.rb
to enter into a pry session.
###Creating
>> david_bowie = Artist.new
>> david_bowie.name = "David Bowie"
>> david_bowie.nationality = "British"
>> david_bowie.save
Here's .create
, which does the same thing as .new
and .save
, but just in one step...
>> Artist.create({name: "Drake", nationality: "Canadian"})
Note: How does one see what SQL code has been generated by these commands?
###Reading
Now we can see how many artists we have Artist.count
and see them all with Artist.all
###Updating
First we must find the artist to update and then change an attribute.
>> drake = Artist.find_by_name("Drake")
>> drake.nationality = "Canadian, aye!"
>> drake.save
###Deleting
We will now delete David Bowie, a moment of silence please...
It is best practice to use an
id
for finding an entry in the table. Assuming David Bowie is number1
(which he is)...
>> david = Artist.find(1)
>> david.destroy
(´;︵;`)
###More CRUD actions
For a comprehensive set of all the CRUD actions ActiveRecord can perform checkout out the CRUD section of Active Record Basics on Ruby Guides!
This a recommended pair programming activity.
In seperate migration files:
1) Add a column "groupie"
2) Rename the column "groupie" to "significant_other"
3) Delete the column "significant_other"
Now, run the seeds.rb file with the command rake db:seed
. Then, in app.rb write code to do the following:
1) Find all artists
2) Find the last artist
3) Find the artist with the name "Enya"
4) Find all artists who are American
5) Create the artist "Puff Daddy"
6) Change his name to "Diddy"
7) Destroy "Diddy"
- What is ActiveRecord and how does it interact with your database?
- What are migrations?
- Why should you never touch
schema.rb
- Briefly, describe how to configure your Sinatra app to use ActiveRecord Models with your database.
- ActiveRecord Migrations Official Docs
- ActiveRecord data types:
- :boolean
- :datetime
- :decimal
- :float
- :integer
- :references
- :string
- :text
- :timestamp
- Bonus: ActiveRecord associations