Rails Application Templates in the Real World

Share this article

Key Takeaways

  • Rails Application Templates streamline project setup by ensuring consistency across Rails services, which is crucial when multiple developers are involved.
  • The Template API, utilizing Thor, provides a robust set of commands for customizing Rails applications, from gem management to environment configuration.
  • Custom templates can significantly cut down development time by pre-configuring applications with necessary gems, files, and settings like Rspec, Docker configurations, and JSON Schema tools.
  • Using a custom template allows for immediate development post-setup without the need to manually configure elements like database settings and test environments.
  • It’s important to store templates locally to avoid issues with remote file access during project generation, enhancing reliability and speed in setting up new services.
railstemplate

I’m about to start a new project that will have many Rails (and rails-api) based services, and I want a way to make sure all the services are created equal. There are a couple of other developers on the project, so I don’t want us each creating wildly different application structures or using different gems unless there’s a good reason.

For example, I’d like us all to start with the same version of Rails and PostgreSQL is the repository of choice, so installing the SQLite gem is silly. In the test and development environments, it’d be great to already have Rspec and Guard ready to go when the application is generated. Also, having Pry available from the get-go is just plain smart. It’s a myriad of these and similar changes that have me wanting to modify our starting point.

There are tons of ways to do this, but I didn’t really want to start with something like RailsBricks or Rails Composer. I have nothing against either of those, quite the opposite. I actually wrote about RailsBricks and I think Daniel Kehoe (the creator of the RailsApps tutorials and Rails Composer) should be knighted. Simply put, I want to do this without adding another dependency, if possible.

The Rails core team offers an approach in Rails Application Templates, so why not take a shot at using what the core team uses.

Spoiler: It works great.

Template API

The Rails Template API is a Domain-Specific Language (DSL) this is powered by Thor. It adds the following actions on top of the core Thor actions:

  • gem– Add a gem to the Gemfile
  • gem_group – Create a group block in the Gemfile with the gems in the supplied block
  • add_source – Add gem source to Gemfile, for example, a private Gem server
  • environment – Add a line to the application.rb or the supplied environment file. This is aliased to application as well. I’ll show you an example of it working in my final template.
  • git – Run git commands
  • vendor – Create a file in the vendor directory
  • lib – Create a file or directory in lib
  • rakefile – Create a new Rakefile
  • initializer – Create a new initializer file with the supplied content
  • generate – Run a Rails generator
  • rake – Run a Rake task
  • capify – Run capify
  • route – Create an entry in routes.rb
  • readme – Prints the contents of the supplied file to the console
  • after_bundle – A callback that runs (you guessed it) once bundle install is complete

It turns out that these few actions, along with the core Thor actions, are more than enough to make a robust starting point for a real Rails application.

The Example

As I previously mentioned, I want to change some items from the core rails new generated site. Here’s the complete list of things I want to tailor:

  • Add the pg gem to the Gemfile and database configuration
  • Use Rails API. This means that I will change which core Railties are included.
  • Use ROAR for my presenters
  • Use Pry (via the byebug gem) in development
  • In test, Use Rspec, Mutant, SimpleCov, and Guard, along with running the generators/install
  • Create a Dockerfile and docker-compose.yml for the application
  • Add a better .gitignore file
  • Add some tools around JSON Schema. The fine folks at Heroku have a ton of them
  • Add a route for a health check endpoint

As you can see, it’s not too much, but having a template that developers can use when generating a service will save some time and remove some of the incongruencies between development environments. Once this template is complete, a developer will be able to type

rails new service_name -m our_template.rb

and then go straight into development. Things like guard will work immediately, without any need to set them up. Just like it should be.

The Template

Here’s the entire template:

# Add the current directory to the path Thor uses
# to look up files
def source_paths
  Array(super) + 
    [File.expand_path(File.dirname(__FILE__))]
end

remove_file "Gemfile"
run "touch Gemfile"
add_source 'https://rubygems.org'
gem 'rails', '4.2.1'
gem 'rails-api'
gem 'puma'

