In this article, we’ll dive into how the rails config file can be helpful to streamline your rails project templates, especially, if you need to set up many rails projects as I needed as a trainee developer and in my study routine.
So, if you clicked on this post, you’re probably a developer, and as a developer, you probably hear about some environment configurations that can improve your coding journey.
One of the most common configurations we can do starts on the developer’s most beloved tool, the terminal. So, maybe you’re already familiar with .bashrc
, .zshrc
, or some other rc
file… but have you heard about .railsrc
?
The Rails Config File
.railsrc
is a configuration file used in Ruby on Rails development. This file allows you to pre-configure certain options for your rails applications. These options can be found when you run rails -h
on your terminal console. This command will print all available flags that your current rails version supports.
That rails is a robust framework we already know, but when we aren’t familiar with it, it’s common that we often create a new project with tons of resources that won’t be used. In fact, if you’re a new rails developer, you probably don’t even know what these resources do. A good starting point for improving your programming skills is to remove unnecessary features and add them on demand. By doing this, you will keep your project folder organized and clean. To exemplify this, have you ever seen a Rails project that uses Rspec as a test suite but still has the test
folder forgotten? Things like that don’t smell good…
As you discove and study the framework, you start to understand more about what each flag does. For example, --database=postgresql
changes the default rails database schema from sqlite3 to postgresql, setting up the environment of your project to work with postgres. Another example is the --css=tailwind
flag, which, as you guessed, sets up the tailwind to the project in the easiest way.
At this point, you can ask yourself, why should I spend some time setting up my config file if I can just write these statements and start coding? And to be honest, maybe your time isn’t worth doing it. I agree with you, it’s not every day that we need to start a new rails project.
But, let’s assume that you’re studying the framework and are expanding your project’s portfolio. For first projects, you start with almost the default configuration. As your application grows, you add gems, env variables, custom files, and any other resources that make sense for your environment and study cases.
After a few projects you achieve your accomplishments, it’s getting boring to set up everything again for the next project, and that is why the .railsrc
can be useful to you. To speak truly, the rails config is just the starting point of automating project configurations.
Let’s code it to never start from scratch again.
Rails Project Templates
When creating a new rails project, one of the rails flags available is the -m
or --template
. This flag allows you to choose a template to apply to your project.
In this context, the rails template isn’t a partial or a layout. It’s a generator that you can set as needed to speed up the new project setup. Instead of wasting your time copying and pasting what you have done on other projects every time, consider doing it just once, writing a good template that serves your purposes.
Before we start to dive into coding, I recommend you to learn just a little bit about Thor, a powerful ruby tool that allows you to create custom tasks to automate processes like the creation of a model, controller, and migration. This CLI tool is strongly recommended by the community and is used by rails framework.
Right. Now that you have understood the problem that we want to solve, and know the tools that we use to solve it, let’s code!
Hands-on
Before we start working on our template, let’s create the ~/.railsrc
. In this file, you can set the flags as you prefer. The syntax is exactly the same as the command line.
In this case, we just want to load a custom template. This way, every time we run rails new project
the template will be loaded by default, without needing to specify the longest command rails new project --template='template.rb'
.
# ~/.railsrc
--template='~/.rails/template.rb'
The next step is to create the folder and the template.rb
file. To do it, on your terminal run:
mkdir ~/.rails
touch ~/.rails/template.rb
These commands will create a hidden folder on your root directory and the template.rb
file. Feel free to use your favorite text/code editor to edit it.
Now, things start to get more interesting. To compose our template, the Rails Guides Docs provide some helper methods and a few examples of how to implement it. In addition, I recommend you to check the Thor Docs and the Generators Docs to discover more about what you can do.
NOTE: In this article, we are going to cover just the Application Templates, this works the same way as a rails generator, but instead of writing ruby classes, we write it as a ruby script.
Composing the Template
Let’s assume that our template will add some useful gems like faker, factory_bot, rspec, simplecov and pry-rails. So, following the docs, write the code below on your template.rb
file.
# '~/.rails/template.rb
gem 'faker'
gem 'factory_bot_rails'
gem 'pry-rails'
gem 'rspec-rails'
gem 'simplecov'
Quite simple, don’t you agree? … But, this script is incomplete. RSpec and SimpleCov require a few more adjustments to be set up correctly to the project. After adding rspec, we need to run rails g rspec:install
to generates the rspec files, and for simplecov, we need to add a few instructions into spec/rails_helper.rb
to make it work correctly. To do it, append the following code in template.rb
# ~/.rails/template.rb
# ...
generate 'rspec:install'
insert_into_file 'spec/rails_helper.rb', before: "# This file is copied to spec/ when you run 'rails generate rspec:install'\n" do
<<-RUBY
require 'simplecov'
SimpleCov.start
RUBY
end
That’s it. Now, when you run rails new project
, the .railsrc
file will load the template script, and the template will add the gems to the Gemfile, generate rspec installation, and finish the simplecov configuration requirements.
Right, everything’s working, so now you just relax, right? … hmm… well, not yet. Things can become more complex than this.
Reviewing the code
After a quick review, I must share three negative things about that script, the location where the code will be inserted, the indentation, and the difficulty of customizing your resource installations. Let’s dig into them.
Code placement
The first point to observe is that the gems that we specified were just appended to the bottom of Gemfile without following any order or validation, even if you use the gem_group
as the example below, expecting that the code will be in the right place, the code won’t be.
# ~/.rails/template.rb
gem_group :development, :test do
gem "rspec-rails"
end
The problem is that the gems were added to all environments (development, test, and production), and, when we define a gem_group
, the group itself is duplicated, which is considered a bad practice.
Indentation
Another point you have to consider is the indentation of your script code and the code generated by it. The script code appears to be indented correctly, but when you look at spec/rails_helper.rb
, you will notice that the simplecov instructions aren’t (lines 9, 10). This can be very frustrating because you need to fix it manually or break the script indentation. In both cases, none is recommended.
Although the code is working, it’s badly indented. Soon I will show you how to deal with it. For now, imagine the same situation if you’re trying to write a YML file, like a database.yml
. It’s certain that you will have some headaches, and will spend a lot of time trying to fix the script, creating a new project, and validating if the code is in the expected place.
Customizing the Installation
The next point to consider is to assume that, for the next project that you’re creating, you won’t use RSpec. At this point, you have three options:
- Don’t use the template and set up the project resources manually;
- Duplicate and edit the template file;
- Add some logic to the script.
And, as good developers, of course we are going to choose the last one.
Let’s try to keep things as simple as possible for now. Following the devise example, we can just ask the user if he or she wants to use the rspec and adjust the logic to do the appropriate configuration.
Let’s update the code…
# ~/.rails/template.rb
# ...
use_rspec = yes?('Do you like to install Rspec?')
if use_rspec
gem_group :development, :test do
gem "rspec-rails"
end
generate 'rspec:install'
end
simplecov_config = <<-RUBY
require 'simplecov'
SimpleCov.start
RUBY
if use_rspec
insert_into_file 'spec/rails_helper.rb', simplecov_config,
before: "# This file is copied to spec/ when you run 'rails generate rspec:install'\n"
else
insert_into_file 'test/test_helper.rb', simplecov_config, before: "module ActiveSupport"
end
As you can see, a simple choice can imply a more complex logic. In addition, you’re still having indent problems, and as the complexity increases, the linear logic can break the script easily.
For example, if you try to add simplecov before generating rspec installation files, the insert_into_file
will throw an error because the target file does not exist.
And now, the purpose of speeding up the setup of a new project has become painful due to all these problems we have to handle. So let’s see how to deal with them.
Improving the template
Before we continue, it’s valid to remember that this is a simple example, with just 5 gems, but in reality, you probably will handle more resources. In the trainee program I joined in Codeminer42, it was common that we needed setup resources like: devise, factory_bot, faker, ffaker, font_awesome, pry_rails, rspec, ruby_lsp, simplecov, shoulda_matchers, rswag, active_storage, action_text, sidekiq, i18n, rubocop, and others…
In this way, it only remains the choice to pass through the painful and lazy process to set it all up each time we need a new rails project, or to invest some time dealing with the template. To help you with it, at the end of this post, I will share with you my own template. However, to make it worth it, stay with me in the refactoring process.
Template Helper Module
This is what we’re going to do. First, we must create template_helper.rb
, which will serve as a module. In this file, we’ll specify all the gems and their installation instructions (if needed). This module allows isolating the template logic (what and how we want to set it up) from the installation steps (what we need to do to set it up properly).
On your terminal run:
touch ~/.rails/template_helper.rb
This will create the template_helper.rb
file. In that file, let’s specify a hash named RESOURCES
that will contain all the gems that you want to make available for your next rails project. Observe that we will use the gem name as the key of the hash, and the value, will be another hash, with the keys { required:, info:, order: }
.
required
, will be a boolean, used to enable the installation of the resource by default. :info
, will be used to describe what the resource does. You can set it as you want, but I recommend setting it with the link of the github repo. And :order
is an integer and optional, and will be used just when it is necessary to prioritize the installation order of a resource, such as RSpec, which, in case it is in the list of features to be installed, it must be configured first, due to the behavior of other gems, like simplecov and factory_bot.
# ~/.rails/template_helper.rb
module TemplateHelper
RESOURCES = {
:factory_bot => {
required: true,
info: 'https://github.com/thoughtbot/factory_bot_rails',
},
:faker => {
required: true,
info: 'https://github.com/faker-ruby/faker',
},
:pry_rails => {
required: true,
info: 'https://github.com/pry/pry',
},
:rspec => {
required: true,
info: 'https://github.com/rspec/rspec-rails',
order: 1
},
:simplecov => {
required: true,
info: 'https://github.com/simplecov-ruby/simplecov',
},
}
Handling the Indentation
To deal with the indent problem, and the place where the code will be inserted, we will create constants for each resource and its post-installation instructions (if needed).
Pay attention to two things. First, the constants must be a Proc. This happens because the constant will be evaluated as a &block
when used as an argument in template.rb
. The second thing is that our file does not have indentation itself, instead, we use .indent (num_of_spaces)
to configure it manually, this way our code is more readable and easier to edit. Just as it is possible to deal with the indentation problem, it is also possible to add empty lines and some comments, aiming for good formatting where the code will be inserted.
# ~/.rails/template_helper.rb
module TemplateHelper
# ...
FACTORY_BOT = Proc.new {
<<-RUBY.indent(2)
gem 'factory_bot_rails'
RUBY
}
FACTORY_BOT_HELPER = Proc.new {
<<-RUBY.indent(2)
# include FactoryBot methods to use in specs
config.include FactoryBot::Syntax::Methods
RUBY
}
FAKER = Proc.new {
<<-RUBY.indent(2)
gem 'faker'
RUBY
}
SIMPLECOV = Proc.new {
<<-RUBY.indent(2)
gem 'simplecov', require: false
RUBY
}
SIMPLECOV_CONFIG = Proc.new {
<<-RUBY.indent(0)
require 'simplecov'
SimpleCov.start
RUBY
}
PRY_RAILS = Proc.new {
<<-RUBY.indent(2)
# Use pry for debugging [https://github.com/pry/pry-rails]
gem 'pry-rails'
RUBY
}
RSPEC = Proc.new {
<<-RUBY.indent(2)
gem 'rspec-rails'
RUBY
}
RSPEC_URL_HELPERS = Proc.new {
<<-RUBY.indent(2)
# include url_helpers to use routes in specs
config.include Rails.application.routes.url_helpers, type: :request
RUBY
}
end
Making the script dynamic
Right, the first part is done. Now, we can update the script template logic. In template.rb
we will structure things differently. First, we will write a ‘menu’ that iterates over each RESOURCE
and let the user decide if he or she wants to use the default template based on :required
value of the resource. Otherwise, the user can choose the resource from the list available on the project.
After the user chooses, the script logic will iterate again over all the RESOURCES
– taking into consideration the :order
key (if it is set) that was set for the installation – and will invoke a method named with the same named key, to perform the configuration of the resource.
# ~/.rails/template.rb
require_relative 'template_helper'
def show_menu
if ask("Do you want to use template?", %i[blue bold], default: 'y', limited_to: %w[y n]) == 'y'
say('The default template includes:')
TemplateHelper::RESOURCES.each do |key, attr|
say("• #{key}", (attr[:required] ? :green : :red))
end
unless ask('Use default template?', %i[blue], default: 'y', limited_to: %w[y n]) == 'y'
TemplateHelper::RESOURCES.each do |key, attr|
if ask("• #{key.to_s.ljust(25)} #{attr[:info]}", %i[white], default: 'y', limited_to: %w[y n]) == 'y'
TemplateHelper::RESOURCES[key][:required] = true
else
TemplateHelper::RESOURCES[key][:required] = false
end
end
say('Your custom template includes:', %i[blue])
TemplateHelper::RESOURCES.each do |key, attr|
say("• #{key}", (attr[:required] ? :green : :red))
end
ask('Press any key to continue, or CTRL + C to cancel...', %i[blue bold])
end
TemplateHelper::RESOURCES.sort_by{|key, attr| [attr[:order] || Float::INFINITY, key]}.each do |key, attr|
if attr[:required]
say("\nSetting up #{key}...", :green)
send(key)
say("#{key} has been set up!\n", :green)
end
end
say('Template has been applied!', %i[green bold])
end
end
# ...
show_menu
Here are some points to observe in that part of the code. Instead of using yes?
we use ask
. This way, we can limit the available options, ensuring that the user input will be a pre-defined value, and also set a default answer for the question. We also set some text formatting to be applied to keep the interface more interactive, and if the user doesn’t want to use the default template, the resource :info
will be a hint to the user while he or she decides which resource will be installed..
The next step is to add the methods for each key. The method itself must be named with the same resource key.
# ~/.rails/template.rb
# ...
def factory_bot
insert_into_file 'Gemfile', TemplateHelper::FACTORY_BOT, after: "group :development, :test do\n"
insert_into_file 'spec/rails_helper.rb', TemplateHelper::FACTORY_BOT_HELPER,
after: "RSpec.configure do |config|\n" if(TemplateHelper::RESOURCES[:rspec][:required])
end
def faker
insert_into_file 'Gemfile', TemplateHelper::FAKER, after: "group :development, :test do\n"
end
def pry_rails
insert_into_file 'Gemfile', TemplateHelper::PRY_RAILS, after: "group :development do\n"
end
def rspec
insert_into_file 'Gemfile', TemplateHelper::RSPEC, after: "group :development, :test do\n"
generate 'rspec:install'
insert_into_file 'spec/rails_helper.rb', TemplateHelper::RSPEC_URL_HELPERS, after: "RSpec.configure do |config|\n"
remove_dir 'test'
end
def simplecov
insert_into_file 'Gemfile', TemplateHelper::SIMPLECOV, after: "group :test do\n"
if TemplateHelper::RESOURCES[:rspec][:required]
insert_into_file 'spec/rails_helper.rb', TemplateHelper::SIMPLECOV_CONFIG,
before: "# This file is copied to spec/ when you run 'rails generate rspec:install'\n"
else
insert_into_file 'test/test_helper.rb', TemplateHelper::SIMPLECOV_CONFIG, before: "module ActiveSupport"
end
end
show_menu
Fixing the code placement
The first thing to notice is that as we’re handling a script file, the show_menu
must be after all the methods to work correctly, and as before, you can notice that we’re using different methods to configure the project. Instead of using the gem
method, now we’re using insert_into_file
. This way, we can control where exactly the code will be inserted. In addition, you can also use append_to_file
if it makes more sense in some cases. Remember, the indentation of what will be inserted/appended is defined on the template_helper.rb
and now we no longer have to deal with wrong indentation.
Another advantage of structuring the code like this is that you can sort your resource keys and the methods in alphabetical order. It keeps the code more organized, once the :order
will be used to determine what will be set up first.
Finishing the setup
To finish this template, we can add an extra step: the after_bundle
block. Throughout your project setup, one of rails’ last steps is to run the bundle
command to install all project dependencies.
So we can use this block statement to perform some complementary settings, like the initial commit of the project and starting the application server.
So, let’s do it. Place the code below before the show_menu
.
# ~/.rails/template.rb
# ...
def git_init
say('Intializing git...', :green)
git :init
git add: "."
git commit: %Q{ -m 'Initial commit' }
say('Git has been initialized!', :green)
end
def start_server
after_bundle do
if ask("Do you wish to start the application server?", :blue, default: 'y', limited_to: %w[y n]) == 'y'
run './bin/dev'
end
end
end
# ...
show_code
To finish, append the code below to the bottom of show_menu
method.
# '~/.rails/template.rb
# ...
def show_menu
# ...
after_bundle do
git_init
start_server
end
end
# ...
Now, after running rails new project
your template will be interactive and will allow you to decide what resource should be used in the project that you’re creating. The best part is that you don’t have to worry about the flow of resources installation once each resource has its own method to do it.
Once your template is defined, you’ll never spend time again to set up a new rails project in the old way. But remember, in day-by-day projects, it’s common to use more than five gems, and there are a lot of gems that are considered almost essential to every rails project, independent of the kind of application that will be developed.
Extra
And as I promised, you can check this gist to find my own version of the template, with many resources already defined.
My template also has two extra features: the setup of a docker environment for Postgres Database (only available when flag --database=postgresql
is provided), and a custom report with all installed resources as in the image below. Of course, the report is optional and is generated after the installation process is over.
Thanks for reading. I hope you have enjoyed it and learned something new with this article! Stay tuned and consider subscribing to CodeMiner’s blog to receive more posts from our team.
We want to work with you. Check out our "What We Do" section!