iCyborg
iCyborg

Reputation: 4738

How to schedule Posts to publish in rails ?

I have a simple scaffold "Post", now I want to add scheduling to it so I can create a post and put date to it and Post will only get created(actually published) at that time.

I can see I need to use 'whenever' gem for cron and probably want to play with published_at field but I am little confused how to do it ?

Thanks

Upvotes: 0

Views: 2076

Answers (2)

Aleksei Chernenkov
Aleksei Chernenkov

Reputation: 1051

I can show you the code actually working in one of my projects as example:

# config/schedule.rb:
#  Once wheneverifyed - asks cron to call rake task `posts:publish` every minute.
#  See whenever docs at https://github.com/javan/whenever.
every 1.minute do
  rake 'posts:publish', environment: environment
end

# models/post.rb
class Post < ActiveRecord::Base

  # Every post has
  #  status - either PUBLISH_WAITING or PUBLISHED
  #  publish_at - date/time post need to be published
  #  published_at - actual time post was published (in this example it'll always be equal to publish_at)

  PUBLISH_WAITING, PUBLISHED = 'not_published', 'published'

  scope :publish_waiting, where(status: Post::PUBLISH_WAITING)
  scope :ready_for_publish, where('publish_at <= ?', Time.now)

  # Method to make post published!
  # Warn: it doesnt check if it is time to publish! Just do that.
  def publish_now!
    self.status = Post::PUBLISHED
    self.published_at = self.publish_at
    save!
  end
end

# lib/tasks/posts.rake
#  Define rake `posts:publish` task,
#  which when called searches through all not published
#  (but ready to be published) posts and publishes them.
#  You can call that task manually from CLI `$ rake posts:publish` to check.
namespace :posts do
  desc "Publish posts with cron on certain time"
  task :publish => :environment do
    Post.publish_waiting.ready_for_publish.find_each do |post|
      post.publish_now!
    end
  end
end

Upvotes: 2

kristinalim
kristinalim

Reputation: 3459

I think it's better to just add a published_at field because you actually already have the record there.

# Add columns. (DB migration)
add_column :posts, :draft, :boolean, :default => true
add_column :posts, :published_at, :datetime

# Define scopes. (model)
#
# These set up shortcuts that will allow you to do:
#   Post.draft
# instead of this in many places:
#   Post.where(:draft => true)
# Or:
#   Post.published
# instead of:
#   Post.where(:draft => false).where('published_at <= ?', Time.zone.now)
scope :draft, where(:draft => true)
scope :published, proc {
  where(:draft => false).where('published_at <= ?', Time.zone.now)
}

# If the user does not set published_at but sets post to public, automatically
# set published_at to the current time before persisting the record to the
# DB. (model)
before_save :ensure_published_at, :unless => :draft?
protected
def ensure_published_at
  # Set it to current time if none has been specified.
  self.published_at ||= Time.zone.now
end

# Might be helpful if you have a "Publish" action. (model)
#
# This already sets draft to false and published at to the default (because of
# ensure_published_at above:
#   post.publish!
def publish!
  self.draft = false
  self.save!
end

# Finally, fetch the published posts: (controller action)
# It's best to also add pagination and ordering.
@posts = Post.published

Upvotes: 3

Related Questions