gem 'pg'
gem 'roar-rails'

gem 'committee'

gem_group :development, :test do
  gem 'spring'
  gem 'pry-rails'
  gem 'web-console', '~> 2.0'
  gem 'prmd'
  gem 'rspec-rails', require: false
end

gem_group :test do
  gem 'simplecov', require: false
  gem 'simplecov-rcov', :require => false
  gem 'guard-rspec'
  gem 'mutant'
  gem 'mutant-rspec'
end

copy_file "Dockerfile"
copy_file "docker-compose.yml"
remove_file ".gitignore"
copy_file ".gitignore"

inside 'config' do
  remove_file 'database.yml'
  create_file 'database.yml' do <<-EOF
default: &default
  adapter: postgresql
  host: db 
  port: 5432
  pool: 5
  timeout: 5000
  user: postgres
  password: postgres

development:
  <<: *default
  database: #{app_name}_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: #{app_name}_test
  host: 192.168.59.103

production:
  <<: *default
  database: #{app_name}_production

EOF
  end
end

create_file 'schema/meta.json' do <<-EOF
{
"description": "Service",
"id":"service-uu",
"links": [{
"href" : "https://api.esalrugs.com",
"rel" : "self"
}],
"title" : "UU Service"
}
EOF
end

# JSON Schema
empty_directory "schema/schemata"

rakefile("schema.rake") do <<-EOF
require 'prmd/rake_tasks/combine'
require 'prmd/rake_tasks/verify'
require 'prmd/rake_tasks/doc'

namespace :schema do
    Prmd::RakeTasks::Combine.new do |t|
      t.options[:meta] = 'schema/meta.json'    
      # use meta.yml if you prefer YAML format
      t.paths << 'schema/schemata'
      t.output_file = 'schema/api.json'
    end

    Prmd::RakeTasks::Verify.new do |t|
      t.files << 'schema/api.json'
    end

    Prmd::RakeTasks::Doc.new do |t|
      t.files = { 'schema/api.json' => 'schema/api.md' }
    end
  task default: ['schema:combine', 'schema:verify', 'schema:doc']
end
EOF
end

after_bundle do
  remove_dir "app/views"
  remove_dir "app/mailers"
  remove_dir "test"

  insert_into_file 'config/application.rb', after: "require 'rails/all'\n" do <<-RUBY
require "active_record/railtie"
require "action_controller/railtie"
  RUBY
  end

  gsub_file 'config/application.rb', /require 'rails\/all'/, '# require "rails/all"'

  application do <<-RUBY
    config.assets.enabled = false
    config.generators do |g|
      g.test_framework :rspec, fixture: true
      g.view_specs false
      g.helper_specs false
    end

    # Validates the supplied and returned schema. 
    # docs: https://github.com/interagent/committee
    config.middleware.use Committee::Middleware::RequestValidation, schema: JSON.parse(File.read("./schema/api.json")) if File.exist?("./schema/api.json")
  RUBY
  end

  gsub_file 'config/environments/development.rb', /action_mailer/, ''
  gsub_file 'config/environments/test.rb', /.*action_mailer.*/n/, ''

  gsub_file 'app/controllers/application_controller.rb', /protect_from_forgery/, '# protect_from_forgery'

  run "spring stop"
  generate "rspec:install"
  run "guard init"

  # Health Check route
  generate(:controller, "health index")
  route "root to: 'health#index'"

  git :init
  git add: "."
  git commit: "-a -m 'Initial commit'"
end

Hmph, that looks long, doesn’t it? Let’s break down the template.

# Add the current directory to the path Thor uses
# to look up files
def source_paths
  Array(super) + 
    [File.expand_path(File.dirname(__FILE__))]
end

Thor uses source_paths to look up files that are sent to file-based Thor actions, such as copy_file and remove_file. It is redfined here so I can add the template directory and copy files from it to the generated application.

