pwshub.com

Creating Powerful Rails Project Templates — Streamlining Your Rails Development Journey with Templates

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.


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.

GemFile Example

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.

Indentation Example

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:

  1. Don’t use the template and set up the project resources manually;
  2. Duplicate and edit the template file;
  3. 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..

Menu Interface

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.  

Report Example

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!

Source: blog.codeminer42.com

Related stories
2 weeks ago - A simple yet powerful approach to Web Component server-rendering, declarative behaviors, and JavaScript islands.
3 weeks ago - A Windows VPS is a web hosting environment to host websites, apps, databases, and other services (media streaming, file storage, etc.) with Windows Servers or desktop clients (ex., Windows 11). Windows hosting also offers the simplest way...
3 weeks ago - Learn about the top Linux commands with our guide, complete with explanation, examples, and a handy cheat sheet
1 month ago - As a frontend developer, you most likely have heard the house analogy used to describe HTML, CSS, and JavaScript. HTML […] The post Creating 3D effects in CSS appeared first on LogRocket Blog.
1 day ago - Toast notifications are messages that appear on the screen to provide feedback to users. When users interact with the user […] The post Creating toast notifications using Solid Toast appeared first on LogRocket Blog.
Other stories
8 hours ago - Looking for a powerful new Linux laptop? The new KDE Slimbook VI may very well appeal. Unveiled at Akademy 2024, KDE’s annual community get-together, the KDE Slimbook VI marks a major refresh from earlier models in the KDE Slimbook line....
12 hours ago - Fixes 130 bugs (addressing 250 👍). `bun pm pack`, faster `node:zlib`. Static routes in Bun.serve(). ReadableStream support in response.clone() & request.clone(). Per-request timeouts. Cancel method in ReadableStream is called. `bun run`...
1 day ago - Have you ever used an attribute in HTML without fully understanding its purpose? You're not alone! Over time, I've dug into the meaning behind many HTML attributes, especially those that are crucial for accessibility. In this in-depth...
1 day ago - Lifetimes are fundamental mechanisms in Rust. There's a very high chance you'll need to work with lifetimes in any Rust project that has any sort of complexity. Even though they are important to Rust projects, lifetimes can be quite...
1 day ago - The first interaction sets the tone for the entire experience — get it right, and you’ve hooked your users from the start. So as a UX designer, you need to know how to put the primacy effect of UX design to good use. The post Leveraging...