#03Rails Application Templates

Subscribe to our episode notifications

* indicates required

Intuit Mailchimp

Introduction

This is not a Rails show, but from time to time we're going to be showcasing techniques or gems on a Rails project.

Creating a Rails project that looks relatively well and works out of the box is something that can take some time, specially if we're going to use it for just one or two shots in a video.

A great solution for this kinds of issues is to create a Rails Application Template.

A Rails Application template is just a ruby script file that follows a simple DSL allowing us to invoke different Rails related actions (as well as external shell commands) in order to perform specific actions to our newly created application without our involvement.

Creating our own template

The first step is to create a template file.

touch template.rb

The gem method

As I mentioned on a previous episode, I use AwesomePrint in most of my projects. So we can call the gem method to add a gem to our Gemfile. If you had other gems that you regularly include and use on your projects, they could be added here as well

gem "awesome_print"

We can even create gem groups.

gem_group :development do
  gem "rubocop"
end

gem_group :development, :test do
  gem "rspec-rails"
end

Using a template

Lets try out to see how we're going so far.

To create a new Rails application using our template, we can pass to the rails new command the -m flag with the path to our template.

rails new blog -m ./template.rb

Note that if we publish this template somewhere over the internet, we can use the file url instead of the local path.

rails new blog -m http://example.com/template.rb

But lets go back to trying it out locally.

$ rails new blog -m ./template.rb
Using --skip-bundle --skip-keeps --skip-jbuilder --skip-test --skip-system-test --minimal --css=tailwind from /home/fedex/.railsrc
create
create  README.md
create  Rakefile

...

apply  /sandbox/template/template.rb
gemfile    awesome_print
gemfile    group :development
gsub    Gemfile
gemfile    rubocop
gsub    Gemfile
gemfile    group :development, :test
gsub    Gemfile
gemfile    rspec-rails
gsub    Gemfile
create    .rubocop.yml
$

Now that the application creation has finished, we can check out our Gemfile and see that awesome-print and both groups we added are present.

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.2.2"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0.6"

# ...

gem "awesome_print"

group :development do
  gem "rubocop"
end

group :development, :test do
  gem "rspec-rails"
end

That's how a Rails application template works. You define what you want on your application from its inception and the template takes care of producing it.

The file method

Since we included rubocop on our Gemfile, lets ask our template to write a .rubocop.yml file to configure it. To create a plain file, we use the file method passing the file relative path from our root directory as the first argument and the desired file contents as the second.

file ".rubocop.yml", <<~CODE
  ---
  AllCops:
    NewCops: enable

  Layout/HashAlignment:
    EnforcedColonStyle: table
    EnforcedHashRocketStyle: table

  Lint/MissingSuper:
    Enabled: false

  Style/BlockDelimiters:
    EnforcedStyle: semantic
    BracesRequiredMethods: ['let', 'let!']
CODE

Updating an application template on a created app

We now have an application, so we can't use rails new to implement our template changes.

But what we can do is run a generator to execute it

To apply a template to an existing application, we would run the app:template generator with the LOCATION environment variable passed in as an argument.

bin/rails app:template LOCATION=./template.rb

Again, the path can be a remote url.

bin/rails app:template LOCATION=http://example.com/template.rb

But of course, we will stick with the local one.

$ cd blog
$ bin/rails app:template LOCATION=../template.rb
gemfile  awesome_print
gemfile  group :development
gsub  Gemfile
gemfile  rubocop
gsub  Gemfile
gemfile  group :development, :test
gsub  Gemfile
gemfile  rspec-rails
gsub  Gemfile
create  .rubocop.yml

Unfortunately, most template methods are not idempotent, so we can see that our gem calls have been duplicated. So keep this in mind.

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

# ...

gem "awesome_print"

group :development do
  gem "rubocop"
end

group :development, :test do
  gem "rspec-rails"
end
gem "awesome_print"

group :development do
  gem "rubocop"
end

group :development, :test do
  gem "rspec-rails"
end

With that caveat in mind, lets take a look at our newly created .rubocop.yml file

---
AllCops:
  NewCops: enable

  Layout/HashAlignment:
    EnforcedColonStyle: table
    EnforcedHashRocketStyle: table

    Lint/MissingSuper:
      Enabled: false

      Style/BlockDelimiters:
        EnforcedStyle: semantic
        BracesRequiredMethods: ['let', 'let!']

        # ...

