# The AsNewSan mixin makes it easier to create associations on a new Active # Record instance. # # Use the +as_new+ method to instantiate new empty objects that are immediately # saved to the database with a special flag marking them as new. Because new # instances are already stored in the database, you always have an +id+ # available for creating associations. This means you can use the same views # and controller logic for new and edit actions which is especially helpful # when you are creating new associated objects using Ajax calls. # # An Active Record class using this mixin needs to have an +as_new+ boolean # column with the default value set to +false+ and a +created_at+ column. # # See AsNewSan::ClassMethods for documentation on the class methods added to # ActiveRecord::Base. # # Example: # # class CreateMessages < ActiveRecord::Migration # def self.up # create_table :messages do |t| # t.string :body # t.boolean :as_new, :default => false # t.timestamps # end # end # end # # class CreateRecipients < ActiveRecord::Migration # def self.up # create_table :recipient do |t| # t.integer :message_id # t.string :name # end # end # end # # class Message < ActiveRecord::Base # include AsNewSan # has_many :recipients, :dependent => :destroy # end # # class Recipient < ActiveRecord::Base # belongs_to :message # end # # class MessagesController < ApplicationController # def index # # Remove all records older than 1 week that have the `as_new` property set to `true`. # Message.collect_garbage! # # # The mixin adds an `exclude_as_new` named_scope and makes `#find` and `#count` use it # # out of the box. # @messages = Message.find(:all) # finds only records that have `as_new` set to `false` # @messages = Message.count(:all) # counts only records that have `as_new` set to `false` # # # If you need to find or count all records, even the ones with `as_new` set to `true`, # # the mixin also comes with the `find_including_new_records` and `count_including_new_records` # # class methods, which behave the same as ActiveRecord::Base#find and ActiveRecord::Base.count. # @messages = Message.find_including_new_records(:all) # finds all records # @messages = Message.count_including_new_records(:all) # counts all records # # # If you need to reload a record that has beed created with `as_new`, `reload` won't work. # # You need to use `#find_including_new_records`. # @message = Message.as_new # @message.reload # => ActiveRecord::RecordNotFound # @message = Message.find_including_new_records(@message.id) # end # # def new # # Create and save a message with the `as_new` property set to `true`. # # # # Recipients are created and associated using Ajax calls on the recipients controller. # @message = Message.as_new # end # # def update # # The mixin comes with a `before_update` filter that sets the `as_new` column to # # `false` before saving the updated record. # @message = Message.find_including_new_records(params[:id]) # @message.update_attributes(params[:message]) # # # So, at this point the record is not marked `as_new` anymore. # end # end module AsNewSan def self.included(base) #:nodoc: base.before_update(:unset_as_new) base.named_scope :exclude_as_new, { :conditions => { :as_new => false } } base.extend(ClassMethods) end module ClassMethods # Instantiates a new object, sets the as_new column to +true+ and saves the # associated record without validation. # # If the record is never updated, or its as_new property explicitely set to # +false+, it will be destroyed after 1 week when the collect_garbage! # class method is called. # # See ActiveRecord::Base.new for all the other options that can be # passed to as_new. def as_new(attributes = {}) returning( new(attributes.merge( :as_new => true )) ) { |record| record.save(false) } end # Finds and destroys all the records where the as_new column is set to # +true+ and +created_at+ is longer than 1 week ago. def collect_garbage! find_including_new_records(:all, :conditions => ['as_new = ? AND created_at < ?', true, Time.now - 1.week]).each(&:destroy) end # Works the same as ActiveRecord::Base.find but only returns # records for which the as_new column is set to +false+. def find_excluding_new_records(*args) exclude_as_new.find_including_new_records(*args) end # Works the same as ActiveRecord::Base.count but only returns # records for which the as_new column is set to +false+. def count_excluding_new_records(*args) exclude_as_new.count_including_new_records(*args) end def self.extended(klass) class << klass alias_method :find_including_new_records, :find alias_method :find, :find_excluding_new_records alias_method :count_including_new_records, :count alias_method :count, :count_excluding_new_records end end end # Returns a boolean indicating whether or not this is a +as_new+ record. def as_new_record? self.as_new end private def unset_as_new self.as_new = false true end end