The default Rails Gemfile has a bunch of stuff we don’t need (sqlite, all the asset stuff, turbolinks), so rather than tediously remove gems, let’s just explicitly rebuild the file. Of course, every Gemfile needs a source to search, so that’s added to the top of the file.

# We'll be building the Gemfile from scratch
remove_file "Gemfile"
run "touch Gemfile"

add_source 'https://rubygems.org'

Here are all the gems we need, :

gem 'rails', '4.2.1'
gem 'rails-api'
gem 'puma'

gem 'pg'
gem 'roar-rails'

gem 'committee'

gem_group :development, :test do
  gem 'spring'
  gem 'pry-rails'
  gem 'web-console', '~> 2.0'
  gem 'prmd'
  gem 'rspec-rails', require: false
end

gem_group :test do
  gem 'simplecov', require: false
  gem 'simplecov-rcov', :require => false
  gem 'guard-rspec'
  gem 'mutant'
  gem 'mutant-rspec'
end

gem and gem_group are supplied by the Rails Template API and do exactly what you’d expect.

One of the goals of this template is to make Dockerizing the app very simple. Explaining Docker and its benefits (and cons) is well beyond the scope of this article. Instead, I’ll encourage you to look into Docker (I hope to write a post on that soon) and look at the following as I just needed to copy some custom files into the generated app.

copy_file "Dockerfile"
copy_file "docker-compose.yml"

The Dockerfile and docker-compose.yml are files in my template directory. The files are just copied in using Thor’s copy_file action.

Let’s make a shiny, new .gitignore file. The reason the file is removed first is to avoid the rails new command from prompting the user to overwrite the file:

remove_file ".gitignore"
copy_file ".gitignore"

In what may be a bit more controversial decision, there’s a core database.yml file that I’d like to distribute. Thanks to Docker, we can standardize this a bit. The cool inside method makes sure the file ends up in the right folder:

inside 'config' do
  remove_file 'database.yml'
  create_file 'database.yml' do <<-EOF
default: &default
  adapter: postgresql
  host: db 
  port: 5432
  pool: 5
  timeout: 5000
  user: postgres
  password: postgres

development:
  <<: *default
  database: #{app_name}_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: #{app_name}_test
  host: 192.168.59.103

production:
  <<: *default
  database: #{app_name}_production

EOF
  end
end

Notice that I use app_name to create the database names. app_name and app_path are available variables to the template.

Time to leverage some JSON Schema tools to make sure I have good schema and doc generation, as well as some validation. The JSON Schema flow is still a bit clumsy in Rails, but the end justifies the means, IMO:

create_file 'schema/meta.json' do <<-EOF
{
"description": "Service",
"id":"service-uu",
"links": [{
"href" : "https://api.esalrugs.com",
"rel" : "self"
}],
"title" : "UU Service"
}
EOF
end

# JSON Schema
empty_directory "schema/schemata"

rakefile("schema.rake") do <<-EOF
require 'prmd/rake_tasks/combine'
require 'prmd/rake_tasks/verify'
require 'prmd/rake_tasks/doc'

namespace :schema do
    Prmd::RakeTasks::Combine.new do |t|
      t.options[:meta] = 'schema/meta.json'    
      # use meta.yml if you prefer YAML format
      t.paths << 'schema/schemata'
      t.output_file = 'schema/api.json'
    end

    Prmd::RakeTasks::Verify.new do |t|
      t.files << 'schema/api.json'
    end

    Prmd::RakeTasks::Doc.new do |t|
      t.files = { 'schema/api.json' => 'schema/api.md' }
    end
  task default: ['schema:combine', 'schema:verify', 'schema:doc']
end
EOF
end

First, create a meta.json file that will hold the metadata about this service. Title, URL, etc. Next, create a schema/schemata directory to hold the schema files for my resources. I am not going to go into the whats and hows of JSON Schema in this post, but I encourage you to check it out. Finally, I add some rake tasks to generate the schema and markdown documents that will document the API. The rakefile method is provided by the Rails Application Template API and it’s cool.

