Time for A Boolean
Sally: Hey, we need to add a flag to Post
Jean: What for?
Sally: Well, we want to let users "delete" posts, but not actually lose the data.
Jean: Sounds reasonable. But what about later, when we have to know when a post was deleted?
Sally: That's a good point, but if we add a timestamp now we have to write all sorts of methods to keep a nice interface on Post...
Jean: Time for A Boolean!
Wait, what?
rails generate migration AddDeletedAtToPosts deleted_at:
class Post < ActiveRecord::Base
time_for_a_boolean :deleted
...
end
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
if @post.deleted?
raise ActiveRecord::RecordNotFound
end
end
def destroy
post = Post.find(params[:id])
post.deleted = true
post.save
redirect_to posts_url
end
end
You keep on saying things and I don't get it.
Okay, let's take a look at what happens.
When we call time_for_a_boolean :deleted in the Post class definition, several
methods are defined:
| Method | Description |
|---|---|
Post#deleted |
true if Post#deleted_at is set to a time before Time.current, false otherwise |
Post#deleted? |
Alias for Post#deleted |
Post#deleted= |
Sets the timestamp to Time.current if the new value is true, and nil otherwise |
These methods allow you to use a timestamp as you would a boolean value in your application.
Okay... why?
- Audit for when a flag was set. Future you wants this.
COUNT(posts.deleted_at)gives you the count of deleted posts, which is useful when writing a report. Define and usePost.deleted.countwhen you have Ruby available.
Other Options
Using a different attribute name
If you have a date or time column that does not follow the attribute_at convention,
you can specify the attribute name:
class User < ActiveRecord::Base
time_for_a_boolean :expires, :expires_on
end
This is especially useful when using date only columns.
Scopes to filter records
ActiveRecord Scopes
can be used to filter records based on the attribute. These are not defined by
default, but can be added by passing the scopes: true option:
class Post < ActiveRecord::Base
time_for_a_boolean :deleted, scopes: true
...
end
This will define the following scopes:
| Scope | Description |
|---|---|
Post.deleted |
Returns all posts where deleted_at is set to a time before Time.current |
Post.not_deleted |
Returns all posts where deleted_at is unset or in the future |