I have made a few Rails 8 templates. They weren’t very complicated, in depth, or long. They were pretty much configuration changes. My system needs a little tweak and so I created a few to help me out. Tools really. They were fun.
Recently I created a gig on Fiverr for a Rails Blog. I have built many and knew it was one I could easily take care of. Even the official Rails docs, is an ”how-to” on blog creation, so after the gig I decided to make a template for the basic blog. My goal was to have something I could just run in a command, maybe answer some questions and blam, ready for tweaks and config.
I am about half done with the template and it has been around 3 days. Holy cow, I had no idea. Not only in how much it will help, but in everything that goes into such a thing. Let me take just a moment to explain what I am building.
The gig has 3 different packages, typical. I want a template for the basic package. This is probably aimed at solo bloggers, small companies, small sites. Any market that would like to have their own personal blog. That in mind there were a few things I needed to handle. Maybe this will be sold as just the template, I need to get it right.
I would like to know what it does. I need to check for certain things like the version of Rails being used, Ruby, Node, FFmpeg, and LibVips. If everything is good, great, if not, let me know. Will use Postgres and I prefer a different config. Will use S3 so need to address that storage config. If changing the storage config then I need to ensure the production environment uses it. Need to install devise and an admin user. There is the Post and Category models, with two controller for each. One for public and one for admin. Views for both and Routes. It needs to get input from the user and let them know what it is doing every step of the way.
There is a lot here. I did not mention Stimulus or Hotwire. So there is a lot of install and deletion. Rails allows for the pretend flag. You can run your command with the “--pretend or -p” on the end to play at running the command. This is great but for this I need to see what the outcome is. I need to see the build logs. So here are some commands and things I have been using to get this done.
Delete the old project
You have run the template, failed miserably, fixed bugs and need to run it again. There is already a blog project there. You can run:
rm -rf <name>
That command will delete the whole project. The -r flag means recursive and will grab that folder and everything in it. The -f is force to Hulk punch that thing out of existence. The <name> is what ever you called the project, the name of the folder you are deleting.
The old data oops
Now if you run the template again with:
rails new <name> -c tailwind -d postgres -m <template>
you will likely get a error from the database. The tables created on the previous run are still there. The best way to get rid of them is to cd back into the old folder you deleted and run:
bin/rails db:drop
but you can’t, because the folder is gone. So the best way I found to handle this is with PostgreSQL. When using this command you need to know the database credentials because it should ask for the password before completing.
dropdb <name>_development -U postgres -h localhost
dropdb <name>_test -U postgres -h localhost
For my setup, I use the default username and password for PostgreSQL on my local machine. For development I typically just spin one up in the cloud and use the URI. Super secure, when you follow best practice and yet, accessible to anyone who wants to learn, or use. Love it!
Heredoc and Thor, ( could be a Marvel - D/C crossover )
What is Thor? Aside from the hammer wielding god? A Rails gem, built right in, used for creating command line interfaces and handling generators and tasks. So glad that cleared things up. Thor is a toolbox of goodies to help you weave Rails magic, tiny human.
What is Heredocs? Actually you have probably been creating a kind of version in your javascript or HTML and is short for here document. For Rails specifically, it is a sort of syntax that allow for writing multiple lines of code. Big difference here is that it is not limited to comments, but allows for several languages like RUBY or YAML which you use a lot in Rails.
To create the command line messages I used Thor’s “say” command and it looks like this:
INDENT = " " # Two spaces
say "#{INDENT}Configure PostgreSQL...", :blue
I use the INDENT there to indent the line to match previous output lines in the terminal. Two spaces is Ruby standard. I use the color at the end to provide visual context to the user. You can set it up to use your own colors with ANSI syntax. I just stuck with simple ones. Green for ok, yellow for warning, red for error, as well as cyan and blue for inputs or step logging.
You get really clean communication in the terminal. You have to love that.
==> Installing Devise... ✅
==> Creating admin user... ✅
==> Setting up PostgreSQL config... ✅
There is also say_status
that, adds a prefix and outputs a status message if you would like that confirmation:
say_status(:remove, "config/database.yml", :red)
As I said, Thor is a toolbox with all sorts of cool things. Here are some of the one’s I used the most:
ask - You can prompt the user for input. I used this for getting the name, email, and password from the user, to seed the creation of a admin user. Passing variables between files can be a little tricky, so I didn’t, but it can be done.
ask "What is the admins name?"
yes - Allows for a yes / no condition. I used this after conditional checks passed to say, “all good here, ready to ride?” The user can say Yes or No and the template will react accordingly. Here I am using “unless” for operation. Want to stop and wait for the reply. Yes will return a boolean, and that is what “unless” is waiting for here.
unless yes?("All checks passed. Continue with install? (Y/n)", :cyan)
say "Aborting template setup.", :red
exit 0
end
remove_file - Give it the location and will remove it. For example, need to remove the database.yml file in the config folder. This is relative to where it is running in root, so the command is:
remove_file 'config/database.yml'
create_file - Use this with heredoc to create the file you want. Again, give it the location. Here I am using JAVASCRIPT as the language, but you can write really anything here. Remember to wrap your code block with the language tag. With Ruby you need to add the “end” statement after the block. Here is a simple example:
create_file 'app/javascript/controllers/test_controller.js', <<~JAVASCRIPT
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
console.log('Wonderful')
}
}
JAVASCRIPT
Another thing to point out here in the heredoc syntax. I use the tilde here, “<<~JS
”. This strips any leading indentation. You can also use “<<-
“ with the hyphen that will ignore indentation all together. I probably made things a little difficult by opting to be more verbose, but I prefer it.
You may have seen this syntax using %Q{} looking very similar:
create_file 'app/javascript/controllers/test_controller.js', %Q{
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
console.log('Wonderful')
}
}
}
Using either works, but I prefer heredoc. In Rails, the HTML, RUBY, YAML tags are show really, but some editors do enforce them. I also love the “at a glance” readability of heredoc. Also you get some functionality built in, unlike %Q{} that treats anything inside like a string.
empty_directory - Give it the location and it will wipe the whole folder clean.
empty_directory 'app/serializers'
generate :<rails component> - Use this like you would in the command line. Need a model? In the terminal you would run “bin/rails g model post title:string” to create the Post model. In the template with Thor you would:
generate :model, 'Post title:string'
rails_command - Let’s you run rails commands like you would in the terminal.
rails_command 'db:create'
Lastly, for now, this template is pretty involved in that it is setting a lot up in a single build process. There is devise, configurations, serializers, not to mention all the views, models, and controllers. I needed this to be modular so I used the “apply” action. With apply I passed it the “File” utility to reach out and grab a sub template file to use. This way I can delete the database.yml file in the main template, then call the file I created the entire new template in, to install the new one. For that file structure, I have the main template, then a folder beside it called blog-templates. In that blog-templates folder is where all the sub template files are. Here is what that snippet looks like:
apply File.join(File.dirname(__FILE__), 'blog-templates', 'bb-pg-config.rb')
This looks in the same directory for a folder called “blog-templates” to find a file called “bb-pg-config.rb”.
P.S. after_bundle block
I nearly forgot the “after_bundle do” block. Most of the code, creating or deleting stuff, needs to be done after bundle has run. You can’t remove a file if it is not there. Thor gives you the after_bundle
so all the code for managing or manipulating the rails app is inside that do block.
P.S.S resource
I have a Thor CheatSheet for you that may help. It is still a work in progress as of today and I need to add things like file_collision, template, and inside to it. I did not go over these here either. Let me know if you are interested in another article for them.