Rails Demo 1: Working with Models & Deploying to Heroku
So we’ve done “Hello World” and it was fun and easy. It turns out that Rails can do a lot for us right out of the box. Let’s take a look at how we can generate database-backed web pages.
-
Let’s assume we want to create a toy catalog. We begin by generating scaffolding for our Toys.
rails generate scaffold Toy name:string description:text manufacturer:string price:decimal
- This created a whole bunch of stuff:
- views,
- controllers,
- database migrations,
- routes (
rake routes
) - models: however, note that these models are backed by the database (sqlite by default).
- (Notice that the files, directories, and database table created have names that are automatically generated based on the name
Toy
. This is the “convention over configuration” pat of Rails.)
-
Take a look in
db/migrate
- you’ll see a file with a name something like20200123221127_create_toys.rb
that contains code specifying how to create theToys
table in our db. (Notice that the number describes the date and time at which the migration was generated.) In execute this code (and, thereby create the table), runrake db:migrate
-
Notice now that we have a file named
db/schema.rb
. This file describes the current structure of the database (i.e., theToys
table has been created). Notice that the comment at the top of the file tells us that the content is auto-generated. That means don’t edit this file yourself. Use migrations. If you run the application, you will see that we can now create and save toys. (Go to thetoys
route.) - Play with the app for a bit. Add/Edit a few toys.
- Let’s look at the changes to the database.
- run
rails dbconsole
- run
.tables
- run
pragma table_info(toys)
- Notice that an
id
column was automatically added.
- run
- The above command launches the sqlite3 interface. It might be occasionally helpful to use this interface to carefully examine the current state of the database; but, I wouldn’t use this interface to make changes. I would stick to the Rails interface for that.
- Let’s look at how all the different parts of the automatically generated scaffolding work together.
- Run
rake routes
. You will see something like this:
Prefix Verb URI Pattern Controller#Action toys GET /toys(.:format) toys#index POST /toys(.:format) toys#create new_toy GET /toys/new(.:format) toys#new edit_toy GET /toys/:id/edit(.:format) toys#edit toy GET /toys/:id(.:format) toys#show PATCH /toys/:id(.:format) toys#update PUT /toys/:id(.:format) toys#update DELETE /toys/:id(.:format) toys#destroy welcome_index GET /welcome/index(.:format) welcome#index welcome_goodbye GET /welcome/goodbye(.:format) welcome#goodbye root GET / welcome#index
- The web server uses the routing table to decide which code to run for a given path.
- Look at the first line (
/toys(.:format)
)- This line specifies that when the path is
/toys
and the verb isGET
, theindex
method on thetoys
controller is run. - Look at
app/controllers/toys_controller.rb
- The
index
method sets the variable@toys
to an array containing all Toys in the database. - Look at
app/views/toys/index.html.erb
. This file generates a table containing the data for the toys.- Notice the difference between
<% %>
and<%= %>
(with and without=
) - (We’ll come back and look at
link_to
later.)
- Notice the difference between
- Now, look at the model
app/models/toy.rb
- This class has no methods;
- but, it inherits from
ApplicationRecord
- The
ApplicationRecord
class provides the methods that interact with the database.- e.g., the class method
all
called by the index method.
- e.g., the class method
- If you want to test out some of these
ApplicationRecord
methods, you can use the Rails console:- Run
rails console
- Run
Toy.all
- Run
Toy.find(1)
- Run
Toy.create({name: 'Barbie', description: "A doll", manufacturer: 'Matell'})
- Now notice that the newly created toy appears in the app.
- Only do this type of thing in your development environment. Don’t screw up your test or production environments!
- Run
- This line specifies that when the path is
- Look at the 5th line (
/toys/:id(.format)
).- This line matches a path that contains
/toys/
followed by a number (e.g.,/toys/6
) when the verb isGET
- When this pattern is matched, rails runs the
show
method in the toys controller.- This body of
toys_controller#show
is empty. - Notice, however, the
before_action
method at the top of the controller.- It specifies that the
set_toy
method should be run beforeshow
,edit
,update
, anddestroy
params
is a hash that maps the placeholders in the routes (e.g,:id
) to the actual value appearing in the path.find
is a class method ofToy
that returns the record with the given id. (Remember, Rails automatically addedid
as a primary key).
- It specifies that the
- Look at
app/views/show.html.erb
- This body of
- This line matches a path that contains
- Are you impressed by how much happens without you having to write any code?
- Are you impressed by how much happens in methods with no (apparent) code in them?
- Look at the 3rd line
/toys/new
- The controller creates a new, “empty”
Toy
- The view uses a “helper”
- The
render
method calls the helper. - By convention, the parameter
form
is converted intoapp/views/toys/_form.html.erb
. Take a look: - This file generates an HTML form for entering the various Toy fields.
- The parameter
toy: @toy
passes a hash with a key oftoy
and a value of the newly created, empty@toy
object.- This hash then causes a local variable named
toy
to be initialized in the scope of the helper.
- This hash then causes a local variable named
- The
- The controller creates a new, “empty”
- If you look at the generated HTML, you will see that the form does a “
post
” to the\toys
route.- Looking back at the routes, this corresponds to to the
#create
method on the controller - Notice that both
#index
and#create
use a route named/toys
, but with different verbs.
- Looking back at the routes, this corresponds to to the
- The
create
route doesn’t return a view, but rather issues a 302 redirect to the page that displays the newly updated toy. - Look at
toys_controller#create
toy_params
is a method that sanitizes the submitted data.params
in an inherited controller method that returns both GET (i.e., query string) and POST values as a single hash. (Well, actually an object of typeActionController::Parameters
that implements many of the same methods.)- Rails allows complex/nested data to be sent. https://guides.rubyonrails.org/action_controller_overview.html#parameters
- Notice that the parameters contains an item with the key
authenticity_token
- If you look back at the form, you will see a hidden field of the same name.
- This is a security measure to prevent XSRF (i.e., getting you to click on a link in an email that sends the POST request).
- Editing a toy is similar.
- Calls “empty”
#edit
method (which really callsset_toy
) - The view uses the same
_form
helper. - You will notice that the code is identical. Rails “magic” figures out whether the toy passed in is new, or existing and sets the submit route accordingly. (https://stackoverflow.com/questions/43827116/how-does-form-for-knows-what-url-path-to-go-when-the-submit-button-is-clicked)
- Use Rails console to demonstrate
persisted?
method.
- Use Rails console to demonstrate
- Calls “empty”
- Open the Chrome developer tools, set “Preserve log” on the Network tab, and submit a change to a toy.
- Notice that the request method is
POST
, even though the intended route (update) isPATCH
. - HTML forms support only
GET
andPOST
. - Consequently, Rails has to use a work-around. Look at the HTML for the form.
- The
method
in the form tag ispost
; but, - There is a hidden filed named
_method
with a value ofpatch
- The
- Apparently,
PATCH
andDELETE
were latter additions to HTML, and nobody ever got around to updating HTML accordingly: https://softwareengineering.stackexchange.com/questions/114156/why-are-there-are-no-put-and-delete-methods-on-html-forms
- Notice that the request method is
- Links: We can use the routing table to create URLs for various links (back to the list of toys, to a specific toy, etc.) however
- It would result in a lot of manual fixing should you ever need to rename anything, and
- Why do the hard work when Rails will do it for you?
- Look at
edit.html.erb
- The
Back
link goes to thetoys_path
. - Notice that
toys
is the “prefix” for the route to the toys index. - The
Show
link goes go a specific toy. Rails takes the object and generates the correct URL.
- The
- Look at
index.html.erb
- Notice that
new_toy
is the “prefix” to the route for creating a new toy.
- Notice that
- Similar with
edit_toy_path
inshow.html.erb
Deploying Your Rails App to Heroku
You’ve worked so hard on your app, and it is now wonderful and ready to change the world — once you can figure out how to deploy it. Let’s take a look at how easy it is to deploy your app to Heroku. https://devcenter.heroku.com/articles/getting-started-with-rails6 Before we do this we first need to push our app out to a repo.
-
First, go out to
github.com
(orbitbucket.com
) and create a new empty repo. -
Next, if you haven’t done this yet, set up
git
in your dev environment (Those of you using cloud IDEs will need to do this for sure):git config --global user.name "YOUR NAME" git config --global user.email "YOUR EMAIL ADDRESS" git config --global core.editor vim
-
When you created your Rails project, Rails already initialized an empty git repo in your project directory; but, you still need to stage / commit your updates. Do this by typing in these commands in the top level directory of your project:
git add . git commit -m "initial commit"
-
Copy/paste these commands on your dev shell (note the repo details need to be replaced with your own repo info. Be sure to use the ssh URL, not the
https
url.):git remote add origin git@github.com:jengelsma/cautious-eureka.git git push -u origin master
-
Now it is time to deploy our app to Heroku. First go out and register for a free account on
heroku.com
. -
Next, note that Heroku uses a postgres database and your app is using sqlite by default.
-
Update
Gemfile
so that it downloads the postgres gem for the production environment. (I suspect you will have to add this group. If you already have a:production
group, you can just add thegem 'pg'
line to it.)group :production do gem ‘pg’ end
-
The sqlite3 gem is listed at the global level in the Gemfile by default. Move that line down under the
development
andtest
groups to avoid trying to install it on Heroku when you deploy. It will fail!group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] # Use sqlite3 as the database for Active Record gem 'sqlite3', '~> 1.4' end
-
-
Next, on Heroku, you will be using the postgres database instead of the default sqlite, so in the
config/database.yml
file you need to change the production database settings to be the following:production: adapter: postgresql encoding: unicode # For details on connection pooling, see Rails configuration guide # http://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> database: workspace_production username: workspace password: <%= ENV['WORKSPACE_DATABASE_PASSWORD'] %>
-
Then we need to run
bundle install
to updateGemfile.lock
. the--without production
asks bundle to ignore production gems for now:bundle install --without production
-
Since we updated
Gemfile
andGemfile.lock
we need to commit and push them.git commit -am "updated gem and db for production" git push origin master
-
Now, make sure the Heroku CLI tools are installed in your dev environment.
heroku version
This is the command to install Heroku in the CodeAnywhere environment
wget -O- https://toolbelt.heroku.com/install-ubuntu.sh | sh
(This page has instructions on installing Heroku locally: https://devcenter.heroku.com/articles/heroku-cli)
-
Are you having fun yet? Next we need to tell the Heroku toolbelt to login to our Heroku account
heroku login -i
(The -i
is for “interactive”. Interactive mode is necessary in
CodeAnywhere. If you are installing Heroku locally, you can omit
the -i
and log in through your browser.)
-
Then we add our ssh keys to our Heroku account:
heroku keys:add
-
And, of course, we create and rename the app instance from Heroku’s craftily generated name to our preferred name. Mine is jre-toys1; you can pick your own and substitute it in the command below.
heroku create heroku rename jre-toys1
-
And at long last, we are finally ready to deploy our app to Heroku using a good old fashioned
git push
.git push heroku master
-
And to cap off our amazing Heroku party, nothing beats a satisfying
rake db:migrate
. If you don’t do this, your app is not going to work because your DB scheme will not be up-to-date!heroku run rake db:migrate
-
At this point, you should be able to point your browser to your Heroku url (you can find that from the heroku portal or the command below) and be amazed at the ease in which you can now deploy a Rails app.
heroku info -s | grep web_url