You probably remember how to associate tables in a database using foreign keys. This assignment will build on that knowledge and extend it into the realm of the ActiveRecord ORM. We'll also use database migrations to modify our database over time.
- SQLite database
- Database migrations
- One-to-many associations
- Many-to-many associations
- Unit testing models with RSpec
- Rake commands
- Fork this repository.
- Clone your fork of this repository into your working directory.
- Install the necessary dependencies using Bundler:
bundle
- Create your database:
rake db:create
Note: There are bonuses for several questions. Come back to these after you have completed the entire activity.
Migrations are ActiveRecord's way for allowing us to modify the database structure using ruby code.
Instead of creating a table manually using a SQL query like this...
CREATE TABLE employees (
id INT NOT NULL,
first_name VARCHAR(255),
last_name VARCHAR(255),
address VARCHAR(255),
PRIMARY KEY (id)
);
...we can use an ActiveRecord migration class to define the table.
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.string :first_name
t.string :last_name
t.string :address
end
end
end
TIP: Do not confuse ActiveRecord migrations with ActiveRecord models. ActiveRecord migrations manage the structure of the tables: add_column :students, :address, :string
. ActiveRecord models manage the data within tables: Student.find(1)
or Student.where(firstname: 'Anne')
.
Read about migrations here (to understand them, don't worry about memorizing the different approaches, etc.):
http://guides.rubyonrails.org/migrations.html
There is a Student model already. But the database does not yet have the students
table. In fact you don't even have a development database to work against yet.
We need to create an empty database and then update the schema (structure of the database) to include a students
table. We will use migrations for this.
Run this rake command to create the database file: rake db:create
How does that work? What does it do? Hint: take a look at the Rakefile
Modify the CreateStudents migration in db/migrate/20140319144238_create_students.rb
and implement the change
method. To find out what columns we need in the students table, look at the student data in db/data/students.csv
.
Once you implement it, run rake db:migrate
to run the migration against the database.
We wrote a test case that checks if you created the students table: spec/migrate_create_table_spec.rb
.
To set up the database for the test environment run:
rake db:create DATABASE=test
rake db:migrate DATABASE=test
Append DATABASE=test
to all rake commands when you want to make those rake commands apply to the test database.
Run this rspec
test case to check your migration worked. Remember to prefix your commands with bundle exec
which tells Ruby to use the correct gem versions from the Gemfile
. For example:
bundle exec rspec spec/migrate_create_table_spec.rb
Take a look at the spec file to see what fields it's expecting. How is it working?
If any of the tests fail, you may need to rollback your migration. Use rake db:rollback DATABASE=test
to reverse the migration against the test database. Then you can fix the migration file and run it again with rake db:migrate DATABASE=test
. Run the tests again to see if your new migration fixed the errors.
To debug/inspect the database, it would be nice to interactively just use ActiveRecord within something like pry
.
In this project the app_config.rb
file does all the following "loading" work:
- Requires a gems like
active_record
. - Requires our model classes.
- Exposes a method for connecting to the database.
So we can just require that one file when we start pry. Give it a shot from the command line:
pry -r './app_config.rb'
Now you should be able to interactively inspect the database using AR.
pry(main)> AppConfig.establish_connection
pry(main)> Student.first
pry(main)> Student.count
For now, we don't have any data in the database, so Student.first
will return nil
and Student.count
will return 0
.
Suggestion: Use a dedicated tab in your Terminal to keep a pry session open. Note that if you make changes to your database or code, you'll want to restart the pry session.
Once the students table is built and the spec/migrate_create_table_spec.rb
test passes, let's import the sample student records from a CSV file (for convenience really):
rake db:populate
TIP: Read the rake command and the CSV importer. Do you understand what is going on? If not, discuss amongst yourselves and ask a TA for clarity.
Students need teachers. So let's introduce teachers to the app.
Create a Teacher
model that extends ActiveRecord::Base
. This model should be created in its own Ruby file and in the right folder (app/models
). Then you'll need to require it in app_config.rb
so that it's available to the other files in the project.
You'll also need to create (touch
) a new migration file in db/migrate
to create the teachers table.
The teachers migration file name must start with a bunch of numbers that represent the exact timestamp of when you created the migration. The students migration file is called 20140319144238_create_students.rb
. This means the migration was created on March 3rd, 2014 at 14:42:38. Precede the migration file you are about to create with a timestamp for the current date and time.
We want to store the following information about a teacher: name
, email
, address
, and phone
.
Without resorting to using SQL or SQLite, write some Ruby code that uses ActiveRecord commands to create some test data. Create 9 teachers, each with unique names and email addresses.
While you're at it, please ensure that no 2 teachers can share the same email address using an ActiveRecord validation.
The student data is imported using a class we wrote called StudentsImporter in lib/students_importer.rb
. To create the 9 teachers, create your own TeachersImporter class in lib/teachers_importer.rb
.
With your new TeachersImporter
, you'll need to require it in app_config.rb
and also edit the Rakefile
's db:populate
task to run it.
Write a test to be sure creating a Teacher works and the email validation works.
Implement a CSV-based mechanism for loading the sample teachers much like how students.csv
is a convenient way to "seed" the students table with sample data. You can use much of the same code as StudentsImporter
.
At this point you should have students and teachers in the database.
Let's say that we need each student to have a teacher, and each teacher to have many students. Does your Teacher model need to change? How about your Student model? Do you need a migration?
Make the necessary changes to your code to support this new constraint on the data.
Tip: You can read about one-to-many and many-to-many relationships here: http://guides.rubyonrails.org/association_basics.html
Tip: Once you are done, you should be able to do the following in your pry session:
pry(main)> first_student = Student.first
pry(main)> first_student.teacher = Teacher.first
pry(main)> first_student.save!
pry(main)> first_student.teacher
# => returns the student's teacher
pry(main)> first_teacher = Teacher.first
pry(main)> first_teacher.students
# => returns an array that contains first_student
Write tests to ensure that your association is working correctly. For example, given a student, can you find a teacher? Can you find all of her other students?
There is a student_spec.rb
file in this repo. Implement changes to the Student
class to make the tests pass.
RSpec Tip: the be_valid
method expects that calling .valid?
on the ActiveRecord object will return true.
Write a teacher_spec.rb
file to validate your Teacher model's validations.