├── VERSION ├── .gitignore ├── rails └── init.rb ├── init.rb ├── lib ├── acts_as_current.rb └── acts_as_current │ ├── instance_methods.rb │ ├── class_methods.rb │ └── base.rb ├── test ├── acts_as_current │ ├── base_test.rb │ ├── simple_test.rb │ └── sti_test.rb └── test_helper.rb ├── MIT-LICENSE ├── Rakefile ├── acts_as_current.gemspec ├── .specification └── README.rdoc /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.gem -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | require "acts_as_current" -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/rails/init.rb" -------------------------------------------------------------------------------- /lib/acts_as_current.rb: -------------------------------------------------------------------------------- 1 | # external gems 2 | require "active_record" 3 | 4 | 5 | # acts_as_current extension 6 | require File.dirname(__FILE__) + "/acts_as_current/base" 7 | require File.dirname(__FILE__) + "/acts_as_current/class_methods" 8 | require File.dirname(__FILE__) + "/acts_as_current/instance_methods" 9 | 10 | 11 | # add extensions to active record 12 | ::ActiveRecord::Base.send(:include, Coroutine::ActsAsCurrent::Base) -------------------------------------------------------------------------------- /lib/acts_as_current/instance_methods.rb: -------------------------------------------------------------------------------- 1 | module Coroutine 2 | module ActsAsCurrent 3 | 4 | # This module defines methods that will be mixed into the instance when acts_as_current is invoked. 5 | # 6 | # The notation used below assumes the module will be invoked using the :include method, ensuring 7 | # the wrapping scope is the instance object. 8 | # 9 | module InstanceMethods 10 | 11 | # This method returns a boolean indicating whether or not the instance is defined 12 | # as the current instance of the class. 13 | # 14 | def current? 15 | !self.class.current.nil? && self.id == self.class.current.id 16 | end 17 | 18 | # This method forces the instance to become the defined current instance of the 19 | # class. 20 | # 21 | def current! 22 | self.class.current = self 23 | end 24 | 25 | end 26 | 27 | end 28 | end -------------------------------------------------------------------------------- /test/acts_as_current/base_test.rb: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------- 2 | # Requirements 3 | #--------------------------------------------------------- 4 | 5 | # all generic stuff required by test helper 6 | require "test/test_helper" 7 | 8 | 9 | 10 | #--------------------------------------------------------- 11 | # Tests 12 | #--------------------------------------------------------- 13 | 14 | class ActsAsCurrentBaseTest < ActiveSupport::TestCase 15 | 16 | #--------------------------------------------- 17 | # setup and teardown delegations 18 | #--------------------------------------------- 19 | 20 | def setup 21 | setup_db 22 | end 23 | def teardown 24 | teardown_db 25 | end 26 | 27 | 28 | 29 | #--------------------------------------------- 30 | # test composition 31 | #--------------------------------------------- 32 | 33 | def test_composition_of_class 34 | assert_respond_to ::ActiveRecord::Base, :acts_as_current 35 | end 36 | 37 | end -------------------------------------------------------------------------------- /lib/acts_as_current/class_methods.rb: -------------------------------------------------------------------------------- 1 | module Coroutine 2 | module ActsAsCurrent 3 | 4 | # This module defines methods that will be mixed into the class definition when acts_as_current 5 | # is invoked. 6 | # 7 | # The notation used below assumes the module will be invoked using the :extend method, ensuring 8 | # the wrapping scope is the class object. 9 | # 10 | module ClassMethods 11 | 12 | # This method returns the reference to the instance defined as current. 13 | # 14 | def current 15 | Thread.current[acts_as_current_symbol] 16 | end 17 | 18 | # This method sets the reference to the instance defined as current. 19 | # 20 | def current=(instance) 21 | unless (instance.is_a?(self) || instance.nil?) 22 | raise(ArgumentError, "The method expected an instance of class '#{name}', but instead was given #{instance.inspect}") 23 | end 24 | Thread.current[acts_as_current_symbol] = instance 25 | end 26 | 27 | 28 | private 29 | 30 | # This method returns the singularized, underscored symbol for the class. 31 | # 32 | def acts_as_current_symbol 33 | name.underscore.to_sym 34 | end 35 | 36 | end 37 | 38 | end 39 | end -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Coroutine LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------- 23 | 24 | acts_as_current was inspired by sentient_user. 25 | 26 | Copyright (c) 2009 bokmann, also released under the MIT license. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | require 'rake/rdoctask' 4 | require 'jeweler' 5 | 6 | 7 | desc 'Default: run tests.' 8 | task :default => [:test] 9 | 10 | 11 | desc 'Test the plugin.' 12 | Rake::TestTask.new(:test) do |t| 13 | t.libs << 'lib' 14 | t.pattern = 'test/**/*_test.rb' 15 | t.verbose = true 16 | end 17 | 18 | 19 | desc 'Generate documentation for the plugin.' 20 | Rake::RDocTask.new(:rdoc) do |rdoc| 21 | rdoc.rdoc_dir = 'rdoc' 22 | rdoc.title = 'acts_as_current' 23 | rdoc.options << '--line-numbers --inline-source' 24 | rdoc.rdoc_files.include('README') 25 | rdoc.rdoc_files.include('lib/**/*.rb') 26 | end 27 | 28 | 29 | begin 30 | Jeweler::Tasks.new do |gemspec| 31 | gemspec.authors = ["Coroutine", "John Dugan"] 32 | gemspec.description = "This acts_as extension modifies ActiveRecord classes so they can carry a reference to the instance defined as current for the given request. The library is particularly useful for providing the authenticated user object to models." 33 | gemspec.email = "gem@coroutine.com" 34 | gemspec.homepage = "http://github.com/coroutine/acts_as_current" 35 | gemspec.name = "acts_as_current" 36 | gemspec.summary = "Gem version of acts_as_current Rails plugin." 37 | 38 | gemspec.add_dependency("activerecord") 39 | gemspec.add_development_dependency("activesupport") 40 | gemspec.files.include("lib/**/*.rb") 41 | end 42 | Jeweler::GemcutterTasks.new 43 | rescue LoadError 44 | puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" 45 | end 46 | 47 | -------------------------------------------------------------------------------- /lib/acts_as_current/base.rb: -------------------------------------------------------------------------------- 1 | module Coroutine 2 | 3 | # This module is an acts_as extension that teaches an ActiveRecord model how to provide a reference 4 | # to the instance owned by the current thread through a class method. 5 | # 6 | # The module includes class methods and instance methods that simplify the process of storing the 7 | # current reference in a thread safe manner. 8 | # 9 | # Because acts_as_current relies on the Thread.current hash, it should probably be used sparingly. 10 | # 11 | module ActsAsCurrent 12 | 13 | # This module provides the base functionality for the acts_as_current extension. It declares the 14 | # class method acts_as_current and handles including all necessary sub modules when the class 15 | # method is invoked. 16 | # 17 | module Base 18 | 19 | def self.included(klass) #:nodoc: 20 | klass.class_eval do 21 | 22 | # This class method extends an ActiveRecord class with behavior appropriate for providing a 23 | # current instance to the request. 24 | # 25 | # Including this method in a model definition adds two public class methods and two public 26 | # instance methods to the model. See modules below for method defintions. Here's a simple 27 | # skeleton that demonstrates the resulting interface. 28 | # 29 | # 30 | # class MyClass < ActiveRecord::Base 31 | # 32 | # def self.current 33 | # end 34 | # 35 | # def self.current=(instance) 36 | # end 37 | # 38 | # def current? 39 | # end 40 | # 41 | # def current! 42 | # end 43 | # 44 | # end 45 | # 46 | def self.acts_as_current 47 | 48 | # mixin methods 49 | extend Coroutine::ActsAsCurrent::ClassMethods 50 | include Coroutine::ActsAsCurrent::InstanceMethods 51 | 52 | end 53 | 54 | end 55 | end 56 | 57 | end 58 | end 59 | end -------------------------------------------------------------------------------- /test/acts_as_current/simple_test.rb: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------- 2 | # Requirements 3 | #--------------------------------------------------------- 4 | 5 | # all generic stuff required by test helper 6 | require "test/test_helper" 7 | 8 | 9 | 10 | #--------------------------------------------------------- 11 | # Tests 12 | #--------------------------------------------------------- 13 | 14 | class ActsAsCurrentSimpleTest < ActiveSupport::TestCase 15 | 16 | #--------------------------------------------- 17 | # setup and teardown delegations 18 | #--------------------------------------------- 19 | 20 | def setup 21 | setup_db 22 | end 23 | def teardown 24 | teardown_db 25 | end 26 | 27 | 28 | 29 | #--------------------------------------------- 30 | # test composition 31 | #--------------------------------------------- 32 | 33 | def test_composition_of_class 34 | assert User.respond_to?(:current) 35 | assert User.respond_to?(:current=) 36 | 37 | assert User.respond_to?(:acts_as_current_symbol, true) 38 | end 39 | 40 | 41 | def test_composition_of_instance 42 | seed_users 43 | @one = User.first 44 | 45 | assert @one.respond_to?(:current?) 46 | assert @one.respond_to?(:current!) 47 | end 48 | 49 | 50 | 51 | #--------------------------------------------- 52 | # test symbol method 53 | #--------------------------------------------- 54 | 55 | def test_symbol_method 56 | assert_equal :user, User.send(:acts_as_current_symbol) # this notation bypasses privacy 57 | end 58 | 59 | 60 | 61 | #--------------------------------------------- 62 | # test current methods 63 | #--------------------------------------------- 64 | 65 | def test_current_methods 66 | seed_users 67 | @one = User.first 68 | @two = User.last 69 | 70 | # starts as nil 71 | assert User.current.nil? 72 | 73 | # set current to one explicitly (instances agree) 74 | User.current = @one 75 | assert_equal User.current, @one 76 | assert @one.current? 77 | assert !@two.current? 78 | 79 | # set current to two by conversion (instances agree) 80 | @two.current! 81 | assert_equal User.current, @two 82 | assert !@one.current? 83 | assert @two.current? 84 | end 85 | 86 | end -------------------------------------------------------------------------------- /test/acts_as_current/sti_test.rb: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------- 2 | # Requirements 3 | #--------------------------------------------------------- 4 | 5 | # all generic stuff required by test helper 6 | require "test/test_helper" 7 | 8 | 9 | 10 | #--------------------------------------------------------- 11 | # Tests 12 | #--------------------------------------------------------- 13 | 14 | class ActsAsCurrentStiTest < ActiveSupport::TestCase 15 | 16 | #--------------------------------------------- 17 | # setup and teardown delegations 18 | #--------------------------------------------- 19 | 20 | def setup 21 | setup_db 22 | end 23 | def teardown 24 | teardown_db 25 | end 26 | 27 | 28 | 29 | #--------------------------------------------- 30 | # test composition 31 | #--------------------------------------------- 32 | 33 | def test_composition_of_class 34 | assert BillingFrequency.respond_to?(:current) 35 | assert BillingFrequency.respond_to?(:current=) 36 | 37 | assert BillingFrequency.respond_to?(:acts_as_current_symbol, true) 38 | end 39 | 40 | 41 | def test_composition_of_instance 42 | seed_billing_frequencies 43 | @one = BillingFrequency.first 44 | 45 | assert @one.respond_to?(:current?) 46 | assert @one.respond_to?(:current!) 47 | end 48 | 49 | 50 | 51 | #--------------------------------------------- 52 | # test symbol method 53 | #--------------------------------------------- 54 | 55 | def test_symbol_method 56 | assert_equal :billing_frequency, BillingFrequency.send(:acts_as_current_symbol) # this notation bypasses privacy 57 | end 58 | 59 | 60 | 61 | #--------------------------------------------- 62 | # test current methods 63 | #--------------------------------------------- 64 | 65 | def test_current_methods 66 | seed_billing_frequencies 67 | @one = BillingFrequency.first 68 | @two = BillingFrequency.last 69 | 70 | # starts as nil 71 | assert BillingFrequency.current.nil? 72 | 73 | # set current to one explicitly (instances agree) 74 | BillingFrequency.current = @one 75 | assert_equal BillingFrequency.current, @one 76 | assert @one.current? 77 | assert !@two.current? 78 | 79 | # set current to two by conversion (instances agree) 80 | @two.current! 81 | assert_equal BillingFrequency.current, @two 82 | assert !@one.current? 83 | assert @two.current? 84 | end 85 | 86 | end -------------------------------------------------------------------------------- /acts_as_current.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{acts_as_current} 8 | s.version = "1.0.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Coroutine", "John Dugan"] 12 | s.date = %q{2010-10-10} 13 | s.description = %q{This acts_as extension modifies ActiveRecord classes so they can carry a reference to the instance defined as current for the given request. The library is particularly useful for providing the authenticated user object to models.} 14 | s.email = %q{gem@coroutine.com} 15 | s.extra_rdoc_files = [ 16 | "README.rdoc" 17 | ] 18 | s.files = [ 19 | ".gitignore", 20 | ".specification", 21 | "MIT-LICENSE", 22 | "README.rdoc", 23 | "Rakefile", 24 | "VERSION", 25 | "acts_as_current.gemspec", 26 | "init.rb", 27 | "lib/acts_as_current.rb", 28 | "lib/acts_as_current/base.rb", 29 | "lib/acts_as_current/class_methods.rb", 30 | "lib/acts_as_current/instance_methods.rb", 31 | "rails/init.rb", 32 | "test/acts_as_current/base_test.rb", 33 | "test/acts_as_current/simple_test.rb", 34 | "test/acts_as_current/sti_test.rb", 35 | "test/test_helper.rb" 36 | ] 37 | s.homepage = %q{http://github.com/coroutine/acts_as_current} 38 | s.rdoc_options = ["--charset=UTF-8"] 39 | s.require_paths = ["lib"] 40 | s.rubygems_version = %q{1.3.7} 41 | s.summary = %q{Gem version of acts_as_current Rails plugin.} 42 | s.test_files = [ 43 | "test/acts_as_current/base_test.rb", 44 | "test/acts_as_current/simple_test.rb", 45 | "test/acts_as_current/sti_test.rb", 46 | "test/test_helper.rb" 47 | ] 48 | 49 | if s.respond_to? :specification_version then 50 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 51 | s.specification_version = 3 52 | 53 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 54 | s.add_runtime_dependency(%q, [">= 0"]) 55 | s.add_development_dependency(%q, [">= 0"]) 56 | else 57 | s.add_dependency(%q, [">= 0"]) 58 | s.add_dependency(%q, [">= 0"]) 59 | end 60 | else 61 | s.add_dependency(%q, [">= 0"]) 62 | s.add_dependency(%q, [">= 0"]) 63 | end 64 | end 65 | 66 | -------------------------------------------------------------------------------- /.specification: -------------------------------------------------------------------------------- 1 | --- !ruby/object:Gem::Specification 2 | name: acts_as_current 3 | version: !ruby/object:Gem::Version 4 | hash: 23 5 | prerelease: false 6 | segments: 7 | - 1 8 | - 0 9 | - 0 10 | version: 1.0.0 11 | platform: ruby 12 | authors: 13 | - Coroutine 14 | - John Dugan 15 | autorequire: 16 | bindir: bin 17 | cert_chain: [] 18 | 19 | date: 2010-10-10 00:00:00 -05:00 20 | default_executable: 21 | dependencies: 22 | - !ruby/object:Gem::Dependency 23 | name: activerecord 24 | prerelease: false 25 | requirement: &id001 !ruby/object:Gem::Requirement 26 | none: false 27 | requirements: 28 | - - ">=" 29 | - !ruby/object:Gem::Version 30 | hash: 3 31 | segments: 32 | - 0 33 | version: "0" 34 | type: :runtime 35 | version_requirements: *id001 36 | - !ruby/object:Gem::Dependency 37 | name: activesupport 38 | prerelease: false 39 | requirement: &id002 !ruby/object:Gem::Requirement 40 | none: false 41 | requirements: 42 | - - ">=" 43 | - !ruby/object:Gem::Version 44 | hash: 3 45 | segments: 46 | - 0 47 | version: "0" 48 | type: :development 49 | version_requirements: *id002 50 | description: This acts_as extension modifies ActiveRecord classes so they can carry a reference to the instance defined as current for the given request. The library is particularly useful for providing the authenticated user object to models. 51 | email: gem@coroutine.com 52 | executables: [] 53 | 54 | extensions: [] 55 | 56 | extra_rdoc_files: 57 | - README.rdoc 58 | files: 59 | - .gitignore 60 | - .specification 61 | - MIT-LICENSE 62 | - README.rdoc 63 | - Rakefile 64 | - VERSION 65 | - acts_as_current.gemspec 66 | - init.rb 67 | - lib/acts_as_current.rb 68 | - lib/acts_as_current/base.rb 69 | - lib/acts_as_current/class_methods.rb 70 | - lib/acts_as_current/instance_methods.rb 71 | - rails/init.rb 72 | - test/acts_as_current/base_test.rb 73 | - test/acts_as_current/simple_test.rb 74 | - test/acts_as_current/sti_test.rb 75 | - test/test_helper.rb 76 | has_rdoc: true 77 | homepage: http://github.com/coroutine/acts_as_current 78 | licenses: [] 79 | 80 | post_install_message: 81 | rdoc_options: 82 | - --charset=UTF-8 83 | require_paths: 84 | - lib 85 | required_ruby_version: !ruby/object:Gem::Requirement 86 | none: false 87 | requirements: 88 | - - ">=" 89 | - !ruby/object:Gem::Version 90 | hash: 3 91 | segments: 92 | - 0 93 | version: "0" 94 | required_rubygems_version: !ruby/object:Gem::Requirement 95 | none: false 96 | requirements: 97 | - - ">=" 98 | - !ruby/object:Gem::Version 99 | hash: 3 100 | segments: 101 | - 0 102 | version: "0" 103 | requirements: [] 104 | 105 | rubyforge_project: 106 | rubygems_version: 1.3.7 107 | signing_key: 108 | specification_version: 3 109 | summary: Gem version of acts_as_current Rails plugin. 110 | test_files: 111 | - test/acts_as_current/base_test.rb 112 | - test/acts_as_current/simple_test.rb 113 | - test/acts_as_current/sti_test.rb 114 | - test/test_helper.rb 115 | 116 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------- 2 | # Requirements 3 | #---------------------------------------------------------- 4 | 5 | # rails stuff 6 | require "rubygems" 7 | require "active_record" 8 | require "active_support" 9 | require "active_support/test_case" 10 | require "test/unit" 11 | 12 | # the plugin itself 13 | require "#{File.dirname(__FILE__)}/../init" 14 | 15 | 16 | 17 | #--------------------------------------------------------- 18 | # Database config 19 | #--------------------------------------------------------- 20 | 21 | # establish db connection 22 | begin 23 | ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:") 24 | rescue ArgumentError 25 | ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:") 26 | end 27 | 28 | 29 | # define and seed tables 30 | def setup_db 31 | ActiveRecord::Schema.define(:version => 1) do 32 | create_table :users do |t| 33 | t.string :email, :limit => 255 34 | t.string :crypted_password, :limit => 255 35 | 36 | t.timestamps 37 | end 38 | end 39 | 40 | ActiveRecord::Schema.define(:version => 1) do 41 | create_table :labels do |t| 42 | t.string :type, :limit => 255 43 | t.string :system_label, :limit => 255 44 | t.string :label, :limit => 255 45 | 46 | t.timestamps 47 | end 48 | end 49 | end 50 | 51 | 52 | # seed users 53 | def seed_users 54 | User.create!({ :email => "user_1@coroutine.com", :crypted_password => "not_crypted_but_does_not_matter_here" }) 55 | User.create!({ :email => "user_2@coroutine.com", :crypted_password => "not_crypted_but_does_not_matter_here" }) 56 | end 57 | 58 | # seed billing frequencies 59 | def seed_billing_frequencies 60 | BillingFrequency.create!({ :system_label => "MONTHLY", :label => "Monthly" }) 61 | BillingFrequency.create!({ :system_label => "YEARLY", :label => "Yearly" }) 62 | end 63 | 64 | 65 | # drop all tables 66 | def teardown_db 67 | ActiveRecord::Base.connection.tables.each do |table| 68 | ActiveRecord::Base.connection.drop_table(table) 69 | end 70 | end 71 | 72 | 73 | 74 | #--------------------------------------------------------- 75 | # Model definitions 76 | #--------------------------------------------------------- 77 | 78 | # users (simple case) 79 | class User < ActiveRecord::Base 80 | acts_as_current 81 | end 82 | 83 | 84 | # labels (sti base) 85 | class Label < ActiveRecord::Base 86 | acts_as_current 87 | end 88 | 89 | 90 | # billing frequencies (sti extension) 91 | class BillingFrequency < Label 92 | end 93 | 94 | 95 | 96 | #---------------------------------------------------------- 97 | # Define global methods 98 | #---------------------------------------------------------- 99 | 100 | class ActiveSupport::TestCase 101 | 102 | # This method allows us to use a convenient notation for testing 103 | # model validations. 104 | def assert_not_valid(object, msg="Object is valid when it should be invalid") 105 | assert(!object.valid?, msg) 106 | end 107 | alias :assert_invalid :assert_not_valid 108 | 109 | end -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = acts_as_current 2 | 3 | This library extends ActiveRecord classes so they can carry a reference to the 4 | instance defined as current for the given request. 5 | 6 | 7 | 8 | == Usage 9 | 10 | acts_as_current can be applied to any model, but it's most common use case is 11 | storing the authenticated user on the corresponding User model. Doing so allows other models 12 | and observers to access information about the user who made the request. This is particularly 13 | useful for auditing, versioning, etc. 14 | 15 | Here's how you'd set up the user example. 16 | 17 | First, extend the User model. 18 | 19 | class User < ActiveRecord::Base 20 | acts_as_current 21 | end 22 | 23 | 24 | Then, tell your application controller to set the current user before all requests. (The controller 25 | method can obviously be anything you want. Here, we use :current_user because that's our 26 | convention at Coroutine.) 27 | 28 | class ApplicationController < ActionController::Base 29 | 30 | before_filter { |controller| User.current = controller.send(:current_user) } 31 | 32 | def current_user 33 | # return user or nil 34 | end 35 | end 36 | 37 | 38 | Finally, in your observer, you can retrieve the value of current_user by using the accessor mixed 39 | into the model class. 40 | 41 | class AuditObserver < ActiveRecord::Observer 42 | observe :account, :balance 43 | 44 | def after_update(record) 45 | AuditTrail.new({ :record => record, :action => :update, :whodunnit => User.current }) 46 | end 47 | end 48 | 49 | 50 | 51 | == Simple Controller Recipe 52 | 53 | He's an example of how you might set a current instance on a User class via a 54 | current_user method, using a before filter in the application controller. 55 | 56 | before_filter { |controller| User.current = controller.send(:current_user) } 57 | 58 | 59 | 60 | == Why Doesn't the Library Handle the Controller Logic For Me? 61 | 62 | Primarily, because we think ActiveRecord extensions have no business altering 63 | controller logic. Doing so couples aspects of your application that Rails is going 64 | out of its way to separate. 65 | 66 | But also because the before_filter syntax is already configurable and extremely expressive. 67 | Writing the before filter is no harder than writing a module include statement, but the former 68 | tells a code maintainer considerably more information than the latter. 69 | 70 | In summary, suck it up and write the controller code yourself. :-) 71 | 72 | 73 | 74 | == Design Notes 75 | 76 | acts_as_current uses the hash Thread.current to store the current value for each class 77 | extended by the library. Many consider this technique a hack, but for now, it is the only thread 78 | safe way to store such data. By coding the technique as a model extension, acts_as_current 79 | abstracts reads and writes against Thread.current, greatly reducing the likelihood of 80 | conflicts and errors. 81 | 82 | We think the real benefits outweigh the perceived risks. 83 | 84 | 85 | 86 | == Helpful Links 87 | 88 | * Repository: http://github.com/coroutine/acts_as_current 89 | * Gem: http://rubygems.org/gems/acts_as_current 90 | * Authors: http://coroutine.com 91 | 92 | 93 | 94 | == Installation (Rails 3) 95 | 96 | Install me from RubyGems.org by adding a gem dependency to your Gemfile. Bundler does 97 | the rest. 98 | 99 | gem "acts_as_current" 100 | 101 | $ bundle install 102 | 103 | 104 | 105 | == Installation (Rails 2) 106 | 107 | Install me from RubyGems.org and add a gem dependency in your configuration file. 108 | 109 | $ sudo gem install acts_as_current 110 | 111 | Or install me as a plugin. 112 | 113 | $ script/plugin install git://github.com/coroutine/acts_as_current.git 114 | 115 | 116 | 117 | == Gemroll 118 | 119 | Other gems by Coroutine include: 120 | 121 | * {acts_as_label}[http://github.com/coroutine/acts_as_label] 122 | * {acts_as_list_with_sti_support}[http://github.com/coroutine/acts_as_list_with_sti_support] 123 | * {delayed_form_observer}[http://github.com/coroutine/delayed_form_observer] 124 | * {kenny_dialoggins}[http://github.com/coroutine/kenny_dialoggins] 125 | * {michael_hintbuble}[http://github.com/coroutine/michael_hintbuble] 126 | * {tiny_navigation}[http://github.com/coroutine/tiny_navigation] 127 | 128 | 129 | 130 | == License 131 | 132 | Copyright (c) 2010 {Coroutine LLC}[http://coroutine.com]. 133 | 134 | Permission is hereby granted, free of charge, to any person obtaining 135 | a copy of this software and associated documentation files (the 136 | "Software"), to deal in the Software without restriction, including 137 | without limitation the rights to use, copy, modify, merge, publish, 138 | distribute, sublicense, and/or sell copies of the Software, and to 139 | permit persons to whom the Software is furnished to do so, subject to 140 | the following conditions: 141 | 142 | The above copyright notice and this permission notice shall be 143 | included in all copies or substantial portions of the Software. 144 | 145 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 146 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 147 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 148 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 149 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 150 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 151 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 152 | 153 | acts_as_current was inspired by sentient_user, copyright (c) 2009 bokmann, also released under the 154 | MIT license. --------------------------------------------------------------------------------