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:
- Migrations
- Models
- Controllers
- Resources
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
string
.
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:
class ChangePostStatusDataTypeToPosts < ActiveRecord::Migration
def change
change_column :posts, :post_status, :string
end
end
Then 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 name
, bio
, and 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:
- A database migration that will add a table and add the columns
name
,genre
, andbio
. - A model file that will inherit from
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:
create app/controllers/admin_controller.rb
route get 'admin/settings'
route get 'admin/financials'
route get 'admin/stats'
route get 'admin/dashboard'
invoke erb
create app/views/admin
create app/views/admin/dashboard.html.erb
create app/views/admin/stats.html.erb
create app/views/admin/financials.html.erb
create app/views/admin/settings.html.erb
invoke helper
create app/helpers/admin_helper.rb
invoke assets
invoke coffee
create app/assets/javascripts/admin.js.coffee
invoke scss
create app/assets/stylesheets/admin.css.scss
So what got added here? Below is a list that is a little more high level:
- A controller file that will inherit from
ApplicationController
- A set of routes to each of the generator arguments:
dashboard
,stats
,financials
, andsettings
- 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
- A Coffeescript file for specific JavaScripts for that controller
- An
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 create
, update
, and 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:
invoke active_record
create db/migrate/20170712011124_create_accounts.rb
create app/models/account.rb
invoke controller
create app/controllers/accounts_controller.rb
invoke erb
create app/views/accounts
invoke helper
create app/helpers/accounts_helper.rb
invoke assets
invoke coffee
create app/assets/javascripts/accounts.js.coffee
invoke scss
create app/assets/stylesheets/accounts.css.scss
invoke resource_route
route resources :accounts
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
ApplicationController
- A view directory, but no view template files
- A view helper file
- A Coffeescript file for specific JavaScripts for that controller
- A
scss
file for the styles for the controller - A full
resources
call in theroutes.rb
file
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
This 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.