$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
module ArPublishControl
VERSION = '0.0.5'
# This is a gem version of http://github.com/avdgaag/acts_as_publishable ( a Rails plugin)
# Thanks to Avdaag for his awesome, super readable code which I ripped off for this gem.
#
# Specify this act if you want to show or hide your object based on date/time settings. This act lets you
# specify two dates two form a range in which the model is publicly available; it is unavailable outside it.
#
# == Usage
#
# You can add this behaviour to your model like so:
#
# class Post < ActiveRecord::Base
# publish_control
# end
#
# Then you can use it as follows:
#
# post = Post.create(:title => 'Hello world')
# post.published? # => true
#
# post.publish!
# post.published? # => true
#
# post.unpublish!
# post.published? # => false
#
# You can use two named_scopes to find the published or unpublished objects.
# You can chain them with other scopes and use them on association collections:
#
# Post.all.size # => 15
# Post.published.size # => 10
# Post.unpublished.size # => 5
# @post.comments.published
#
# Post.recent.published
#
# There's a third named_scope that you can pass a boolean in order to find only published items or all of them
# This is useful in controller for permission-based publish control
#
# @post = Post.published.find(params[:id])
# @comments = @post.comments.only_published( logged_in? )
#
module Publishable
def self.included(base) #:nodoc:
base.extend ClassMethods
end
module ClassMethods
# == Configuration options
#
# Right now this plugin has only one configuration option. Models with no publication dates
# are by default published, not unpublished. If you want to hide your model when it has no
# explicit publication date set, you can turn off this behaviour with the
# +publish_by_default+ (defaults to true) option like so:
#
# class Post < ActiveRecord::Base
# publish_control :publish_by_default => false
# end
#
# == Database Schema
#
# The model that you're publishing needs to have two special date attributes:
#
# * publish_at
# * unpublish_at
#
# These attributes have no further requirements or required validations; they
# just need to be datetime-columns.
#
# You can use a migration like this to add these columns to your model:
#
# class AddPublicationDatesToPosts < ActiveRecord::Migration
# def self.up
# add_column :posts, :publish_at, :datetime
# add_column :posts, :unpublish_at, :datetime
# end
#
# def self.down
# remove_column :posts, :publish_at
# remove_column :posts, :unpublish_at
# end
# end
#
def publish_control(options = { :publish_by_default => true })
# don't allow multiple calls
return if self.included_modules.include?(ArPublishControl::Publishable::InstanceMethods)
send :include, ArPublishControl::Publishable::InstanceMethods
named_scope :published, lambda{{:conditions => published_conditions}}
named_scope :unpublished, lambda{{:conditions => unpublished_conditions}}
named_scope :upcoming, lambda{{:conditions => upcoming_conditions}}
named_scope :expired, lambda {{:conditions => expired_conditions}}
named_scope :published_only, lambda {|*args|
bool = (args.first.nil? ? true : (args.first)) # nil = true by default
bool ? {:conditions => published_conditions} : {}
}
before_validation :init_publish_date # don't allow empty publish_at
before_create :publish_by_default if options[:publish_by_default]
end
# Takes a block whose containing SQL queries are limited to
# published objects. You can pass a boolean flag indicating
# whether this scope should be applied or not--for example,
# you might want to disable it when the user is an administrator.
# By default the scope is applied.
#
# Example usage:
#
# Post.published_only(!logged_in?)
# @post.comments.published_only(!logged_in?)
#
protected
# returns a string for use in SQL to filter the query to unpublished results only
# Meant for internal use only
def unpublished_conditions
t = Time.now
["(#{table_name}.is_published = ? OR #{table_name}.publish_at > ?) OR (#{table_name}.unpublish_at IS NOT NULL AND #{table_name}.unpublish_at < ?)",false,t,t]
end
# return a string for use in SQL to filter the query to published results only
# Meant for internal use only
def published_conditions
t = Time.now
["(#{table_name}.is_published = ? AND #{table_name}.publish_at <= ?) AND (#{table_name}.unpublish_at IS NULL OR #{table_name}.unpublish_at > ?)",true,t,t]
end
def upcoming_conditions
t = Time.now
["(#{table_name}.is_published = ? AND #{table_name}.publish_at > ?)",true,t]
end
def expired_conditions
t = Time.now
["(#{table_name}.unpublish_at IS NOT NULL AND #{table_name}.unpublish_at < ?)",t]
end
end
module InstanceMethods
def publish_at
read_attribute(:publish_at) || Time.now
end
# Publish at is NOW if not set
def init_publish_date
write_attribute(:publish_at, Time.now) if publish_at.nil?
end
# ActiveRecrod callback fired on +before_create+ to make
# sure a new object always gets a publication date; if
# none is supplied it defaults to the creation date.
def publish_by_default
write_attribute(:is_published, true) if is_published.nil?
end
# Validate that unpublish_at is greater than publish_at
# publish_at must not be nil
def validate
if unpublish_at && publish_at && unpublish_at <= publish_at
errors.add(:unpublish_at,"should be greater than publication date or empty")
end
end
public
# Return whether the current object is published or not
def published?
(is_published? && (publish_at <=> Time.now) <= 0) && (unpublish_at.nil? || (unpublish_at <=> Time.now) >= 0)
end
def upcoming?
(is_published? && publish_at > Time.now)
end
def expired?
(!unpublish_at.nil? && unpublish_at < Time.now)
end
# Indefinitely publish the current object right now
def publish
return if published?
self.is_published = true
self.publish_at = Time.now
self.unpublish_at = nil
end
# Same as publish, but immediatly saves the object.
# Raises an error when saving fails.
def publish!
publish
save!
end
# Immediatly unpublish the current object
def unpublish
return unless published?
self.is_published = false
end
# Same as unpublish, but immediatly saves the object.
# Raises an error when saving files.
def unpublish!
unpublish
save!
end
end
end
end
require 'rubygems'
require 'active_record'
ActiveRecord::Base.send :include, ArPublishControl::Publishable