Now we’ve reached the post-bundle callback. There’s a lot done after bundling, some of which could probably be moved to pre-bundle, but here we are. This is a Rails API applicsation, so we don’t have a need for views and mail won’t be handled by these services. Also, we’re using Rspec, so just remove the test directory:

after_bundle do
  remove_dir "app/views"
  remove_dir "app/mailers"
  remove_dir "test"

Since Rails API doesn’t use all the core Railties, we can gain some performance here by only requiring what we need. Thor’s cool insert_into_file method allows us to put it right where we want it. Once done, we can comment out the line that includes everything, using gsub_file:

insert_into_file 'config/application.rb', after: "require 'rails/all'\n" do <<-RUBY
    require "active_record/railtie"
    require "action_controller/railtie"
  RUBY
  end

  gsub_file 'config/application.rb', /require 'rails\/all'/, '# require "rails/all"'

Since there are no assets or views, disabling assets altogether is a good idea. Also, while I want Rspec as the test framework, I don’t want view specs. Here, I use the Rails Template API method application which, if you remember, is just an alias for environment:

application do <<-RUBY
    config.assets.enabled = false
    config.generators do |g|
      g.test_framework :rspec, fixture: true
      g.view_specs false
      g.helper_specs false
    end

Here is some more JSON Schema fun in the form of middleware that will perform validation:

# Validates the supplied and returned schema. 
# docs: https://github.com/interagent/committee
config.middleware.use Committee::Middleware::RequestValidation, schema: JSON.parse(File.read("./schema/api.json")) if File.exist?("./schema/api.json")

RUBY
end

The committee gem provides the middleware to validate the JSON Schema in requests and responses. Input validation is groovy.

Next, get rid of all the Action Mailer config:

gsub_file 'config/environments/development.rb', /.*action_mailer.*/n/, ''
  gsub_file 'config/environments/test.rb', /.*action_mailer.*/n/, ''

Since we won’t be using cookies with the API (because that is utter lunacy), the protect_from_forgery method is not needed:

gsub_file 'app/controllers/application_controller.rb', /protect_from_forgery/, '# protect_from_forgery'

I’d like to have the test/spec environment ready to go, so that means running the Rspec generator and initializing Guard. I learned the hard way that if I don’t stop Spring, the generator will hang:

run "spring stop"
  generate "rspec:install"
  run "guard init"

Now, I want each service to have a heath check endpoint, so let’s make one:

# Health Check route
generate(:controller, "health index")
route "root to: 'health#index'"

Almost done, just need to setup Git:

git :init
  git add: "."
  git commit: "-a -m 'Initial commit'"
end

Thats a wrap! I can tell our development team to use my template to generate their Rails services, and we will have a common foundation along with a ready-to-go environment. Just cd into the new application directory and guard or docker-compose up and you’re off. Not bad for very little work. I have put a lot of things into place to encourage some best practices and get rid of cruft. When people say Rails makes you more productive, this is a great example, in my humble opinion.

Gotchas

Of course, there’s a couple of things to be aware of using this particular example. The big one is, since I decided to copy files from the template directory, you can’t use an HTTP endpoint for the template file. In other words, this won’t work:

rails new service_name -m https://raw.githubusercontent.com/skookum/rails-api-template/master/rails-api-template.rb

because it won’t find the Dockerfile to copy. This could be fixed by inlining all the file content into the template itself using heredocs. So, if you’re going to use this, be sure to clone the repository to a local spot and refer to the local path in the -m parameter.

Also, some of you might be asking why I am not using rails-api new service_name. It’s a good question. Basically, I looked at what rails-api new generates and made my template do something similar. For what it’s worth, you can pass the same -m parameter to the rails-api generator and you’ll end up in a similar place.

