You follow these same four steps every time you want to create a view. You don't have to repeat a step if you've already done it, though!
Create the controller class
# app/controllers/places_controller.rb
class PlacesController < ApplicationController
end
Create the action method
# app/controllers/places_controller.rb
class PlacesController < ApplicationController
def index
end
end
Create the routes for the controller & action method
# config/routes.rb
resources :places
Create the view template file.
<!-- app/views/places/index.html.erb -->
<h1>Index!</h1>
We've talked a lot about index
and show
, but what about the rest of the methods?
HTTP Verb | Path | Controller#Action | Used for |
---|---|---|---|
GET | /articles | articles#index | display a list of all articles |
GET | /articles/new | articles#new | return an HTML form for creating a new article |
POST | /articles | articles#create | create a new article |
GET | /articles/:id | articles#show | display a specific article |
GET | /articles/:id/edit | articles#edit | return an HTML form for editing a article |
PATCH/PUT | /articles/:id | articles#update | update a specific article |
DELETE | /articles/:id | articles#destroy | delete a specific article |
The new
and create
methods are paired together. The new
method will display an empty form to the user. The create
method is the submission action of the form. When a user clicks submit, the form will POST to the create
method.
new
create
new
page.Just like new
/create
, the edit
and update
methods are paired together in the same way. The edit
method displays a form for the user, and the update
method.
edit
update
show
pageedit
page.The destroy method simply deletes a model.
index
page.Rails uses the form_for
helper to quickly and easily generate a form. Rails understands the state of a model, and will correctly choose what the destination of the form is, and the form method.
# app/controllers/places_controller.rb
class PlacesController < ApplicationController
def new
@place = Place.new
end
end
<!-- app/views/places/new.html.erb -->
<%= form_for @place do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_area :description %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
When creating forms, make sure that you're using strong parameters in your controller! Rails will give you an error if you don't, so it's hard to forget.
However, it is easy to forget to update your strong parameters when you add columns to your models.
# app/controllers/places_controller.rb
# ...
private
def place_params
params.require(:place).permit(:name, :description)
end
end
If you add another field to the form, make sure you update your strong parameters so you don't filter out data you want to save!
What if you want to share code between different views? The new.html.erb
and edit.html.erb
forms are usually identical. Remember, don't repeat yourself? That's what view partials are for.
Partials are just a regular view template, except their names always start with an underscore, like _form.html.erb
.
Here's how we use that with the new
and edit
actions.
<!-- app/views/places/new.html.erb -->
<%= render 'form' %>
<!-- app/views/places/edit.html.erb -->
<%= render 'form' %>
<!-- app/views/places/_form.html.erb -->
<%= form_for @place do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_area :description %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
As you can see, a view partial allows you to share view code between multiple views by using the render
method.
HTML is a repetitive language though, because it defines the structure of a document. Don't overuse partials. You should think of a partial when you have a kind of component of a page, like a sidebar, a form, a comments box, etc.
The HTML on each view is very basic. So where does all of that extra HTML, like the head and body tag, come from in our rendered web pages? Layouts!
Layouts are simply a "top level" view that Rails will automatically render and put your code inside of.
The default layout is app/views/layouts/application.html.erb
. Any application-specific CSS, JavaScript, Google Fonts, etc. should go in this file, along with any site-wide nav bars, login buttons, etc.
You usually don't need more than one layout, and by the time you do, you'll know a lot more about Rails.
From the Rails Guides on Active Record Validations:
Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address. Model-level validations are the best way to ensure that only valid data is saved into your database.
Validations also us to display error messages on our forms if the users enter invalid data.
We'll outline a few examples, but you'll have to dig into the Active Record Validations for all of the options; there are a lot!
If we want to require the name
column for our Place
model, we would do this:
class Place < ActiveRecord::Base
validates :name, presence: true
end
Sometimes we want to set a maximum or minimum value for a text field. We might want our Place
model to require the name to be at least 4 characters, but no more than 50.
class Place < ActiveRecord::Base
validates :name, length: { minimum: 4, maximum: 50 }
end
It's very common for us to require that data in a column be unique. The most common example is a username or an email address that a user uses to log in. If two users had the same username, we'd have no way of knowing who to log in!
In our Lekker Plekke app, we don't want users to enter the same location twice, so we'll require that the name be unique.
class Place < ActiveRecord::Base
validates :name, uniqueness: true
end
We use numbers in our databases a lot. For Lekker Plekke, we're storing the number of likes that a post has. What does 4.5
likes mean? What about -5
likes? That is meaningless data, so we'll create validation that requries our data to be a specific type of number.
class Place < ActiveRecord::Base
validates :likes, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
end
This ensures that our likes are always a whole number, and that they're always 0 or greater.
If your model tries to save and fails, Rails will update the .errors
object with information about the errors that happened during save.
>> place = Place.new
>> place.save
=> false
>> place.errors
=> #<ActiveModel::Errors:0x007fa35bfe8908
@base=
#<Place:0x007fa35f959b88
id: nil,
name: nil,
description: nil,
created_at: nil,
updated_at: nil,
address: nil>,
@messages={:name=>["can't be blank"]}>
# Access the errors by using column names as a hash key:
>> place.errors[:name]
=> ["can't be blank"]
# Get the full error messages for display with `.full_messages`
>> place.errors.full_messages
=> ["Name can't be blank"]
You should always display errors in the user input in the form, so the users know that something was wrong. There are many ways of making this prettier, but here's a basic "good enough" way of doing it.
<%= form_for @place do |f| %>
<% if @place.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@place.errors.count, "error") %> prohibited this place from being saved:</h2>
<ul>
<% @place.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<!-- your form stuff here -->
<% end %>
How do we tell Rails that two models are related to each other? We use model associations.
This is a common use case:
These are ordered by most common to least common.
association | description |
---|---|
has_many | When one object has many of another, like a user's photos |
belongs_to | Always the other side of a has_many or has_one association |
has_many :through | When one object has many of another, and there's a model connecting them |
has_and_belongs_to_many | When both objects have_many of each other. Movies have_many genres, and Genres have_many movies. |
has_one | An association where the model only has one. Users have_one UserProfile |
has_one :through | An association where the model only has one, and there's another model connecting them. |
A foreign key is the column that contains a model's id that references another object. The foreign key is always named othermodel_id
. The foreign key is always on the table that belongs to another model.
This is easier to explain with an example. If our Movies have many Reviews, this is what it would look like.
# app/models/movie.rb
class Movie
has_many :reviews
end
# app/models/review.rb
class Review
belongs_to :movie
end
Because our Review model has the belongs_to
, it is the model with the foreign key. This is always how it works. Reviews must know which movie they belong to. Let's peek at the schema.rb
file to see how it looks in the database.
# db/schema.rb
create_table "movies", force: :cascade do |t|
t.string "title"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "reviews", force: :cascade do |t|
t.integer "movie_id"
t.text "body"
end