Rails: Views & Forms, Model Associations

Advanced Models

Validations

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!

presence

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

length

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

uniqueness

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

numericality

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.

Checking errors

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"]

Displaying error messages on forms

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 %>

Associations

How do we tell Rails that two models are related to each other? We use model associations.

This is a common use case:

When to use Associations

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.

Foreign Key

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