diff --git a/README b/README index 84c4528..7a593e8 100644 --- a/README +++ b/README @@ -6,11 +6,11 @@ ActsAsEventable makes it easy to log events corresponding to actions taken on ac in your controller (typically application_controller) -class FooController +class FooController record_events - - # or - + + # or + record_events lambda { something_that_returns_a_user } end diff --git a/generators/acts_as_eventable_migration/acts_as_eventable_migration_generator.rb b/generators/acts_as_eventable_migration/acts_as_eventable_migration_generator.rb index 6cc6fbb..53cd93e 100644 --- a/generators/acts_as_eventable_migration/acts_as_eventable_migration_generator.rb +++ b/generators/acts_as_eventable_migration/acts_as_eventable_migration_generator.rb @@ -1,10 +1,10 @@ -class ActsAsEventableMigrationGenerator < Rails::Generator::Base - def manifest - record do |m| +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 diff --git a/generators/acts_as_eventable_migration/templates/migration.rb b/generators/acts_as_eventable_migration/templates/migration.rb index 0ba1c79..131ee55 100644 --- a/generators/acts_as_eventable_migration/templates/migration.rb +++ b/generators/acts_as_eventable_migration/templates/migration.rb @@ -3,25 +3,25 @@ def self.up create_table :events do |t| t.string :eventable_type, :null => false t.integer :eventable_id - + t.integer :user_id - + # 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] end - + def self.down drop_table :events end diff --git a/lib/acts_as_eventable/active_record.rb b/lib/acts_as_eventable/active_record.rb index 491e89d..4a0b093 100644 --- a/lib/acts_as_eventable/active_record.rb +++ b/lib/acts_as_eventable/active_record.rb @@ -1,31 +1,31 @@ # ActsAsEventable module ActsAsEventable module ActiveRecord - + def self.included(base) base.extend ActsMethods end - + module ActsMethods def acts_as_eventable(options={}, &block) if self.respond_to?(:acts_as_eventable_options) raise "acts_as_eventable cannot be used twice on the same model" else options[:block] = block - + write_inheritable_attribute :acts_as_eventable_options, options class_inheritable_reader :acts_as_eventable_options - + has_many :events, :as => :eventable, :order => 'id desc' - + after_update :event_update after_create :event_create before_destroy :event_destroy # use before instead of after in case we want to access association before they are destroyed - + include BatchingMethods include InstanceMethods - - # We need to alias these method chains + + # We need to alias these method chains # to manage batch events alias_method_chain :save, :batch alias_method_chain :save!, :batch @@ -33,7 +33,7 @@ def acts_as_eventable(options={}, &block) end end end - + module BatchingMethods def save_with_batch(*args) #:nodoc: batch { save_without_batch(*args) } @@ -42,13 +42,13 @@ def save_with_batch(*args) #:nodoc: 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 @@ -64,11 +64,11 @@ def save_batch_events 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 + ::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? @@ -85,26 +85,26 @@ def batch(&block) 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) @@ -112,11 +112,11 @@ def record_event!(action) raise "Cannot record an event on new records" if new_record? event.save! end - + private - - # Destroys all the old events and creates a - # new destroy event that also captures the eventable_attributes + + # 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 transaction do @@ -126,7 +126,7 @@ def event_destroy batch_events[self] = event end end - + # Saves the event after assigning eventable def event_update event = build_event('updated') @@ -135,12 +135,12 @@ def event_update batch_events[self] = event end end - + def event_create event = build_event('created') batch_events[self] = event 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. @@ -148,16 +148,16 @@ def build_event(action) event = ::Event.new event.eventable = self event.action = action - + # Allow the eventable model class to modify the event record before it # is saved to do things like assign the current user if block = self.class.acts_as_eventable_options[:block] block.call(self, event) end - + return event end end - + end end \ No newline at end of file diff --git a/lib/acts_as_eventable/event/base.rb b/lib/acts_as_eventable/event/base.rb index ef8e5bc..0c96837 100644 --- a/lib/acts_as_eventable/event/base.rb +++ b/lib/acts_as_eventable/event/base.rb @@ -3,7 +3,7 @@ module Event module Base def self.included(base) base.class_eval do - + # Attribute Modifiers # ------------------- @@ -28,7 +28,7 @@ def self.included(base) # --------- before_save :clear_eventable_id, :if => Proc.new {|e| e.action == "destroyed"} - + # Finders # ------- @@ -37,19 +37,19 @@ def self.included(base) named_scope :by_user, lambda {|user| {:conditions => {:user_id => user.id}}} named_scope :with_users, :include => :user named_scope :with_eventables, :include => :eventable - + extend ClassMethods - + # This is redefined so that it returns the eventable # object even if it has been destroyed by reconstructing # it from the eventable attributes that were saved # on the event alias_method :eventable_when_not_destroyed, :eventable - + include InstanceMethods end end - + module ClassMethods # This will replace the way rails includes the eventable # association with the inject_eventables method below. @@ -92,7 +92,7 @@ def inject_eventables(events, &block) end end - private + private # If options contains includes for eventable, removes it # and return true. Otherwise, returns false. @@ -100,9 +100,9 @@ def remove_eventable_from_includes(options) inject = false if options case includes = options[:include] - when Array then + when Array then inject = !includes.delete(:eventable).nil? - when Symbol + when Symbol if includes == :eventable options.delete(:include) inject = true @@ -112,7 +112,7 @@ def remove_eventable_from_includes(options) return inject end end - + module InstanceMethods def eventable if eventable_id.nil? && !eventable_attributes.nil? @@ -121,15 +121,15 @@ def eventable eventable_when_not_destroyed end end - + private - + # Used to clear the eventable id on destroy events def clear_eventable_id self.eventable_id = nil end end - + end # Base end # Event end # ActsAsEventable \ No newline at end of file diff --git a/spec/acts_as_eventable_spec.rb b/spec/acts_as_eventable_spec.rb index e0b8227..7d59528 100644 --- a/spec/acts_as_eventable_spec.rb +++ b/spec/acts_as_eventable_spec.rb @@ -3,49 +3,49 @@ describe ActsAsEventable do include UserSpecHelper include FormSpecHelper - + before(:each) do @user = create_valid_user end - + describe "when event user is set" do - before do + before do Form.current_user = @user end - + describe "after create" do before do @event_count = Event.count @form = Form.create!(valid_form_attributes) @event = Event.last end - + it "should create a single event" do Event.count.should == @event_count + 1 end - + it "should set the action to created" do @event.action.should == 'created' end - + it "should set eventable to the model that triggered the event" do @event.eventable.should == @form end - + it "should set the parent_id to nil since this wasn't a batch event" do @event.batch_parent_id.should == nil end - + it "should set the batch_size to 1" do @event.batch_size.should == 1 end end - + describe "existing record" do before do @form = Form.create!(valid_form_attributes) end - + it "should update successfully" do @form.update_attribute(:title, "Updated Title") end @@ -53,7 +53,7 @@ it "should destroy successfully" do @form.destroy end - + describe "after update" do before do @event_count = Event.count @@ -81,14 +81,14 @@ @event.batch_size.should == 1 end end - + describe "after_destroy" do before do @event_count = Event.count @form.destroy @event = Event.last end - + it "should leave a single event" do Event.count.should == 1 end @@ -108,14 +108,14 @@ it "should set the batch_size to 1" do @event.batch_size.should == 1 end - + it "should record the attributes of the deleted record" do @event.eventable_attributes.should_not == nil @event.eventable_attributes.should == @form.attributes end end end - + describe "with nested events" do before :each do Field.current_user = Form.current_user @@ -125,37 +125,37 @@ @form.save! Field.current_user = nil end - + it "should create two events total" do (Event.count - @event_count).should == 2 end - + it "should create an event for each model" do @form.events.first.should_not be_nil @field.events.first.should_not be_nil end - + it "should create the parent event and then the child event" do @field.events.first.id.should > @form.events.first.id end - + it "should not set batch_parent_id on the parent event" do @form.events.first.batch_parent_id.should be_nil end - + it "should set the batch_size of the field event to 1 since there were no children events for it" do @field.events.first.batch_size.should == 1 end - + it "should set the batch_size of the form event to 2 since it has a child event" do @form.events.first.batch_size.should == 2 end - + it "should make the first event a child_batch_event of the parent event" do @form.events.first.child_batch_events.first.should == @field.events.first end - - + + describe "after destroying the parent resource" do before(:each) do @base_destory_event_count = Event.count(:conditions=>{:action=>'destroyed'}) @@ -163,38 +163,38 @@ @form.destroy Field.current_user = nil end - + it "should create two new destroy events" do (Event.count(:conditions=>{:action=>'destroyed'}) - @base_destory_event_count).should == 2 end - + it "should create the form destroy event before the field destroy event" do Event.find(:all,:order=>'id desc')[0].eventable_type.should == "Field" Event.find(:all,:order=>'id desc')[1].eventable_type.should == "Form" end - + it "should not set batch_parent_id on the Form event" do Event.find(:all,:order=>'id desc')[1].batch_parent_id.should == nil end - + it "should set batch_parent_id on the Field event to that of the Form event" do Event.find(:all,:order=>'id desc')[0].batch_parent_id.should == Event.find(:all,:order=>'id desc')[1].id end - + it "should set the batch_size on the Form event to 2" do Event.find(:all,:order=>'id desc')[1].batch_size.should == 2 end - + it "should set the batch_size on the Field event to 1" do Event.find(:all,:order=>'id desc')[0].batch_size.should == 1 end - + it "should make the field event a child_batch_event" do Event.find(:all,:order=>'id desc')[1].child_batch_events.first.should == Event.find(:all,:order=>'id desc')[0] end end end - + # describe "with nested events" do # before(:each) do # @base_event_count = Event.count @@ -202,96 +202,96 @@ # @field = @form.fields.build(valid_field_attributes) # @form.save! # end - # + # # it "should create two events total" do # (Event.count - @base_event_count).should == 2 # end - # + # # it "should create an event for the form" do # @form.events.first.should_not == nil # end - # + # # it "should create an event for the field" do # @field.events.first.should_not == nil # end - # + # # it "should create the form event and then the field event" do # @field.events.first.id.should > @form.events.first.id # end - # + # # it "should not set batch_parent_id on the Form event" do # @form.events.first.batch_parent_id.should == nil # end - # + # # it "should set batch_parent_id on the Field event" do # @field.events.first.batch_parent_id.should == @form.events.first.id # end - # + # # it "should set the batch_size on the Form event to 2" do # @form.events.first.batch_size.should == 2 # end - # + # # it "should set the batch_size on the Field event to 1" do # @field.events.first.batch_size.should == 1 # end - # + # # it "should make the field event a child_batch_event" do # @form.events.first.child_batch_events.first.should == @field.events.first # end - # + # # describe "after destroying the parent resource" do # before(:each) do # @base_destory_event_count = Event.count(:conditions=>{:action=>'destroyed'}) # @form.destroy # end - # + # # it "should create two new destroy events" do # (Event.count(:conditions=>{:action=>'destroyed'}) - @base_destory_event_count).should == 2 # end - # + # # it "should create the form destroy event before the field destroy event" do # Event.find(:all,:order=>'id desc')[0].eventable_type.should == "Field" # Event.find(:all,:order=>'id desc')[1].eventable_type.should == "Form" # end - # + # # it "should not set batch_parent_id on the Form event" do # Event.find(:all,:order=>'id desc')[1].batch_parent_id.should == nil # end - # + # # it "should set batch_parent_id on the Field event to that of the Form event" do # Event.find(:all,:order=>'id desc')[0].batch_parent_id.should == Event.find(:all,:order=>'id desc')[1].id # end - # + # # it "should set the batch_size on the Form event to 2" do # Event.find(:all,:order=>'id desc')[1].batch_size.should == 2 # end - # + # # it "should set the batch_size on the Field event to 1" do # Event.find(:all,:order=>'id desc')[0].batch_size.should == 1 # end - # + # # it "should make the field event a child_batch_event" do # Event.find(:all,:order=>'id desc')[1].child_batch_events.first.should == Event.find(:all,:order=>'id desc')[0] # end # end # end - # + # # after(:each) do # Form.current_user = nil # end - - + + after do Form.current_user = nil end end - + it "should raise an Exception on create when event user is not set" do lambda { Form.create!(valid_form_attributes) }.should raise_error(/without an event user/) end - + describe "existing record" do before do begin @@ -301,20 +301,20 @@ Form.current_user = nil end end - + it "should raise an Exception on update" do lambda { @form.update_attribute(:title, "Updated Title") }.should raise_error(/without an event user/) end - + it "should raise an Exception on destroy" do lambda { @form.destroy }.should raise_error(/without an event user/) end end - + describe ".record_event" do before do begin @@ -324,29 +324,29 @@ Form.current_user = nil end end - + it "should raise an Exception when the event user is not set" do lambda { @form.record_event!('published') }.should raise_error(/without an event user/) end - + describe "when event user is set" do before do Form.current_user = @user end - + after do Form.current_user = nil end - + it "should create the event successfully" do @form.record_event!('published') last_event = Event.find(:last) last_event.eventable.should == @form last_event.action.should == 'published' end - + describe "when the resource is new" do it "should raise a RuntimeError" do @form = Form.new(valid_form_attributes) @@ -357,20 +357,20 @@ end end end - - - # + + + # # describe "after destroy the resource when event user is not set" do # before(:each) do # @form.destroy # end - # + # # it "should remove the all events and not create a new one" do # Event.count.should == 0 # end # end - # + # # describe "with nested events" do # before(:each) do # @base_event_count = Event.count @@ -378,80 +378,80 @@ # @field = @form.fields.build(valid_field_attributes) # @form.save! # end - # + # # it "should create two events total" do # (Event.count - @base_event_count).should == 2 # end - # + # # it "should create an event for the form" do # @form.events.first.should_not == nil # end - # + # # it "should create an event for the field" do # @field.events.first.should_not == nil # end - # + # # it "should create the form event and then the field event" do # @field.events.first.id.should > @form.events.first.id # end - # + # # it "should not set batch_parent_id on the Form event" do # @form.events.first.batch_parent_id.should == nil # end - # + # # it "should set batch_parent_id on the Field event" do # @field.events.first.batch_parent_id.should == @form.events.first.id # end - # + # # it "should set the batch_size on the Form event to 2" do # @form.events.first.batch_size.should == 2 # end - # + # # it "should set the batch_size on the Field event to 1" do # @field.events.first.batch_size.should == 1 # end - # + # # it "should make the field event a child_batch_event" do # @form.events.first.child_batch_events.first.should == @field.events.first # end - # + # # describe "after destroying the parent resource" do # before(:each) do # @base_destory_event_count = Event.count(:conditions=>{:action=>'destroyed'}) # @form.destroy # end - # + # # it "should create two new destroy events" do # (Event.count(:conditions=>{:action=>'destroyed'}) - @base_destory_event_count).should == 2 # end - # + # # it "should create the form destroy event before the field destroy event" do # Event.find(:all,:order=>'id desc')[0].eventable_type.should == "Field" # Event.find(:all,:order=>'id desc')[1].eventable_type.should == "Form" # end - # + # # it "should not set batch_parent_id on the Form event" do # Event.find(:all,:order=>'id desc')[1].batch_parent_id.should == nil # end - # + # # it "should set batch_parent_id on the Field event to that of the Form event" do # Event.find(:all,:order=>'id desc')[0].batch_parent_id.should == Event.find(:all,:order=>'id desc')[1].id # end - # + # # it "should set the batch_size on the Form event to 2" do # Event.find(:all,:order=>'id desc')[1].batch_size.should == 2 # end - # + # # it "should set the batch_size on the Field event to 1" do # Event.find(:all,:order=>'id desc')[0].batch_size.should == 1 # end - # + # # it "should make the field event a child_batch_event" do # Event.find(:all,:order=>'id desc')[1].child_batch_events.first.should == Event.find(:all,:order=>'id desc')[0] # end # end # end - # + # # after(:each) do # Form.current_user = nil # end diff --git a/spec/db/schema.rb b/spec/db/schema.rb index 29d59ce..3348422 100644 --- a/spec/db/schema.rb +++ b/spec/db/schema.rb @@ -2,22 +2,22 @@ ActiveRecord::Schema.define(:version => 1) do ActsAsEventableMigration.up - + create_table :users do |t| t.string :username t.timestamps end - + create_table :authors do |t| t.string :username t.timestamps end - + create_table :forms do |t| t.string :title t.timestamps end - + create_table :fields do |t| t.integer :form_id t.string :name diff --git a/spec/event_spec.rb b/spec/event_spec.rb index eb1b5d3..2157521 100644 --- a/spec/event_spec.rb +++ b/spec/event_spec.rb @@ -3,7 +3,7 @@ describe Event do include UserSpecHelper - + %w(create update publish).each do |action| describe 'non destroy event' do before(:each) do @@ -15,29 +15,29 @@ :user_id => 1 } end - + it "should create a new instance given valid attributes" do Event.create!(@valid_event_attributes) end - + it "should be invalid without an action" do Event.new(@valid_event_attributes.except(:action)).valid?.should == false end - + it "should be invalid without an eventable_id" do Event.new(@valid_event_attributes.except(:eventable_id)).valid?.should == false end - + it "should be invalid without an eventable_type" do Event.new(@valid_event_attributes.except(:eventable_type)).valid?.should == false end - + it "should be invalid without a user_id" do Event.new(@valid_event_attributes.except(:user_id)).valid?.should == false end end end - + describe 'destroy event' do before(:each) do @valid_event_attributes = { @@ -48,37 +48,37 @@ :user_id => 1 } end - + it "should create a new instance given valid attributes" do Event.create!(@valid_event_attributes) end - + it "should clear the eventable_id" do event = Event.create!(@valid_event_attributes) event.eventable_id.should == nil end - + it "should be valid without an eventable_id" do Event.new(@valid_event_attributes.except(:eventable_id)).valid?.should == true end - + it "should be invalid without an eventable_type" do Event.new(@valid_event_attributes.except(:eventable_type)).valid?.should == false end - + it "should be invalid without a user_id" do Event.new(@valid_event_attributes.except(:user_id)).valid?.should == false end - + it "should be invalid without eventable_attributes" do Event.new(@valid_event_attributes.except(:eventable_attributes)).valid?.should == false end end - + describe "with standard user class name" do before(:each) do @user = create_valid_user - + @valid_event_attributes = { :action => 'created', :eventable_id => 1, @@ -87,12 +87,12 @@ :user => @user } end - + it "should create a new instance given valid attributes" do Event.create!(@valid_event_attributes) end end - + describe "with non-standard user class name" do include AuthorSpecHelper @@ -110,19 +110,19 @@ ActiveSupport::Dependencies.remove_constant('Event') ActsAsEventable::Options.event_belongs_to_user_options[:class_name] = 'Author' class Event < ActiveRecord::Base - include ActsAsEventable::Event::Base + include ActsAsEventable::Event::Base end end it "should create a new instance given valid attributes" do Event.create!(@valid_event_attributes) end - + after(:each) do ActiveSupport::Dependencies.remove_constant('Event') ActsAsEventable::Options.event_belongs_to_user_options.delete(:class_name) class Event < ActiveRecord::Base - include ActsAsEventable::Event::Base + include ActsAsEventable::Event::Base end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d3c77e1..561a107 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -28,7 +28,7 @@ end class Event < ActiveRecord::Base - include ActsAsEventable::Event::Base + include ActsAsEventable::Event::Base end class User < ActiveRecord::Base @@ -44,39 +44,39 @@ class Author < ActiveRecord::Base class Form < ActiveRecord::Base validates_presence_of :title validates_length_of :title, :maximum => 255, :allow_blank => true - + @@current_user = nil def self.current_user=(user) @@current_user = user end - + def self.current_user @@current_user || nil end - + acts_as_eventable do |form, event| event.user = form.class.current_user end - + has_many :fields, :dependent => :destroy end class Field < ActiveRecord::Base belongs_to :form - + validates_presence_of :name validates_length_of :name, :maximum => 255, :allow_blank => true validates_length_of :value, :maximum => 255, :allow_blank => true - + @@current_user = nil def self.current_user=(user) @@current_user = user end - + def self.current_user @@current_user end - + acts_as_eventable do |field, event| event.user = field.class.current_user end @@ -98,7 +98,7 @@ module FormSpecHelper def valid_form_attributes {:title=>"Form 1"} end - + def valid_field_attributes {:name=>"Field 1", :value=>'Value 1'} end