module DataMapper module Is # = dm-is-published # # This plugin makes it very easy to add different states to your models, like 'draft' vs 'live'. # By default it also adds validations of the field value. # # Originally inspired by the Rails plugin +acts_as_publishable+ by fr.ivolo.us. # # # == Installation # # # Add GitHub to your RubyGems sources # $ gem sources -a http://gems.github.com # # $ (sudo)? gem install kematzy-dm-is-published # # NB! Depends upon the whole DataMapper suite being installed, and has ONLY been tested with DM 0.10.0 (next branch). # # # == Getting Started # # First of all, for a better understanding of this gem, make sure you study the 'dm-is-published/spec/integration/published_spec.rb' file. # # ---- # # Require +dm-is-published+ in your app. # # require 'dm-core' # must be required first # require 'dm-is-published' # # Lets say we have an Article class, and each Article can have a current state, # ie: whether it's Live, Draft or an Obituary awaiting the death of someone famous (real or rumored) # # # class Article # include DataMapper::Resource # property :id, Serial # property :title, String # ... # # is :published # # end # # Once you have your Article model we can create our Articles just as normal # # Article.create(:title => 'Example 1') # # # The instance of Article.get(1) now has the following things for free: # # * a :publish_status attribute with the value 'live'. Default choices are [ :live, :draft, :hidden ]. # # * :is_live?, :is_draft? or :is_hidden? methods that returns true/false based upon the state. # # * :save_as_live, :save_as_draft or :save_as_hidden converts the instance to the state and saves it. # # * :publishable? method that returns true for models where is :published has been declared, # but false for those where it has not been declared. # # # The Article class also gets a bit of new functionality: # # Article.all(:draft) => finds all Articles with :publish_status = :draft # # # Article.all(:draft, :author => @author_joe ) => finds all Articles with :publish_status = :draft and author == Joe # # # Todo:: add more documentation here... # # # == Usage Scenarios # # In a Blog/Publishing scenario you could use it like this: # # class Article # ...... # # is :published :live, :draft, :hidden # end # # Whereas in another scenario - like in a MenuItem model for a Restaurant - you could use it like this: # # class MenuItem # ...... # # is :published :on, :off # the item is either on the menu or not # end # # # == RTFM # # As I said above, for a better understanding of this gem/plugin, make sure you study the 'dm-is-published/spec/integration/published_spec.rb' file. # # # == Errors / Bugs # # If something is not behaving intuitively, it is a bug, and should be reported. # Report it here: http://datamapper.lighthouseapp.com/ # # === Credits # # Copyright (c) 2008-05-07 [Kematzy at gmail] # # Loosely based on the ActsAsPublishable plugin by [http://fr.ivolo.us/posts/acts-as-publishable] # # == Licence # # Released under the MIT license. module Published ## # method that adds a basic published status attribute to your model # # == params # # * +states+ - an array of 'states' as symbols or strings. ie: :live, :draft, :hidden # # ==== Examples # # # is :published :on, :off # # is :published %w(a b c d) # # # @api public def is_published(*args) # set default args if none passed in args = [:live, :draft, :hidden] if args.blank? args = args.first if args.first.is_a?(Array) # the various publish states accepted. @publish_states = args.collect{ |state| state.to_s.downcase.to_sym } @publish_states_for_validation = args.collect{ |state| state.to_s.downcase } extend DataMapper::Is::Published::ClassMethods include DataMapper::Is::Published::InstanceMethods # do we have a :publish_status declared if properties.any?{ |p| p.name == :publish_status } # set default value to first value in declaration or the given value d = properties[:publish_status].default.blank? ? @publish_states.first.to_s : properties[:publish_status].default # set the length to 10 if missing or if shorter than 5, otherwise use the given value l = 5 if properties[:publish_status].length.blank? l = (properties[:publish_status].length <= 10 ? 10 : properties[:publish_status].length) property :publish_status, String, :length => l, :default => d.to_s else # no such property, so adding it with default values property :publish_status, String, :length => 10, :default => @publish_states.first.to_s end # create the state specific instance methods self.publish_states.each do |state| define_method("is_#{state}?") do self.publish_status == state.to_s end define_method("save_as_#{state}") do self.publish_status = state.to_s save end end # ensure we are always saving publish_status values as strings before :valid? do self.publish_status = self.publish_status.to_s if self.respond_to?(:publish_status) end validates_within :publish_status, :set => @publish_states_for_validation, :message => "The publish_status value can only be one of these values: [ #{@publish_states_for_validation.join(', ')} ]" end module ClassMethods attr_reader :publish_states, :publish_states_for_validation ## # Overriding the normal #all method to add some extra sugar. # # ==== Examples # # Article.all => returns all Articles as usual # # Article.all( :publish_status => :live ) => returns all Articles with :publish_status == :lve # # Article.all(:draft) => returns all Articles with :publish_status == :draft # # Article.all(:draft, :author => @author_joe ) => finds all Articles with :publish_status = :draft and author == Joe # # # @api public/private def all(*args) # incoming can either be: # -- nil (nothing passed in, so just use super ) # -- (Hash) => all(:key => "value") ( normal operations, so just pass on the Hash ) # -- (Symbol) => all(:draft) ( just get the symbol, ) # -- (Symbol, Hash ) => :draft, { extra options } if args.empty? return super elsif args.first.is_a?(Hash) return super(args.first) else # set the from args Array, remove first item if Symbol, and then check for 2nd item's presence state, options = args.shift.to_s, (args.blank? ? {} : args.first) if args.first.is_a?(Symbol) # puts " and state=[#{state}] and options=[#{options.class}] options.inspect=[#{options.inspect}] [#{__FILE__}:#{__LINE__}]" return super({ :publish_status => state }.merge(options) ) end end end # ClassMethods module InstanceMethods ## # Ensuring all models using this plugin responds to publishable? with true. # # ==== Examples # # @published_model.publishable? => true # # @api public def publishable? true end end # InstanceMethods module ResourceInstanceMethods ## # Ensuring all models NOT using this plugin responds to publishable? with false. # # ==== Examples # # @unpublished_model.publishable? => false # # @api public def publishable? false end end #/module ResourceInstanceMethods end # Published end # Is Model.append_extensions(Is::Published) end # DataMapper