Boolean vs Datetime

Episode #504 by Teacher's Avatar David Kimura

Summary

In this episode, we look at refactoring an existing application where it uses a boolean to determine if a post is published or unpublished. However, this feature has its limitations, so we change the functionality to work off of a datetime column instead.
rails model business logic 13:33

Chapters

  • Introduction (0:00)
  • Looking at the existing application (1:15)
  • Migration (1:44)
  • Updating the Controller (7:16)
  • Updating the Model (7:48)
  • Updating the view (9:59)
  • Final thoughts (11:39)

Resources

Source - https://github.com/driftingruby/504-boolean-vs-datetime

Honeybadger is a performance monitoring and error tracking tool that combines the best monitoring features into one simple interface that works with all the frameworks you use and comes with fantastic support from a small team of passionate developers. With error tracking, performance and uptime monitoring, log management, dashboards, and more, Honeybadger has everything you need to gain real-time insights into the health of your applications.Keep your apps healthy and your customers happy with Honeybadger! It’s free to get started, and setup takes less than five minutes. Start monitoring today →
Download Source Code

Summary

# Terminal
rails g migration change_published_to_published_on_posts

# db/migrate/20250406012935_change_published_to_published_on_posts.rb
class ChangePublishedToPublishedOnPosts < ActiveRecord::Migration[8.0]
  def up
    add_column :posts, :published_at, :datetime
    execute <<-SQL
      UPDATE posts
      SET published_at = updated_at
      WHERE published = TRUE
    SQL
    remove_column :posts, :published
    add_index :posts, :published_at
  end

  def down
    add_column :posts, :published, :boolean, default: false
    execute <<-SQL
      UPDATE posts
      SET published = (published_at IS NOT NULL AND published_at <= CURRENT_TIMESTAMP)
    SQL
    remove_column :posts, :published_at
  end
end

# app/controllers/posts_controller.rb
def index
  @posts = Post.published.order(published_at: :desc)
end

def post_params
  params.expect(post: [ :title, :published_at, :content ])
end

# app/models/post.rb
class Post < ApplicationRecord
  has_rich_text :content

  scope :published, -> { where(published_at: ..Time.current) }
  scope :scheduled, -> { where(published_at: Time.current..) }
  # scope :scheduled, -> { where("published_at > ?", Time.current) }
  scope :unpublished, -> { where(published_at: nil) }

  def published?
    published_at.present? && published_at.past?
  end

  def scheduled?
    published_at.present? && published_at.future?
  end
end

# app/views/posts/_form.html.erb
<div class="my-5 flex items-center gap-2">
  <%= form.label :published_at %>
  <%= form.datetime_field :published_at %>
</div>

# app/views/posts/_post.html.erb
<div>
  <%= "Published" if post.published? %>
  <%= "Scheduled" if post.scheduled? %>
  <%= "Unpublished" if !post.scheduled? && !post.published? %>
</div>