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.rbthat contains code specifying how to create theToystable 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., theToystable 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 thetoysroute.) - 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
idcolumn 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
/toysand the verb isGET, theindexmethod on thetoyscontroller is run. - Look at
app/controllers/toys_controller.rb - The
indexmethod sets the variable@toysto 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_tolater.)
- Notice the difference between
- Now, look at the model
app/models/toy.rb- This class has no methods;
- but, it inherits from
ApplicationRecord - The
ApplicationRecordclass provides the methods that interact with the database.- e.g., the class method
allcalled by the index method.
- e.g., the class method
- If you want to test out some of these
ApplicationRecordmethods, 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
showmethod in the toys controller.- This body of
toys_controller#showis empty. - Notice, however, the
before_actionmethod at the top of the controller.- It specifies that the
set_toymethod should be run beforeshow,edit,update, anddestroy paramsis a hash that maps the placeholders in the routes (e.g,:id) to the actual value appearing in the path.findis a class method ofToythat returns the record with the given id. (Remember, Rails automatically addedidas 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
rendermethod calls the helper. - By convention, the parameter
formis 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: @toypasses a hash with a key oftoyand a value of the newly created, empty@toyobject.- This hash then causes a local variable named
toyto 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\toysroute.- Looking back at the routes, this corresponds to to the
#createmethod on the controller - Notice that both
#indexand#createuse a route named/toys, but with different verbs.
- Looking back at the routes, this corresponds to to the
- The
createroute doesn’t return a view, but rather issues a 302 redirect to the page that displays the newly updated toy. - Look at
toys_controller#createtoy_paramsis a method that sanitizes the submitted data.paramsin 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::Parametersthat 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”
#editmethod (which really callsset_toy) - The view uses the same
_formhelper. - 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
GETandPOST. - Consequently, Rails has to use a work-around. Look at the HTML for the form.
- The
methodin the form tag ispost; but, - There is a hidden filed named
_methodwith a value ofpatch
- The
- Apparently,
PATCHandDELETEwere 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
Backlink goes to thetoys_path. - Notice that
toysis the “prefix” for the route to the toys index. - The
Showlink goes go a specific toy. Rails takes the object and generates the correct URL.
- The
- Look at
index.html.erb- Notice that
new_toyis the “prefix” to the route for creating a new toy.
- Notice that
- Similar with
edit_toy_pathinshow.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
gitin 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
httpsurl.):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
Gemfileso that it downloads the postgres gem for the production environment. (I suspect you will have to add this group. If you already have a:productiongroup, 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
developmentandtestgroups 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.ymlfile 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 installto updateGemfile.lock. the--without productionasks bundle to ignore production gems for now:bundle install --without production -
Since we updated
GemfileandGemfile.lockwe 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 versionThis 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