Permalink
Browse files

initial commit. Migrated from other projects written a year or two ago.

  • Loading branch information...
0 parents commit 353c5ce7cb9477aea6fb5cf9f5b7202cf3c2cc31 @krisr committed Aug 30, 2009
@@ -0,0 +1 @@
+rdoc/*
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Kris Rasmussen
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5 README
@@ -0,0 +1,5 @@
+== ActsAsEventable
+
+ActsAsEventable makes it easy to log events corresponding to actions taken on active record models.
+
+Copyright (c) 2009 Kris Rasmussen, released under the MIT license
@@ -0,0 +1,11 @@
+require 'rake'
+require 'spec/rake/spectask'
+
+desc 'Default: run specs.'
+task :default => :spec
+
+desc 'Run the specs'
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
+ t.spec_files = FileList['spec/**/*_spec.rb']
+end
@@ -0,0 +1,11 @@
+class ActsAsEventableMigrationGenerator < Rails::Generator::Base
+ def manifest
+ record do |m|
+ m.migration_template 'migration.rb', 'db/migrate'
+ end
+ end
+
+ def file_name
+ "acts_as_eventable_migration"
+ end
+end
@@ -0,0 +1,29 @@
+class ActsAsEventableMigration < ActiveRecord::Migration
+ def self.up
+ create_table :events do |t|
+ t.string :eventable_type, :null => false
+ t.integer :eventable_id
+
+ t.integer :user_id, :null => false
+
+ # this is for when the action is destroy
+ t.text :eventable_attributes
+
+ # this is for identifying and clustering batch updates
+ t.integer :batch_parent_id
+ t.integer :batch_size, :null => false, :default => 1
+
+ t.string :action, :null => false
+
+ t.datetime :created_at, :null => false
+ end
+
+ add_index :events, [:eventable_type, :eventable_id]
+ add_index :events, [:batch_parent_id, :user_id]
+ add_index :events, :batch_parent_id
+ end
+
+ def self.down
+ drop_table :events
+ end
+end
11 init.rb
@@ -0,0 +1,11 @@
+# we do not require event because rails will autoload it
+# if we require it here it will not be reloaded in development
+# mode and that will cause problems with the User reference
+require 'acts_as_eventable_options'
+require 'acts_as_eventable_action_controller'
+require 'acts_as_eventable_active_record'
+
+ActiveRecord::Base.send(:include, ActsAsEventable::ActiveRecord)
+ActionController::Base.send(:include, ActsAsEventable::ActionController)
+
+ActiveSupport::Dependencies.load_once_paths.delete lib_path
@@ -0,0 +1 @@
+# Install hook code here
@@ -0,0 +1,36 @@
+module ActsAsEventable
+ module ActionController
+
+ def self.included(base) # :nodoc:
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ def record_events(&event_user)
+ write_inheritable_attribute('event_user', &event_user) if block_given?
+
+ around_filter :setup_event_user
+
+ include InstanceMethods
+ end
+ end
+
+ module InstanceMethods
+ private
+
+ def setup_event_user
+ user = begin
+ if block = self.class.read_inheritable_attribute('event_user')
+ block.call(self)
+ elsif self.respond_to? :current_user
+ current_user
+ else
+ raise "record_events: you must pass a block to fetch the current user or define a 'current_user' method"
+ end
+ end
+
+ Event.record_events(user) {yield}
+ end
+ end
+ end
+end
@@ -0,0 +1,170 @@
+# ActsAsEventable
+module ActsAsEventable
+ module ActiveRecord
+
+ def self.included(base)
+ base.extend ActsMethods
+ end
+
+ module ActsMethods
+ def acts_as_eventable(options={})
+ write_inheritable_attribute :acts_as_eventable_options, options
+ class_inheritable_reader :acts_as_eventable_options
+
+ has_many :events, :as => :eventable, :order => 'id desc'
+
+ before_save :event_build
+ after_save :event_save
+ before_destroy :event_destroy # use before instead of after in case we want to access association before they are destroyed
+
+ include BatchingMethods
+ include InstanceMethods
+ extend ClassMethods
+
+ # We need to alias these method chains
+ # to manage batch events
+ alias_method_chain :save, :batch
+ alias_method_chain :save!, :batch
+ alias_method_chain :destroy, :batch
+ end
+ end
+
+ module ClassMethods
+ # This is mainly here so we know that we
+ # can detect if something is eventable,
+ # but also as a convience
+ def event_user
+ Event.event_user
+ end
+ end
+
+ module BatchingMethods
+ def save_with_batch(*args) #:nodoc:
+ batch { save_without_batch(*args) }
+ end
+
+ def save_with_batch!(*args) #:nodoc:
+ batch { save_without_batch!(*args) }
+ end
+
+ def destroy_with_batch(*args)
+ batch { destroy_without_batch(*args) }
+ end
+
+ private
+
+ # This saves the batch events in the correct order with the correct
+ # batch id
+ def save_batch_events
+ batch_parent_id = nil
+ batch_size = 0
+ batch_event_queue.each do |record|
+ event = batch_events[record]
+ if event
+ event.batch_parent_id = batch_parent_id
+ event.save!
+ logger.debug "Recorded #{event.eventable_type} #{event.action} event with batch parent id = #{batch_parent_id}"
+ batch_parent_id ||= event.id
+ batch_size += 1
+ end
+ end
+
+ # set the batch size of the parent
+ Event.update_all({:batch_size=>batch_size},{:id=>batch_parent_id}) if batch_parent_id
+ end
+
+ def batch(&block)
+ status = nil
+ if batch_event_state.empty?
+ begin
+ batch_event_queue << self
+ status = block.call
+ save_batch_events if status
+ ensure
+ clear_batch_event_state
+ end
+ else
+ batch_event_queue << self
+ status = block.call
+ end
+ status
+ end
+
+ def batch_event_queue
+ batch_event_state[:queue] ||= []
+ end
+
+ def batch_events
+ batch_event_state[:events] ||= {}
+ end
+
+ def clear_batch_event_state
+ Thread.current['batch_event_state'] = {}
+ end
+
+ def batch_event_state
+ Thread.current['batch_event_state'] ||= {}
+ end
+ end
+
+ module InstanceMethods
+
+ # This is to be used for recording arbitrary events as necessary
+ # like when a post is published, or a user logs in.
+ def record_event!(action, event_user=nil)
+ event_user ||= self.class.event_user
+
+ raise "Cannot record an event without an event user!" unless event_user
+ raise "Cannot record an event on new records" if new_record?
+
+ @event = Event.new
+ @event.action = action
+ @event.user = event_user
+ @event.eventable = self
+ @event.save!
+ end
+
+ private
+
+ # Destroys all the old events and creates a
+ # new destroy event that also captures the eventable_attributes
+ # so that the record can still be shown in the event log.
+ def event_destroy
+ self.events.destroy_all
+
+ if event_user = self.class.event_user
+ @event = Event.new
+ @event.action = 'destroyed'
+ @event.eventable = self
+ @event.eventable_attributes = self.attributes
+ @event.user = event_user
+ batch_events[self] = @event
+ end
+ end
+
+ # Builds the initial event and sets the default
+ # action type. Does not assign eventable yet because
+ # it may not have been saved if this was a new record.
+ def event_build
+ if event_user = self.class.event_user
+ @event = Event.new
+ @event.action = case
+ when self.new_record? then 'created'
+ else 'updated'
+ end
+ @event.user = event_user
+ end
+ end
+
+ # Saves the event after assigning eventable
+ def event_save
+ updated_if = acts_as_eventable_options[:updated_if]
+ if @event && !(@event.action == 'updated' && updated_if && !updated_if.call(self))
+ @event.eventable = self
+ batch_events[self] = @event
+ end
+ end
+ end
+
+ end
+end
@@ -0,0 +1,10 @@
+module ActsAsEventable
+ class Options
+ # This can be configured from an initializer as needed
+ # for example, to add a clause for acts_as_paranoid
+ # so it still selects deleted users or to specify
+ # the class_name
+ @@event_belongs_to_user_options = {}
+ cattr_accessor :event_belongs_to_user_options
+ end
+end
Oops, something went wrong.

0 comments on commit 353c5ce

Please sign in to comment.