Finally, this template hasn’t been proven out yet. As we use it and find things that it doesn’t do or is missing, I plan to update the repository. As such, the template may diverge from the article.

Template All the Things

Hopefully, this post will encourage you to take a similar approach to your Rails team-based development. We are only really just starting with templates, so I am sure I’ve missed something or one of you has some great advice on how to make this even better. Put that stuff in the comments!

Thanks for reading!

UPDATE: A previous version of this article incorrectly stated that Rails Composer was created by M. Hartl when, in fact, it was created by Daniel Kehoe. Our apologies for getting this wrong. Those responsible have been sacked.

Frequently Asked Questions (FAQs) about Rails Application Templates

What are the benefits of using Rails Application Templates?

Rails Application Templates are a powerful feature that can help you build Rails applications more efficiently. They allow you to customize the default Rails application structure to suit your specific needs, saving you time and effort in setting up the basic structure of your application. You can add gems, configure settings, generate scaffolding, and even add front-end frameworks like Bootstrap or Tailwind CSS. This means you can start working on the unique features of your application right away, rather than spending time on setup and configuration.

How do I create a Rails Application Template?

Creating a Rails Application Template involves writing a Ruby script that uses the Rails::Generators API. This script defines the changes you want to make to the default Rails application structure. You can use methods like gem to add gems, generate to generate scaffolding, and run to run arbitrary commands. Once you’ve written your template, you can use it to create a new Rails application with the -m option, like so: rails new myapp -m my_template.rb.

Can I use Rails Application Templates with existing applications?

Rails Application Templates are primarily designed for use with new applications. However, you can apply a template to an existing application by using the rails app:template command, like so: rails app:template LOCATION=path/to/my_template.rb. Keep in mind that this will run the template’s commands in the context of your application, which may have unintended side effects if the template was not designed with this use case in mind.

Can I share my Rails Application Templates with others?

Yes, Rails Application Templates are just Ruby scripts, so you can share them like any other piece of code. You could, for example, put your template in a GitHub repository and share the URL with others. They can then use your template by passing the URL to the -m option, like so: rails new myapp -m https://raw.githubusercontent.com/username/repo/master/template.rb.

How can I add a front-end framework to my Rails Application Template?

You can add a front-end framework to your Rails Application Template by using the gem method to add the necessary gems, and the run method to run any necessary commands. For example, to add Bootstrap, you might do something like this: gem 'bootstrap', '~> 4.3.1' and then run 'yarn add bootstrap@4.3.1 jquery popper.js'.

Can I use Rails Application Templates to configure my database?

Yes, you can use Rails Application Templates to configure your database. You can use the gsub_file method to replace the default database configuration in config/database.yml with your own. You can also use the gem method to add any necessary gems for your chosen database.

How can I handle complex logic in my Rails Application Template?

If you need to handle complex logic in your Rails Application Template, you can use regular Ruby code. The template is just a Ruby script, so you can use conditionals, loops, and other control structures as needed. You can also define and call methods to organize your code.

Can I use Rails Application Templates to set up testing?

Yes, you can use Rails Application Templates to set up testing for your application. You can use the gem method to add testing gems like RSpec or Minitest, and the generate method to generate any necessary files or scaffolding.

How can I debug my Rails Application Template?

If you’re having trouble with your Rails Application Template, you can use the say method to print messages to the console. This can help you understand what the template is doing at each step. You can also use the ask method to prompt for user input, which can be useful for debugging purposes.

Can I use Rails Application Templates to set up background job processing?

Yes, you can use Rails Application Templates to set up background job processing. You can use the gem method to add gems like Sidekiq or Delayed Job, and the generate method to generate any necessary files or scaffolding. You can also use the run method to run any necessary commands, like setting up the job queue.

Glenn GoodrichGlenn Goodrich
View Author

Glenn works for Skookum Digital Works by day and manages the SitePoint Ruby channel at night. He likes to pretend he has a secret identity, but can't come up with a good superhero name. He's settling for "Roob", for now.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week