Adding Tailwind

So far, we've been creating files one way or another. Lets do something more involved.

Over the last few years, I've gotten used to using TailwindCSS in my projects. Lets install it using our template.

We first need to add the gem to the Gemfile, which will make sure we'll have the tailwind-rails gem available after bundle install has been run.

gem "tailwindcss-rails"

The next step to installing TailwindCSS the Rails way is to run the tailwindcss:install command.

rails_command "tailwindcss:install"

To run a command, we can use the rails_command method. This method is equivalent to calling the rails command in the terminal from the project's root directory. Like the terminal command, we need to pass the name of the generator to run; in our case, it's tailwindcss:install.

Running generators

We can always use generators to, for example, create a scaffold for a user.

We just need to use the generate method, passing the generator name as the first argument, and the generator's argument as the second.

And of course, since we're creating a table on the database, we need to run our migrations.

generate(:scaffold, "user name:string")
rails_command("db:migrate")

Also, we don't see the rails welcome screen when we go to the root path, so lets add a route to our routes file.

route 'root to: "users#index"'

Lets run the generator one more time and run the server.

$ rails new blog -m ./template.rb
$ cd blog
$ bin/dev

If we navigate to localhost:3000, we can now see the users#index page, stylized with TailwindCSS and completely functional, which means that the migrations have already been run.

Committing our code

Finally, since this'll be the default application definition to use on every project, we can create a commit so that we don't have a bunch of files to stage right out of the box.

To do so, we can call the git method with the :init symbol as an argument to initialize the repository.

Then we can stage all changes. and finally commit with a message.

git :init
git add: "--all"
git commit: "-m 'Initial commit'"

Lets run the template and check the status of our repository.

Something went wrong here, we can see that even though we added all the files and commited, there are still several files to commit.

$ rails new blog -m ./template.rb
$ cd blog
$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   app/assets/config/manifest.js
        modified:   app/views/layouts/application.html.erb

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        app/javascript/
        bin/bundle
        bin/importmap
        config/importmap.rb
        vendor/javascript/

The problem was that some of the commands we run actually get run after the bundle install step.

An example of this is the Tailwind installer. We need the tailwindcss-rails gem available in order to run rails tailwindcss:install, so it gets run after bundling.

rails_command "tailwindcss:install"

We also saw that the scaffold generator was actually styled using Tailwind, which means it was run even after that.

For this situations, Rails Application Templates have an after_bundle callback.

The after_bundle method will run all the steps inside it's block after the bundle install step has been run, giving us some extra flexibility.

after_bundle do
  git :init
  git add: "--all"
  git commit: "-m 'Initial commit'"
end

If we run the template now, we can see that all the changes have been commited as we expected.

$ rails new blog -m ./template.rb
$ cd blog
$ git status
On branch main
nothing to commit, working tree clean

Asking questions

Rails Application Templates ship with a series of methods that allows us to ask questions to our users in order to customize the template at execution time. We have the ask, =yes?= and =no?= methods.

For example, we can ask our user if they actually want to commit the changes.

after_bundle do
  if yes?("Do you want to add an initial commit?")
    git :init
    git add: "."
    git commit: %Q{ -m 'Initial commit' }
  end
end

And when we run our template, we'll be able to decide if we want to execute this last step.

$ rails new blog -m ./template.rb
# ...
Do you want to add an initial commit? y

         run  git init from "."
Reinitialized existing Git repository in /sandbox/template/blog/.git/
         run  git add . from "."
         run  git commit  -m 'Initial commit'  from "."
[main (root-commit) a1f4548] Initial commit
 106 files changed, 1945 insertions(+)
 create mode 100644 .gitattributes

# ...

 create mode 100644 .gitignore
 create mode 100644 tmp/storage/.keep
 create mode 100644 vendor/.keep
 create mode 100644 vendor/javascript/.keep

Conclusion

That's a quick look at how Rails Application templates work.

You can learn more about them on the Rails Guides.

You can also find a great collection of templates on the Rails Bytes site.

Finally, you can view our templates for NB Casts episodes on GitHub. We'll be updating and modifying it according to our needs and it's free for you to use.

I hope you enjoyed this episode, and I'll see you on the next one.

Subscribe to our episode notifications

* indicates required

Intuit Mailchimp

Comments