DEV Community

Cover image for Rails Project:  A 12-step Guide
Alexander Rovang
Alexander Rovang

Posted on • Edited on

Rails Project: A 12-step Guide

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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 %>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 } %>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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) }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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 %>
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)