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.
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 Gemfilegem_group
– Create agroup
block in the Gemfile with the gems in the supplied blockadd_source
– Add gem source to Gemfile, for example, a private Gem serverenvironment
– Add a line to the application.rb or the supplied environment file. This is aliased toapplication
as well. I’ll show you an example of it working in my final template.git
– Run git commandsvendor
– Create a file in the vendor directorylib
– Create a file or directory in librakefile
– Create a new Rakefileinitializer
– Create a new initializer file with the supplied contentgenerate
– Run a Rails generatorrake
– Run a Rake taskcapify
– Runcapify
route
– Create an entry in routes.rbreadme
– Prints the contents of the supplied file to the consoleafter_bundle
– A callback that runs (you guessed it) oncebundle 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 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.