Rails Project: A 12-step Guide
Alexander Rovang
Posted on April 2, 2021
Organization, organization, organization.
The deeper I get into programming the more I envy my father-in-law's ability to pack his car for summer camping trips.
It's impossible (particularly given the 4-week timeframe we had) to even begin to scratch the surface of the Rails 190,000 line Framework ... but what we were able to do is start to see the elegance of it's organizational capacities. The real challenge, though, is in structuring how we CREATE the project in the first place. So, in true "Notes to Myself" fashion, that's what I'd like to focus on in this blog post.
Step One: Create your App
Pretty basic.
rails new app-name
Rails convention is to hyphenate multi-word names. After everything is loaded you can add whatever you like to your Gemfile (gem 'pry', gem 'bcrypt') and then bundle.
bundle install
Step Two: GIT!
The number of times I forget to do this... woof! Crazy important, though. Create a new repository and commit constantly. A couple of tips:
a) Don't worry about adding a README, gitignore, or license. The first two are generated when you created the Rails app, and the last one you can do later.
b) You are creating a new repository (not pushing).
c) Skip "echo # asdf".
d) Do "git add ." instead of "git add README.md".
Also remember how to commit.
git add .
git commit -m 'some pithy comment'
git push
Step Three: Create Models
I like to use the model generator here. The great thing about it is that it creates your migrations for you.
rails g model model_name model_attribute:attribute_type --no-test-framework
The syntax here: rails, g (for "generate"), model, the name of the model, and then each attribute you would like in that model. You can add as many attributes here as you like (name, email, gender), just make sure that each one is followed by a colon and it's corresponding type (integer, string, text). Rails will automatically create tests when you use the generate method, so if you don't want them, add "--no-test-framework" to the end of each command.
Step Four: Migrate
Just because Rails gave you some beautiful migrations doesn't mean you have anything going on in the database!
rake db:migrate
Step Five: Create Controllers
Again, I really like to use Rails to generate my controllers because they will automatically give you some empty actions, routes and corresponding views.
rails g controller users index show create --no-test-framework
The syntax here: rails, g (for "generate"), controller, the name of the controller, and then each action you would like in that controller. It's pretty awesome.
Step Six: Fix or Create Routes
As I mentioned, Rails will create routes for you when you use the controller generator, but they're pretty messy. Each route is written individually instead of using resources, and if you want anything custom you'll have to do it yourself anyway. So, I recommend jumping into your config/routes.rb file and doing some cleaning.
resources :users, only: [:index, :show, :create]
The syntax here: resources, the name of the controller. If that's all you put, you'll get the full 7 RESTful routes, but you can also specify "only" or "except" to limit which routes are added.
Step Seven: Model Associations & Validations
Before you can start any real work on your controller or views, you need to set up your associations (has_many, belongs_to), and while you're there you might as well start getting some validations together.
class User < ApplicationRecord
include ActiveModel::Validations
has_secure_password
validates_presence_of :username, :email
validates_uniqueness_of :username, :email
has_many :instruments
has_many :makers, through: :instruments
end
has_secure_password belongs to the Bcrypt gem, so be sure to add that to your Gemfile if you haven't already and create a migration for your users table that adds a column for 'password_digest'.
class AddColumnToUser < ActiveRecord::Migration[6.1]
def change
add_column :users, :password_digest, :string
end
end
Step Eight: Controller Basics
This next step is a matter of preference. Because a lot of the controller actions are the same or VERY similar between models, I like to do some of the simple logic that I know they all could use. Instance variables, strong params. That sort of thing. These can be adjusted later, but if I have several controllers with identical actions I'll fill one of them in, copy it, paste it into the other controllers and simply change the model name.
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
@users = User.all
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
end
def show
end
def edit
end
def update
end
def destroy
end
private
def set_user
@user = User.find_by(id: params[:id])
end
def user_params
params.require(:user).permit(:username)
end
end
Step Nine: Forms & Partials
Now that all of the pieces are in place, it's time to set up our forms. And because the "new" and "edit" forms are often identical, it's worth creating a partial or two to save yourself from some unnecessary code. I have a few other blog posts I'm writing that cover forms in Rails, so I'm not going to go too deeply into that here, but I will copy a fairly comprehensive form page that I have in my project.
<%= form_for @instrument do |form| %>
<div><%= form.label "Type"%></div>
<%= form.collection_select :category_id, banjo_categories, :id, :name %>
<div><%= form.label "Maker" %></div>
<%= form.text_field :maker_name, list: "makers_autocomplete" %>
<datalist id="makers_autocomplete">
<% Maker.all.each do |maker| %>
<option value="<%= maker.name %>">
<% end %>
</datalist>
<div><%= form.label "Price"%></div>
<%= form.number_field :price%>
<div><%= form.label "Year"%></div>
<%= form.number_field :year%>
<div><%= form.label "Description"%></div>
<%= form.text_area :description%>
<div><%= form.submit "Add Instrument"%></div>
<% end %>
This page utilizes 3 types of input: collection_select, datalist, and the normal input for a form_for page. Most of this (excluding the opening and closing tags) can be encapsulated in a partial. Partials are named beginning with an underscore and then followed by the name of the view folder they're in.
_instruments.html.erb
The repetitive information is put into this file and then the view file has a render method that essentially yields to the partial.
<%= render partial: "instruments", locals: { form: form, family: @family, instrument: @instrument } %>
The syntax here: "render partial:", the name of the partial, "locals:" and then a hash with key/value pairs linking established variables from the controller to local variables in the partial.
Step Ten: Controller Logic
This is the big one. Get into a comfy chair. Lock the door. Put the 6-pack of Dr.Pepper next to you and get to work. If 20% of your time is spent setting everything up, and another 20% is cleaning up/refactoring, then 60% will be figuring out how everything actually needs to work. I also have some blog posts dedicated to routes and what I call the "Client's Journey", but the pattern I feel I use for every Controller action is this:
-What's coming in?
-What would you like to do here?
-Where is it going after?
The first question is a params issue. Whether it's coming from the database, a form, or just a GET query ... there always seems to be useful information right at the top of any action. The second piece is how you want to manipulate that data. Most of the time that question is answered for you by which action you're in. I'm in the index? I must want to show some information, so I probably need an instance variable. I'm in the update action? I need to connect with the database and use what's coming in to alter it. And finally, where do you want to redirect the user to after? This tends to be the most malleable of the decisions. Some of them are fairly obvious (edit will go to the edit view), but others are a matter of choice. When you finish updating a User's info you could go to their page, but you could also go to a homepage, or really anywhere you want to take them. It can get a little overwhelming when you start to add in validations and protections against bad data, but sticking with those 3 steps will get you pretty far!
Step Eleven: Helper Methods
There are several types of helper methods. Some go in the model, some in the controller, and some in the helpers folder (which are specific to views). But at the end of the day, helper methods are simply ways to clean up your code so that looking at your controller doesn't feel like you're seeing the Matrix for the first time.
View helpers keep SQL queries out of of your actual view pages. This helper is in my instruments helper folder and is accessed in my instruments "new" page.
def banjo_categories
Category.where(id: [1...5])
end
This is a scope method helper that I have in my Maker class which is accessed in the index of my Makers Controller.
scope :search, ->(name) { where("name LIKE ?", name) }
And then, of course, you have regular helper methods like this one in my Instrument Controller to help alleviate repetition.
def set_instrument
@instrument = Instrument.find_by(id: params[:id])
end
There's also a helper_method that you can use to expose Controller methods to your views. Apparently it's not ideal, but it is useful in certain situations such as accessing a current users params.
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
@current_user = User.find_by(id: session[:user_id])
end
def require_login
redirect_to "/login" if !current_user
end
end
Step Twelve: Error Messages
Last but not least! Well, it is actually kind of least. But still important because it has to do with user experience. Most of these steps were either to get an app up and running or to clean up an app so that 10yrs down the line you (or another coder) can look at it and easily understand what's going on and fix things quickly and efficiently. Error Messages are our way of guiding the user through our program. It took me awhile to grasp the concept of an error being an object, but there it is. And as with all other objects, errors have attributes that we can call upon and display. This is a pretty common error syntax.
<% if instrument.errors.any? %>
<div id="error_explanation">
<h2>Oops! We noticed
<%= pluralize(instrument.errors.count, "error") %>
when trying to process your information.
</h2>
<ul>
<% instrument.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
The first line checks to see if our instrument object had any errors. If it did, there will be a hash containing messages about those errors. In the h2 we count the number of errors committed, and in the ul we display each of them with the full_messages method.
I wish I could say "And that's it!". Hahaha Remember: 190,000 lines of code. We haven't even scratched the surface. But these 12 steps will hopefully help guide you (and me!) through our next Rails adventure with a little less sweat than the first one.
Peace
Posted on April 2, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.