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