If you go down the list on all of the tasks necessary to build out CRUD functionality in an application, it's quite extensive. From creating database tables, configuring views, and drawing individual routes, the feature builds can be time consuming and bug prone. Wouldn't it be nice if there was a more efficient way of integrating standard features instead of having to build them manually each time?
A primary goal of the Rails team was to make it efficient to build core application functionality. The Rails system has a number of generators that will do some of the manual work for us.
As nice as it is to use the generators to save time, they also provide some additional extra benefits:
They can set up some basic specs for an application's test suite. They won't write our complex logic tests for us, but they will provide some basic examples.
They are set up to work the same way each time. This helps standardize your code and enables your development to be more efficient since you don't have to worry as much about bugs related to spelling, syntax errors, or anything else that can occur when writing code manually.
They follow Rails best practices, which includes utilizing RESTful naming patterns, removing duplicate code, using partials and a number of other best of breed design patterns. (If you don't know what all of these are, don't worry -- we will cover them shortly.)
So if generators are so amazing, why wouldn't we always want to use them? I'll answer your question with another question: a chainsaw is a wonderful tool, so why wouldn't you use it for every job you have around the house? The answer to both questions is the same: great tools are only great tools if they are matched with the right task. In the same way that you should only use a chainsaw when you have a job that requires it, generators should only be used when they are needed.
Extending our example from above, why wouldn't you use a chainsaw to build a model airplane?
In the same manner as our chainsaw example, certain generators create quite a bit of code. If that code is not going to be used, it will needlessly clutter the application code and cause confusion for future developers. One of our instructors recounts the following all-too-familiar anecdote:
A few years ago I took over as the lead developer for a large legacy Rails application. The previous developer had relied on generators, even when they shouldn't have been used, and the end result was that it took months to simply figure out what code was being used and what was 'garbage' code that simply came from the generators.
So when is the right time to use a generator? After we've gone over the actions of each of the generators, the answer to this query should become readily apparent. In addition, we'll walk through some case studies to help understand when each type of generator is beneficial.
All of the Rails generators are entered as commands into the terminal and will follow this syntax:
rails generate <name of generator> <options> --no-test-framework
--no-test-framework is a flag that tells the generator not to create any tests for the newly-generated models, controllers, etc. When you're working on your own Rails applications, you don't need the flag — it's quite helpful for quickly stubbing out a test suite. However, it's necessary for labs because we don't want Rails adding additional tests on top of the test suite that already comes with the lesson.
For efficiency's sake, Rails aliased the
generate method to
g, so the CLI command above could be shortened to:
rails g <name of generator> <options> --no-test-framework
Below are the main generators that Rails offers. We'll go through examples of each:
Up to this point, we've been creating our migrations by hand. This has been beneficial because it's important to understand how migrations work. However, Rails has a great set of migration generators with conventions that can help make managing the database schema very efficient.
Let's start using database migrations in our case study application and update the
posts table. To add a new column called
published_status, we can use the following command:
rails g migration add_published_status_to_posts published_status:string --no-test-framework
In the terminal you will see it creates a migration file for us:
db/migrate/20151127174031_add_published_status_to_posts.rb. Since migration file names need to be unique, the generator prepends a timestamp before the file name. In the case of the migration I just ran, it added
20151127174031. You can break this timestamp down as follows:
year: 2015, month: 11, date: 27, and then the time itself.
Ready to see something pretty cool? Open up the file it created, which you can find in the
db/migrate directory. It should look something like this:
class AddPublishedStatusToPosts < ActiveRecord::Migration def change add_column :posts, :published_status, :string end end
Notice what the generator did? It automatically knew that we wanted to add a new column and built out the
add_column method call. How did this happen? It turns out that the way that you name the migration file is very important. By prepending the
add_ text to the name it gave a signal to the migration generator that the purpose of this schema change will be to add a column(s) to the table. How did it know the table we wanted to add to? By appending the
_posts text to the end of the migration name it tells Rails that the table we want to change is the
posts table. Lastly, by adding the
published_status:string text at the end of the command tells the generator that the new column name will be
published_status and the data will be of type
To update the database schema you can run
rake db:migrate and the schema will reflect the change.
Oh no, we made a mistake, let's get rid of that column name with another migration:
rails g migration remove_published_status_from_posts published_status:string --no-test-framework
If you open up this migration file, you will see the following code:
class RemovePublishedStatusFromPosts < ActiveRecord::Migration def change remove_column :posts, :published_status, :string end end
So we can add and remove columns automatically by running migration generators. What else can we do? Let's walk through a real world scenario:
rails g migration add_post_status_to_posts post_status:boolean --no-test-framework
With this migration we'll add the column
post_status with the data type of boolean. While adding this new attribute to one of the forms we discover that the column really needs to be of type
string instead of being a
boolean. Let's see if we can use the same syntax for the generator:
rails g migration change_post_status_data_type_to_posts post_status:string --no-test-framework
This won't automatically create the
change_column method; the file will look something like this:
class ChangePostStatusDataTypeToPosts < ActiveRecord::Migration def change end end
We can simply add in the
change_column method like this:
change_column :posts, :post_status, :string and after running
rake db:migrate our schema will be updated.
This is a generator type that gets used regularly. It does a great job of creating the core code needed to create a model and associated database table without adding a lot of bloat to the application. Let's add a new model to the app called
Author with columns
genre, we can use the model generator with the following CLI command:
rails g model Author name:string genre:string bio:text --no-test-framework
Running this generator will create the following files for us:
invoke active_record create db/migrate/20190618010724_create_authors.rb create app/models/application_record.rb create app/models/author.rb
At a high level, this has created:
ApplicationRecord(as of Rails 5)
Note: Up to Rails 4.2, all models inherited from
ActiveRecord::Base. Since Rails 5, all models inherit from
ApplicationRecord. If you've used an older version of Rails in the past, you may be wondering what happened to
ActiveRecord::Base? Well, not a lot has changed, actually. This file is automatically added to models in Rails 5 applications:
# app/models/application_record.rb class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end
It allows more flexibility if you want to add some extra functionality to Active Record.
To continue with the code-along, let's run
rake db:migrate, which will add the authors table to the database schema. We can test this out in the console:
Author.all => #<ActiveRecord::Relation > Author.create!(name: "Stephen King", genre: "Horror", bio: "Bio details go here") => #<Author id: 1, name: "Stephen King", genre: "Horror", bio: "Bio details go here", created_at: "2015-11-27 22:59:14", updated_at: "2015-11-27 22:59:14">
So it looks like our model has been created properly. As you can see, this particular generator created a few different pieces of functionality with a single command, and it did it with minimal code bloat.
Controller generators are great if you are creating static views or non-CRUD related features (we'll walk through why this is the case shortly). Let's create an
admin controller that will manage the data flow and view rendering for our admin dashboard pages:
rails g controller admin dashboard stats financials settings --no-test-framework
This will create a ton of code! Below is the full list:
So what got added here? Below is a list that is a little more high level:
A controller file that will inherit from
A set of routes to each of the generator arguments:
A new directory for all of the view templates along with a view template file for each of the controller actions that we declared in the generator command
A view helper method file
scss file for the styles for the controller
As you can see, this one generator created a large number of files and code. This is a generator to be careful with – it can create a number of files that are never used and can cause wasted files in an application.
So why are controller generators not the best for creating CRUD based features? What would have happened if we wanted to create a controller that managed the CRUD flow for managing accounts? Here would be one implementation:
rails g controller accounts new create edit update destroy index show --no-test-framework
Immediately you may notice that this would create wasted code since it would create view templates for
destroy actions, so they would need to be removed immediately. They would also be set up with
get HTTP requests, which would not work at all. In the next section we're going to cover a better option for creating CRUD functionality.
If you are building an API, using a front end MVC framework, or simply want to manually create your views, the
resource generator is a great option for creating the code. Since we didn't create the
Account controller we mentioned before, let's build it here:
rails g resource Account name:string payment_status:string --no-test-framework
This creates quite a bit of code for us. Below is the full list:
So what does our app have now due to the generator? Below is a summary:
A migration file that will create a new database table for the attributes passed to it in the generator
A model file that inherits from
ApplicationRecord (as of Rails 5; see Note above)
A controller file that inherits from
A view directory, but no view template files
A view helper file
scss file for the styles for the controller
resources call in the
resource generator is a smart generator that creates some of the core functionality needed for a full featured resource without much code bloat. Looking over the files I can't find one file that I need to remove, so that's a good sign.
The last item that was added may not look familiar to you.
resources :accounts is considered a 'magic' route that entails the full set of RESTful routes needed to perform CRUD in an application. So what does
resources :accounts translate into?
There's an easy way to find out. Let's run
rake routes with a filter so it only shows us the routes for accounts:
rake routes | grep account
rake command will produce the following output in the console:
accounts GET /accounts(.:format) accounts#index POST /accounts(.:format) accounts#create new_account GET /accounts/new(.:format) accounts#new edit_account GET /accounts/:id/edit(.:format) accounts#edit account GET /accounts/:id(.:format) accounts#show PATCH /accounts/:id(.:format) accounts#update PUT /accounts/:id(.:format) accounts#update DELETE /accounts/:id(.:format) accounts#destroy
resources automatically creates each of these routes and makes them available to the controller. If you open up the
accounts_controller.rb file you may notice something interesting: none of the actions shown in the route list are even there! However, I actually like this because it creates the minimal amount of code possible and then lets me add only the features that the app needs. We'll get into a full review of each of the options available with the
resources method in a later lesson. For right now just know that by default it creates the full suite of CRUD routes.