Useful tips for beginners working with Active Record and seed data
Introduction
At the beginning of our journey learning Ruby and building simple CLI applications and Sinatra-based webpages where we want to experiment with performing CRUD actions (i.e. Create, Read, Update and Delete), we’ll most certainly need to use Active Record to handle these CRUD actions and also actually hold data in our database to work with.
This blogpost is aimed at beginner Ruby programmers at the early stages of their journey to building web apps. The article will shed light on some useful points to bear in mind when starting to work with Active Record and sample data (i.e. seed data).
What is Active Record?
If you’re not familiar with Active Record, let’s spend a brief moment to explain what its purpose is. In Ruby, we work with classes and objects because Ruby as language follows the object-oriented paradigm. Although it would have probably been nice working with some object-oriented database in conjunction with our object-oriented Ruby classes, in most cases what’s available to us is a relational database.
Storing object data in relational form and make the data persist in a relational database requires a ‘bridge’ between the object-oriented and relational realms. This is where Active Record comes into play. Active Record is an Object Relational Mapper (ORM) that take cares of the process for allowing Ruby object data to persist in a relational database (e.g. SQLite3) and we’re then able to perform the CRUD actions over the stored data as needed. The diagram below illustrates the whole idea behind Object Relational Mapping.
Active Record Migrations
Reduce manual intervention when creating migration files
Migrations provide an ordered way of creating and modifying database tables with a focus on maintaining the ability to audit the changes made to a database. While it’s possible to manually create migration files, doing so requires remembering the distinct order of the migrations, prepending filenames with, for example, 01_, 02_, 03_, etc., and being very cautious about sticking to a numbering convention. This can get pretty tedious if we have a number of migrations to maintain!
To quicken the process of creating migrations, we can use the Rake gem, install it and specify it in our Gemfile. Then in a Terminal window we can simply run the following with the appropriate filename:
rake db:create_migration NAME=create_artists
This creates the migration file whose name is prepended with a unique timestamp, meaning every time a similar command is run for other migration files, they will follow the correct order based on the automatically-generated timestamps.
Label A in the figure below illustrates the result of manually creating Active Record migration files, while Label B captures the result of using the rake db:create_migration approach.
Keep a separate migration file for each database table
While it can be tempting to create single migration files for handling multiple database table migrations, this is regarded as poor practice for various reasons. For example, dealing with multiple table migrations in a single migration file makes it harder to troubleshoot should we need to amend a specific table at a later stage.
Also, from a database management perspective it’s best to work with individual table migrations in individual migration files. Like that, each migration file has a distinct purpose and can directly be linked to a class model in our domain. This is much better from a code organisation perspective as things are kept more modular, maintainable and auditable. To run our migrations, we can execute the following line in a Terminal window:
rake db:migrate
Keep an audit of the objects being created and updated
When a migration file is created using rake db:create_migration, the content of the file initially contains a basic skeleton, which we can then complete as per our domain and class model requirements. In the example below, we have defined a name column of type string in our table called songs. We have the columns artist_id and genre_id which are foreign keys allowing us to reference the primary keys from the artists and genres tables and, additionally, we also have a timestamps property specified.
It’s always recommended to add the timestamps property to any table. Doing so provides us with a means of auditing the creation and update of records in the database. This also provides us with two useful methods (create_at and updated_at), which we are able to call on class objects to audit them and investigate any issues during the population of our database records.
Use the full power of Active Record associations
There’s definitely more to associations in Active Record than the commonly encountered belongs_to, has_many and has_many :through relations. In fact, Active Record gives us the ability to work with six different types of relations listed below.
belongs_to
has_one
has_many
has_many :through
has_one :through
has_and_belongs_to_many
Therefore, what’s important from a domain modelling perspective is the whole idea of carefully planning what the conceptual model looks like and how the classes in a domain can logically be related through the appropriate selection of Active Record associations.
Seeding a database
With successful Active Record migrations executed, it’s then possible to load our database with dummy or sample data in order to test that we have the correct behaviour. This process of loading the dummy data is what’s known as ‘seeding a database’.
Let Ruby and Faker generate most of the dummy data you need
Tired of typing up, copying, pasting and modifying your seed data line by line? There are some pretty handy techniques we can utilise in Ruby in order to quicken the process of populating dummy data.
The code below is an example of how we can automate the creation of objects. Here we are instructing Ruby to create 30 distinct artist objects where each is to have a unique name. Instead of taking time to think about 30 distinct names and type each one down, we can use Faker to do the job for us. Faker is a Ruby gem and by including it in our Gemfile for a Ruby project, we can use Faker’s rich generators to create dummy data for us. In this case, we’re using Faker to populate unique names for 30 different artist objects.
30.times do |count|
Artist.create(name: Faker::Name.unique.name)
end
Once the logic for creating the dummy data has been set up, go ahead and seed your database by running:
rake db:seed
Perform quick checks over the seed data
After we’ve seeded our database, we’d want to run some quick checks to ensure your data has been properly populated in the database. Start the Rake Console by typing the following in the Terminal window:
rake console
Then, call the Active Record all method on a relevant class to see a list of all the instances of that class. For example, entering the line below would list all the instances of the class Artist.
Artist.all
Or, say, we wanted to find out how many artist objects were created, we could simply run the following:
Artist.all.size
There are other Active Record methods we can use for quickly checking the status of the seed data. For example, running the below would list the very first instance of the Song class that was created, regardless of what the database id for the record might be.
Song.first
Or, if we wanted to see the last instance of the Song class, we could simply execute:
Song.last
Clean up seed data every time a database is seeded
Another nice practice when thinking about seed data is to clean up our databases every time we need to seed it. This is only applicable to development and test environments, as we wouldn’t want to wipe any production database clean. We’d get fired for that!
What we mean here by ‘cleaning up’ is basically the process of deleting any pre-existing data when seeding our database. This process ensures that there is no conflicting prior data when we perform our quick checks over the seed data. Cleaning up seed data at the point of seeding a database in pretty straightforward. At the top of a seed file, we can call the destroy_all method on our classes and this should remove all current data.
Artist.destroy_allGenre.destroy_allSong.destroy_all