Ruby on Rails Resource Generator: Instant Full-Stack MVC Framework (CRUD not included)
This is the second of four blogs I am writing during my 15-week Software Development bootcamp at Flatiron School, in downtown NYC. For each blog, I will focus on a topic that directly relates to my learning now. The metacognitive benefits to myself are obvious, and I hope they may be useful resources to my peers as well. My first blog was all about setting up my own custom Visual Studio Code environment.
In this blog, I will describe how to instantly set up a very basic full-stack web content management framework using Ruby, Rails, and the resource generator
. Amongst the various generators available in Rails, the resource generator
occupies a nice sweet spot, offering plenty of prepackaged functionality, but not too much bloat code that you’ll probably never end up using. The generated code adheres to RESTful conventions, as well as Model-View-Controller (MVC) patterns. Surprisingly, googling ‘Ruby Rails resource generator’ brings up very little information. It seems that everybody prefers the scaffold generator
, and for good reason. It provides way more prepackaged functionality, but at the cost of forcing you to do things in a very pre-scripted way. Sometimes, it’s useful, but sometimes not, especially if it overwhelms your needs. One tutorial I recently read refers to this phenomena as ‘[using] a chainsaw to build a model airplane’ (Flatiron School Web Development Immersive).
Double Happiness
For my project I will be building out a basic has many — belongs to — has many relationship between three Ruby classes for a donation website called ‘Double Happiness’. The 3 classes are: Giver
, Getter
, and Thing
. The database tables will look like this:
table “givers”
string “name”
string “location”
table “getters”
string “name”
string “location”
table “things”
string “name”
integer “point_value”
integer “artist_id”
integer “genre_id”
And that’s basically all you need to get started. First, I cd
to a target folder in my terminal, and create a new Rails project.
rails new double-happiness
Resource Generator
And voila, lots of folders and useful files, most of which I don’t understand, yet. Next, I cd
into the newly created Rails project directory, and generate resources for my three classes (g
is short for generate
). For my sample project, I’m using a no-test-framework
, meaning it won’t create any tests for the newly-generated models, controllers, etc. That’s fine for now, I think.
rails g resource Giver name:string location:string — no-test-frameworkrails g resource Getter name:string location:string — no-test-frameworkrails g resource Thing name:string point_value:integer getter_id:integer giver_id:integer — no-test-framework
For each of my classes, I get:
invoke active_record
create db/migrate/20180430015713_create_givers.rb
create app/models/giver.rb
invoke controller
create app/controllers/givers_controller.rb
invoke erb
create app/views/givers
invoke helper
create app/helpers/givers_helper.rb
invoke assets
invoke coffee
create app/assets/javascripts/givers.coffee
invoke scss
create app/assets/stylesheets/givers.scss
invoke resource_route
route resources :giversinvoke active_record
create db/migrate/20180430015726_create_getters.rb
create app/models/getter.rb
invoke controller
create app/controllers/getters_controller.rb
invoke erb
create app/views/getters
invoke helper
create app/helpers/getters_helper.rb
invoke assets
invoke coffee
create app/assets/javascripts/getters.coffee
invoke scss
create app/assets/stylesheets/getters.scss
invoke resource_route
route resources :gettersinvoke active_record
create db/migrate/20180430015738_create_things.rb
create app/models/thing.rb
invoke controller
create app/controllers/things_controller.rb
invoke erb
create app/views/things
invoke helper
create app/helpers/things_helper.rb
invoke assets
invoke coffee
create app/assets/javascripts/things.coffee
invoke scss
create app/assets/stylesheets/things.scss
invoke resource_route
route resources :things
Wow, that’s a lot of stuff!
That’s a lot of code that Rails just generated for us. For a newbie like me, the ones that jump out are:
- migration files that will create new database tables for the attributes passed to it in the generator
- model files that inherit from
ActiveRecord::Base
- controller files that inherit from
ApplicationController
- view directories, but no view template files
- view helper files
- Coffeescript files for specific JavaScripts for that controller
scss
files for the styles for the controller- full
resources
call in theroutes.rb
file
Migrations
Let’s take a look at the migration files before we run our migrations.
class CreateGivers < ActiveRecord::Migration[5.1]
def change
create_table :givers do |t|
t.string :name
t.string :location t.timestamps
end
end
endclass CreateGetters < ActiveRecord::Migration[5.1]
def change
create_table :getters do |t|
t.string :name
t.string :location t.timestamps
end
end
endclass CreateThings < ActiveRecord::Migration[5.1]
def change
create_table :things do |t|
t.string :name
t.integer :point_value
t.integer :getter_id
t.integer :giver_id t.timestamps
end
end
end
Everything looks perfect. Next, we run our migrations rake db:migrate
, and the terminal tells us that our migrations appear to be successful.
== 20180430015713 CreateGivers: migrating =====================================
— create_table(:givers)
-> 0.0031s
== 20180430015713 CreateGivers: migrated (0.0033s) ============================== 20180430015726 CreateGetters: migrating ====================================
— create_table(:getters)
-> 0.0017s
== 20180430015726 CreateGetters: migrated (0.0018s) ============================= 20180430015738 CreateThings: migrating =====================================
— create_table(:things)
-> 0.0013s
== 20180430015738 CreateThings: migrated (0.0014s) ============================
Just to be sure, we should look at our schema.
ActiveRecord::Schema.define(version: 20180430015738) do create_table “getters”, force: :cascade do |t|
t.string “name”
t.string “location”
t.datetime “created_at”, null: false
t.datetime “updated_at”, null: false
end create_table “givers”, force: :cascade do |t|
t.string “name”
t.string “location”
t.datetime “created_at”, null: false
t.datetime “updated_at”, null: false
end create_table “things”, force: :cascade do |t|
t.string “name”
t.integer “point_value”
t.integer “getter_id”
t.integer “giver_id”
t.datetime “created_at”, null: false
t.datetime “updated_at”, null: false
endend
We can confirm that the tables have been created exactly as we wanted.
Class Associations
Next we need to define the associations between our classes:
- A giver has many things
- An getter has many things
- A thing belongs to a giver
- A thing belongs to a getter
Inside the app/models
folder, we find the class files.
class Giver < ApplicationRecord
end
We change this to:
class Giver < ApplicationRecord
has_many :things
end
and
class Getter < ApplicationRecord
end
to
class Getter < ApplicationRecord
has_many :things
end
and finally
class Thing < ApplicationRecord
end
to
class Thing < ApplicationRecord
belongs_to :giver
belongs_to :getter
end
This will allow the Thing
class to act as a join class between the Giver
and Getter
classes.
Controllers
All that’s left to do at this point is to define some actions in the respective controllers, and insert some basic HTML in our views folders. Many people may see the lack of any out-of-the-box controller functions as one of the weaknesses of the resource generator
. On the other hand, it gives you, the programmer, total control over how you want to define your actions. Here, I am including all of the basic CRUD functionality, except delete
, which I haven’t learned how to implement yet.
class GiversController < ApplicationController def index
@givers = all_givers
end def show
@giver = find_giver_id
end def new
@giver = Giver.new
end def create
@giver = Giver.new(post_params(:name, :location))
@giver.save
redirect_to giver_path(@giver)
end def edit
@giver = find_giver_id
end def update
@giver = find_giver_id
@giver.update(post_params(:name, :location))
redirect_to giver_path(@giver)
end
private def all_givers
Giver.all
end def find_giver_id
Giver.find(params[:id])
end def post_params(*args)
params.require(:giver).permit(*args)
endend
You can see that the other classes look pretty similar.
class GettersController < ApplicationController def index
@getters = all_getters
end def show
@getter = find_getter_id
end def new
@getter = Getter.new
end def create
@getter = Getter.new(post_params(:name, :location))
@getter.save
redirect_to getter_path(@getter)
end def edit
@getter = find_getter_id
end def update
@getter = find_getter_id
@getter.update(post_params(:name, :location))
redirect_to getter_path(@getter)
end
private def all_getters
Getter.all
end def find_getter_id
Getter.find(params[:id])
end def post_params(*args)
params.require(:getter).permit(*args)
endend
Similarly, for the Thing
class. However, as you can see in the show
method below, as the join class, Thing
has access to theGiver
and Getter
classes, as well as their attributes, name
and location
, which we’ll access in one of our views later.
class ThingsController < ApplicationController def index
@things = all_things
end def show
@thing = find_thing_id
@giver = @thing.giver
@getter = @thing.getter
end def new
@thing = Thing.new
end def create
@thing = Thing.new(post_params(:name, :point_value, :giver_id,
:getter_id))
@thing.save
redirect_to thing_path(@thing)
end def edit
@thing = find_thing_id
end def update
@thing = find_thing_id
@thing.update(post_params(:name, :point_value, :giver_id,
:getter_id))
redirect_to thing_path(@thing)
end
private def all_things
Thing.all
end def find_thing_id
Thing.find(params[:id])
end def post_params(*args)
params.require(:thing).permit(*args)
endend
Views
The last thing to do is to build out our views files. As you can see below, the resource generator was smart enough to give us some appropriately named folders, but we need to create html.erb
files (embedded Ruby) so our actions can render or redirect, as needed.
At their most basic, each folder will need index
, show
, new
, and edit
files.
<!-- givers/index.html.erb -->
<% @givers.each do |giver| %>
<%= link_to giver.name, giver_path(giver) %>
<% end %><!-- givers/show.html.erb -->
<%= link_to @giver.name, edit_giver_path(@giver) %></p>
<%= @giver.location %></p><!-- givers/new.html.erb -->
<%= form_for(@giver) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :location %>
<%= f.text_field :location %>
<%= f.submit %>
<% end %><!-- givers/edit.html.erb -->
<%= form_for(@giver) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :location %>
<%= f.text_field :location %>
<%= f.submit %>
<% end %>
Similarly.
<!-- getters/index.html.erb -->
<% @getters.each do |getter| %>
<%= link_to getter.name, getter_path(getter) %>
<% end %><!-- getters/show.html.erb -->
<%= link_to @getter.name, edit_getter_path(@getter) %></p>
<%= @getter.location %></p><!-- getters/new.html.erb -->
<%= form_for(@getter) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :location %>
<%= f.text_field :location %>
<%= f.submit %>
<% end %><!-- getters/edit.html.erb -->
<%= form_for(@getter) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :location %>
<%= f.text_field :location %>
<%= f.submit %>
<% end %>
And in the code for the show.html.erb
file below, we can see that the Thing
class has access to data in the adjoining Giver
and Getter
classes, in this case, the #name
method. Wow!
<!-- things/index.html.erb -->
<% @things.each do |thing| %>
<div><%= link_to thing.name, thing_path(thing) %></div>
<% end %><!-- things/show.html.erb -->
<%= link_to @thing.name, thing_path(@thing) %>
<%= link_to @getter.name, getter_path(@getter) if @getter %>
<%= link_to @giver.name, giver_path(@giver) if @giver %><!-- things/new.html.erb -->
<%= form_for(@thing) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :point_value %>
<%= f.text_field :point_value %>
<%= f.label :giver_id %>
<%= f.text_field :giver_id %>
<%= f.label :getter_id %>
<%= f.text_field :getter_id %>
<%= f.submit %>
<% end %><!-- things/edit.html.erb -->
<%= form_for(@thing) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :point_value %>
<%= f.text_field :point_value %>
<%= f.label :giver_id %>
<%= f.text_field :giver_id %>
<%= f.label :getter_id %>
<%= f.text_field :getter_id %>
<%= f.submit %>
<% end %>
Testing It Out
Now that all of our essential files are built out, we can make sure everything is working properly. First, we start our application by entering rails s
in our terminal. You should see something like this:
=> Booting Puma
=> Rails 5.1.6 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode…
* Version 3.11.4 (ruby 2.3.3-p222), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
This tells us that our application is running. We can open our browser, and enter the URI for our website: http://localhost:3000
. We can navigate to our /givers
, /getters
, and /things
routes to see their respective index pages. But all I see is blank webpages! That’s okay because we don’t have any data to display yet. Navigating over to one of our /new
pages that we created earlier, we can enter and submit data into the form. And it works! Similarly, entering data into our other forms, we can make sure our website is behaving as we intended. Playing around with our new creation, we can begin plotting and planning many improvements we would like to make. But, more on that some other time.
The Heart of Ruby Magic
A real functioning website will need plenty more HTML, CSS, and JavaScript to even begin to look usable. But our website’s skeleton is now standing, and there’s data flowing. That flow is managed by the resources macros in our config/routes.rb
file. And hence, where this generator gets its name, the resource generator
. For the flow of data in our application, these routes.rb
resources
are the heart of Ruby magic.
Rails.application.routes.draw do
resources :things
resources :getters
resources :givers
end
For more information: http://guides.rubyonrails.org/command_line.html#rails-generate