acts_as_audited

acts_as_audited | plugin | rails | ruby July 21 2006
acts_as_audited is an Active Record plugin that logs all modifications to your models in an audits table. It uses a polymorphic association to store an audit record for any of the model objects that you wish to have audited. The audit log stores the model that the change was on, the “action” (create, update, destroy), a serialzied hash of the changes, and optionally the user that performed the action.

Auditing in Rails

NOTE: read the caveats section if the following isn’t working.

If you’re using acts_as_audited within Rails, you can simply declare which models should be audited. acts_as_audited can also automatically record the user that made the change if your controller has a current_user method.

class ApplicationController < ActionController::Base
  audit User, List, Item
protected
  def current_user
    @user ||= User.find(session[:user])
  end
end

Caveats

Auditing with user support depends on Rails’ caching mechanisms, therefore auditing isn’t enabled during development mode. To test that auditing is working, start up your app in production mode, or change the following options in config/environments/development.rb:

config.cache_classes = true
config.action_controller.perform_caching = true

Customizing

To get auditing outside of Rails, or to customize which fields are audited within Rails, you can explicitly declare acts_as_audited on your models. The :except option allows you to specify one or more attributes that you don’t want to be saved in the audit log.

class User < ActiveRecord::Base
  acts_as_audited :except => [:password, :credit_card_number]
end

Installation

You can grab the plugin by running:

script/plugin install git://github.com/collectiveidea/acts_as_audited.git

Run the migration generator and migrate to add the audits table.

script/generate audited_migration add_audits_table
rake db:migrate

Upgrading

Those upgrading from version 0.2 need to add 2 fields the audits table:

add_column :audits, :user_type, :string
add_column :audits, :username, :string
posted by brandon | updated June 19th 07:30 PM
comments feed

120 comments

  1. In the audit table, what is the significance of auditable_id and auditable type? In example above does auditable_type User reflect the model being audited?
    Navjeet Navjeet
    July 21, 2006 at 04:50 AM
  2. Yeah, sorry, I should have explained that. The plugin adds a polymorphic join from the audited models to the Audit model, so you can retrieve all the audits for a model by calling:
      User.find(:first).audits
    
    If you have an instance of Audit, you can also call audit.auditable to get the model that the audit record is for.
    Brandon Brandon
    July 21, 2006 at 05:22 AM
  3. So, can we version and audit a model?
    bryanl bryanl
    July 21, 2006 at 05:50 AM
  4. Well, there shouldn't be anything that stops you from using the acts_as_versioned in combination with acts_as_audited. But on that note, I'm toying with the idea of building in the ability to revert individual fields using the audit record. I'd love to hear poeple's thoughts on that.
    Brandon Brandon
    July 21, 2006 at 06:18 AM
  5. Wow, way cool plugin. Somehow Rails just keeps drawing me in. :)
    Matt Morton Matt Morton
    July 26, 2006 at 04:12 AM
  6. The plugin looks good but I am trying to understand why I would use this instead of the "acts_as_versioned" plugin? It seems that the "acts_as_versioned" plugin can provide the same information but in a more usuable form. With the acts_as_versioned plugin I can instantionate the object at the time it was versioned. The only big advantages I see for this plugin over acts_as_versioned are: * It should use less space since it is only recording what changed and not the entire record. * Since the audit trail is stored in one table you could easily get a list of changes across all models in one query. With the acts_as_versioned plugin it seems that it would be more difficult to get all the changes made by user X in the last 5 days. This comment is not meant to critisize. I am just trying to understand the proper time to use this and the proper time to use acts_as_versioned.
    Eric Anderson Eric Anderson
    August 01, 2006 at 06:13 AM
  7. Eric, Thanks for your feedback. I think you're exactly right. I wrote acts_as_audited for a specific purpose: an audit trail. I want to be able to answer questions like, "show me all of the changes made by Sally", or "show me all of the changes for July 31". I could have done this all with acts_as_versioned, but not as easily, and I didn't want to double the number of tables in my database. Now, I have been thinking about adding versioning to acts_as_audited (but not for the project I working on now). It would basically be a polymorphic version of acts_as_versioned. We'll see, versioning isn't something that I have a need for right now. I'm open to any suggestions you may have.
    Brandon Brandon
    August 01, 2006 at 06:31 AM
  8. Wouldn't you know it... I just implemented something like this using callbacks. Wrangling with Observer proved to be too much of a hassle for the time being but this is much cleaner!
    Kevin Marsh Kevin Marsh
    August 04, 2006 at 06:07 PM
  9. IMHO, you should use the serialization feature of ActiveRecord::Base. It as easy as class Audit
    Michael Schuerig Michael Schuerig
    August 11, 2006 at 07:27 AM
  10. Sigh, your blog can't handle the less-than sign. class Audit; serialize :changes; end
    Michael Schuerig Michael Schuerig
    August 11, 2006 at 07:28 AM
  11. Michael, Thanks for the suggestion. I had thought about doing that and for some talked myself out of it (don't remember why). I will probably switch to that.
    Brandon Brandon
    August 12, 2006 at 06:05 AM
  12. Here's another change, I hope you can decipher it after the blog has mangled the formatting. This version of write_attribute ensures that the comparison of attributes is done with old and new values properly typecast. Also, it writes the new value every time it is called, not just the first time. def write_attribute(attr_name, attr_value) attr_name = attr_name.to_s if audited_attributes.include?(attr_name) @changed_attributes ||= {} if old_entry = @changed_attributes[attr_name] old_value = old_entry.first else old_value = self[attr_name] end super(attr_name, attr_value) new_value = self[attr_name] if new_value != old_value @changed_attributes[attr_name] = [old_value, new_value] end else super(attr_name, attr_value) end end
    Michael Schuerig Michael Schuerig
    August 13, 2006 at 11:07 PM
  13. Michael, Thanks! I've committed that along with serializing the changes.
    Brandon Brandon
    August 14, 2006 at 06:19 AM
  14. One more thing... I think has_many :audits, :as => :auditable should *not* nullify the referring foreign keys on destruction of the audited object. The auditable_id doesn't serve any purpose in the DB after the object is destroyed, but it is the only thing that ties together the events that happened to an object during its lifetime -- even after it is gone.
    Michael Schuerig Michael Schuerig
    August 15, 2006 at 05:46 AM
  15. Yeah, you're right. It's committed. Thanks!
    Brandon Brandon
    August 15, 2006 at 06:07 AM
  16. Thanks a lot for such great plugin, and I must agree wth you that sometimes there is no need for versions, but for a simple audit trail. Off Topic: This is just a quick note to invite you to register at RubyCorner.com, a meeting place for people interested in the Ruby Programming Language or any of the related technologies.
    Aníbal Rojas Aníbal Rojas
    August 21, 2006 at 06:24 PM
  17. Great plugin; ever thought of hooking it into the login_engine?
    Dewet Dewet
    August 28, 2006 at 10:15 AM
  18. I am not 100% sure about this one, but isn't it a bad idea to access the user id by calling User.current. I thought models shouldn't have access to such data. Or at least I always put such a call in the application controller / helper.
    namxam namxam
    August 29, 2006 at 10:08 AM
  19. Dewet, No, I haven't really used engines much. I tried using login_engine and found that it was going to take more work to get it working how I wanted than to just use one of the generator plugins. namxam, You're right, it is poor design. And it would be even poorer if Rails ran in a threaded environment. Ideally there should be some type of security system injected between the controller and the model that would audit what users did. Unfortunately that would be a lot more complicated than this simple plugin. Putting a class method on User that keeps track of the current user is the cleanest solution I can think of. I'm interested if you have any ideas.
    Brandon Brandon
    August 29, 2006 at 10:39 AM
  20. Hi, Does the serialization approach introduce a possible issue for existing data if Ruby itself is upgraded? (I come from java where one has to be quite careful here) Tks Greg
    GregH GregH
    August 30, 2006 at 06:54 PM
  21. PS Does "auditable_id" provide a unique identify for all data rows that changed for a particular user HTTP request into a controller does it? (i.e. if one action, e.g. user commits a multi-page form, the same auditable_id is used for all the database changes?). If this is the case could potentially it be a good idea to have an auditable_id type table which has a controller focused summary of the event (from the user perspective). If the user was then going to review audit they could see the overall summary of actions they have performed, and then click on one to then bring up the detailed data changes associated with this event? Comments?
    GregH GregH
    August 30, 2006 at 07:07 PM
  22. GregH, No, serialization is not an issue for different versions of ruby. The serialized verson is actually just a YAML dump of the attributes. auditable_id is the primary key of the originial record, auditable_type is the model of that record. acts_as_audited doesn't actually know anything about the HTTP reqeust or controller. It works the same way inside of an HTTP request as it does from the command line. What I've done on the frontend is use naming conventions to link to the original record. If my model name is "User", then I assume the controller name is "users". So, if I have an audit record with auditable_id => 10 and auditable_type => User, then I create a link by doing:
    <% link_to 'record', :controller => audit.autidable_type.downcase.pluralize, :action => 'show', :id => audit.auditable_id %>
    
    Which creates a link to "/users/show/10".
    Brandon Brandon
    August 31, 2006 at 05:14 AM
  23. Brandon - understood. Sounds good. I was actually thinking of a slightly different user case however. The concept I have is that there is a (a) user interaction audit/event type log and (b) the actual database data change audit trail [i.e. acts_as_audited]. For (a) the concept would be an audit entry (one record) for each user action carried out. This could often be a simple action that changes one data in one table, but at times could be more complex and make changes to numerous tables (e.g. 5/4/06 7.23am, GregH, Submitted request for purchase all cart items). This could be just one table that contained this. What would then be cool is to link this user level event audit to the underlying database tables/rows which were changed (i.e. the acts_as_audited table rows). So this might involve acts_as_audited code picking up a user action audit row ID. What do you think? Is there a better way to do this? Do you think acts_as_audited could be easily modified to do what I was suggesting? Cheers
    GregH GregH
    August 31, 2006 at 03:12 PM
  24. PSS. Can anyone help? Still can't work out how to get the userid column to log. I use ActiveRBAC for user authentication (just for background) and have acts_as_audited working for everything but for logging the userid. I'm not exactly sure how to setup the required class/objects with the appropriate method for acts_as_audited that it needs to get the username. Can anyone help out with: a) how to setup the appropriate class b) probably more to the point, how to make sure it's in the required context for acts_as_audited to get (i.e. I guess it needs to be visible from the .rb even though it would no doubt get created in the controller which has access to the session no? For background, with ActiveRBAC I can issue the following to get the userid "current_user.login" which is based on use of an ActiveRBAC helper (I think helpers are available in views). Extract from the rbac_helper.rb: ============================= def current_user return @active_rbac_user unless @active_rbac_user.nil? @active_rbac_user = if session[:rbac_user_id].nil? then ::AnonymousUser.instance else ::User.find(session[:rbac_user_id]) end return @active_rbac_user end ========================= Tks in advance
    Greg Hauptmann Greg Hauptmann
    September 05, 2006 at 02:57 AM
  25. @Brandon: I think this is awesome. However, there's one thing I don't like and that's the use of cattr_accessor for the user id. I have a plugin that stores the user id on the table in the created_by field. It was based on a similar plugin which uses the same method as you. My approach was to overload the save and update_attributes methods of ActiveRecord to take an optional parameter.. the user id. If you're interested in refactoring your plugin to do something similar, hit me up at my email and we could discuss some more. I want to use your plugin and may modify it myself but I wanted to talk to you first. I love the plugin... great job!!!!
    Brian Hogan Brian Hogan
    September 05, 2006 at 04:30 AM
  26. Greg, I don't use ActiveRBAC, but what I did with acts_as_authenticated is replace the use of an instance variable with User.current_user. So in your code, replace all instances of @active_rbac_user with User.curent_user.
    Brandon Brandon
    September 05, 2006 at 05:37 AM
  27. Brian, I agree, I don't like it either. However, it is the most unobtrusive way I can think of. I want to stay way from modifying the API so that it can be added and removed from apps without breaking or having to change all your code. With the current implementation, you only have to change a few lines in your login code. What would be ideal is if Rails' around filter didn't force you to have separate before_filter and after_filter methods so you could just use a block for auditing. Something like:
    Audit.as_user(current_user) do
      // continue processing filter chain & actions
    end
    
    That being said, I am interested in seeing your implementation. Feel free to email me a patch.
    Brandon Brandon
    September 05, 2006 at 05:52 AM
  28. The way I do it is by allowing the save method to take an optional parameter. This way nothing breaks. If the developer doesn't pass the id through the save or update_attributes, then the record does not get stamped. I'll send you a link to my code (it's tiny) and you can see if it would work for you.
    Brian Hogan Brian Hogan
    September 05, 2006 at 07:39 AM
  29. Brandon - do you mind if I could ask for a quick explanation of how the userid passing and contexts work here. I'm just a little confused (still new to Ruby). For example: * in your controller set User.curent_user to @active_rbac_user (i.e the userid). So is this mean I'm effectively creating a class User with an attribute "current_user" on the fly? * In the Model class one does "acts_as_audited :user_class_name => 'User', :user_method => 'current'", and cattr_accessor :current_user - can the model context "see" the User object created in the controller? How does this work? - how does the "cattr_accessor" help here? Thats the main thing for me, just trying to understand how the userid is passed through. I also have several other questions if you've got time, posted at: http://www.ruby-forum.com/topic/79999#new Thanks again
    Greg Hauptmann Greg Hauptmann
    September 05, 2006 at 02:05 PM
  30. [...] Thanks to Michael Schuerig for pointing out that malicious users could unassociate your audit records due to the use of has_many in acts_as_audited. has_many :audits creates an attribute accessor called audit_ids on the model objects that you declare acts_as_audited, which could allow users to pass an array of ids that would overwrite the actual audit records. [...]
    opensoul.org » acts_as_audited Security update opensoul.org &raquo; acts_as_audited Security update
    September 07, 2006 at 05:11 PM
  31. Hi, Does anyone have the code to read in the changes (stored in yaml) and display them in a view they could post? For a given record in the audits table for example, and the yaml that is stored in the changes column. I have read in the yaml from the changes column for a given audit table entry, but just now wondering how to consume this and display it (my first time with YAML) Thanks Greg
    Greg Hauptmann Greg Hauptmann
    October 10, 2006 at 01:16 PM
  32. Greg, ActiveRecord handles that for you, so you should just be able to treat the changes as a hash.
    @user.autits.first.changes[:username]
    
    Brandon Brandon
    October 10, 2006 at 03:27 PM
  33. Hello This is a fantastic plugin! Previously I was using my own solution to create an audit trail, but that involved adding code into every update method - messy and certainly not DRY. This acts_as_audited is really neat. There's only one downside compared with the manual approach I used to use - I need to record the reason /why/ a change was made. Is there any way the plugin could be modified so that it could take an optional parameter and record a reason for the change? I think this would be very useful for most people who need to keep an aufit trail. Many thanks for a brilliant plugin. Tim
    Tim Tim
    October 12, 2006 at 05:53 AM
  34. Hi. This plugin is great; exactly what I wanted - although it took me quite a while to get the user_id correctly logging my acts_as_authenticated user; and in a horribly hacky way. Is there a recommended technique to achieve this? On a second note, I have two classes of people updating my records; Administrators update records at the back end, and Clients can update their own records at the front end. At the moment, my audit table contains my user_id, but I don't know if that refers to an Administrator or a Client. Is there a way I can record which model that user_id refers to? (Or can this be a feature request if there's no way to do it! :-) Thanks for this great plugin - and any advice will be warmly received. NeilS.
    NeilS NeilS
    October 20, 2006 at 03:52 AM
  35. NeilS, The user_id is a big hack that I'm not very happy with. The way I suggest to do it above doesn't actually work, it causes concurrency issues. Someone actually suggested overwriting the ActiveRecord save method to accept the user as an argument. I'm hesitant to do that because I don't want to change the API, but that may be the best solution. As to your second question, yes, I think you could use a polymorphic association between the Audit model and your Administrator and Client models. I'll look into it. Thanks, Brandon
    Brandon Brandon
    October 20, 2006 at 05:23 AM
  36. Have you considered using Sweepers to figure out the current user? I stole the idea from the Rails recepies book to add auditing to one of my projects, and it's worked well thus far.
    Chaz Chaz
    November 07, 2006 at 12:29 PM
  37. Any thought on how this plugin could use Apache's REMOTE_USER variable to integrate with users that have authenticated against LDAP but have no associated model/table?
    vasudeva vasudeva
    November 14, 2006 at 01:02 PM
  38. Chaz: no, I hadn't, but that's a great idea! Thanks for pointing that out. I'll try to incorporate that in the next couple weeks. vasudeva: at the moment, there isn't really a way to do that. But I'm going to be working on some changes in the next couple weeks. I'll take that into account.
    Brandon Brandon
    November 14, 2006 at 01:20 PM
  39. Great. So far the plugin works beautifully otherwise; it's exactly what I needed. Can't show it to the boss until we can successfully track changes by user. I'll probably be reloading this page twice daily to see if you find a way to work that in. ;)
    vasudeva vasudeva
    November 14, 2006 at 01:34 PM
  40. vasudeva: I've made some updates to the plugin that will allow you to set the user in the audit record to a string in addition to an ActiveRecord model. Just add a method to your controller called current_user that returns the username. Let me know how it works out for you.
    Brandon Brandon
    November 19, 2006 at 12:43 AM
  41. Hi Brandon, I saw you plugin and thought it would work perfectly for my needs. However I am having small problem when I try to run the migration "script/generate audited_model add_audits_table" I just get this error "Couldn't find 'audited_model' generator" I assume I am doing something wrong, but I can't figure out what it is?
    Peer Allan Peer Allan
    November 29, 2006 at 09:02 AM
  42. Peer Allen: Sounds like it's not picking up the generators in the plugins directory. What happens when you run script/generate without any arguments? Here's what I see for installed generators:
    Installed Generators
      Plugins: audited_migration
      Builtin: controller, integration_test, mailer, migration, model, plugin, scaffold, session_migration, web_service
    
    Brandon Brandon
    November 29, 2006 at 09:42 AM
  43. Peer Allen: Ah, I didn't notice it when I first glanced at your comment, but it should be audited_migration, not audited_model.
    Brandon Brandon
    November 29, 2006 at 09:44 AM
  44. Whoops, I guess I should get a little more sleep before attempting to tackle blatantly obvious tasks. Thanks Brandon.
    Peer Allan Peer Allan
    November 29, 2006 at 12:22 PM
  45. There is something wrong and I can’t lay my finger on it. In a view that lists found entries from the audits table, these work… <%= audit.auditable_type %> <%= audit.username %> <%= audit.action %> <% for change in audit.changes ><= change >
    <
    end %> <%= audit.created_at %> <%= audit.auditable_id %>

    but no matter what, this will toss an syntax error…

    <%= (audit.auditable_type == ‘Placement’ ? (link_to ‘Show Placement’, { :controller => ‘placements’, :action => ‘show’, :id => audit.auditable_id } )) %>

    so I cannot make conditional links to the actual records which is something I would really like to do

    Craig

    Craig White Craig White
    December 28, 2006 at 02:50 PM
  46. Hi,

    A newbie here.. I thought that this plugin is the best suited for my need. I am able to install it, but I cannot move on after executing

    ruby script/generate audited_migration add_audits_table

    I get some error. It says:
    script/../config/../vendor/rails/activerecord/lib/../../activesupport/lib/act
    ive_support/dependencies.rb:123:in `const_missing': uninitialized constant ActionController (NameError) from script/../config/../vendor/plugins/acts_as_audited/init.rb:8:in
    `load_plugin' from script/../config/../vendor/rails/railties/lib/initializer.rb:348
    :in `load_plugin' from ...

    Can you please help me? Many thanks!

    Chris Chris
    January 05, 2007 at 10:10 PM
  47. Chris,

    Not sure what is causing that error. What version of Rails are you using? This plugin was developed using 1.1.x, but I’m using it in a 1.2 project now, so I know it works.

    Brandon Brandon
    January 09, 2007 at 11:33 PM
  48. Thanks Brandon.

    I am now into 1.2.1. Tried it again, but i’m still getting the same error.

    What do you think could be wrong? Any insight on this?

    Many thanks!

    Chris Chris
    January 19, 2007 at 09:52 PM
  49. I’m reading about the plugin and I knows is what I nees because I need to ansewer the same kind of questions than Brandon. The problem is that is my first time with ruby, and with plugin, and eventhough I understand english, I ``m not really sure how can i use this wonderful act_as_audited… I`m working on that. so if anybody has something that explains it in spanish, or step by step in english, I’ll be thankful, happy, and closer to get this think working. thanks!

    Camila Camila
    January 25, 2007 at 10:30 AM
  50. Ok I finally got it. I didn`t know where to make the changes; now I understand that in every model you want to audit you have to put: act_as_audited Now here is my new question: I have a table “people” that is related wuith the table “area” through the table “peoplearea”, meaning that each person may have many areas. The “peoplearea” and the “people” models are being audited, and it works. The problem is that if I’m editing a person info, and I add or delete a new area, the “audits” table show this changes as destroying or creating the table “peoplearea” and I need to be saved as a change made over the person. Is this possible with this plugin?

    Camila Camila
    January 25, 2007 at 01:39 PM
  51. Camilia:

    Nope. It doesn’t record changes to associations. That’s not a feature that I need, but patches are welcome.

    Brandon Brandon
    January 25, 2007 at 04:41 PM
  52. I’ve finally got the plugin to work!!! It’s really great. It’s what I actually need.

    But, I need more help. Does the user object need to have exactly the same field names as the audits table (user_id, user_type, and username) for it to actually work? I tried to turn on caching for the dev environment, and to put the current_user method in the application controller, but it still doesn’t log anything under the 3 user columns.

    What must I do? Am I missing anything at all? Thanks!

    Chris Chris
    February 10, 2007 at 11:05 AM
  53. Chris,

    Does the user object need to have exactly the same field names as the audits table (user_id, user_type, and username) for it to actually work?

    Nope.

    Did you declare the models to audit in the controller?

    class ApplicationController < ActionController::Base
      audit User, List, Item
    end
    

    If you did, and it is auditing changes and not the user, then I would check to make sure that your current_user method on your controllers returns an ActiveRecord object or a string.

    Brandon Brandon
    February 10, 2007 at 05:19 PM
  54. I’ve been trying to interface this with the existing acts_as_authenticated for current_user support, but I cannot for the life of me update the username or associated user. Someone above has had this previously but did not provide any information.

    Skiz Skiz
    February 14, 2007 at 01:10 PM
  55. Skiz,

    I use it with acts_as_authenticated, it should “Just Work”(TM). Have you tried it in production mode? It wasn’t working for me in development the one time I tried (even with caching enabled), but I know it works in production.

    Brandon Brandon
    February 14, 2007 at 04:31 PM
  56. First off, thanks Brandon for an outstanding plugin. I am curious though, if it is possible to return(/display) all audits in total and chronologically as opposed to on a per user basis? I am a bit new to ror in general but I was thinking maybe I could make an additional model that also refers to the audits table. Would that work? Poor form? Is there another way? Thanks for any insight.

    josh josh
    February 22, 2007 at 04:12 PM
  57. josh,

    There already is a model for it:

    Audit.find(:all, :order => "created_at")
    
    Brandon Brandon
    February 22, 2007 at 04:31 PM
  58. Thanks for a simple, to the point plugin, works exactly as expected. Saved a lot of time. Is it listed in Rails Wiki, I could not find it there, had to rely on Google. Anyway great work.

    Deepak Kumar Deepak Kumar
    March 13, 2007 at 06:22 AM
  59. Thanks for the great plugin. I was wondering whether you could add a feature to selectively audit on action.
    acts_as_audited :on => [:update, :destroy]

    Its just that I dont usually audit the creation of a record, as this stands as the record itself until the first change which is audited.

    Adam Adam
    March 18, 2007 at 05:27 PM
  60. Adam,

    That’s a great suggestion. I probably won’t have time to add features to this plugin for a month or so, but I’m open to taking patches.

    Brandon Brandon
    March 19, 2007 at 01:01 PM
  61. I like this plugin, but I’ve run into a weird problem and I’m wondering if you have any advice on solving it.

    In my application I have an Address model, with a :coords attribute, implemented with GeoRuby’s Point class.

    I installed acts_as_audited and told it to audit Address. It works fine in production mode, and in development mode with caching enabled.

    However, when I run my unit tests, I get lots of errors about undefined methods on Point. If I take the audit statement out, the errors go away.

    Running a unit test in the debugger, I find that at the point when I try to call, say, Point.from_lon_lat (a class method), Point is a subclass of Object with no geometry-related methods, rather than the subclass of Geometry it should be.

    Obviously I’m not asking you to study up on GeoRuby or debug my problem for me, but I thought you might have some thoughts on why the presence of an audit statement somehow makes Point refer to a different class.

    Erik Erik
    March 31, 2007 at 07:00 PM
  62. Well, that didn’t take so long. The answer is that referring to the classes in application.rb changed the order in which they were loaded, revealing a previously hidden bug in my code.

    (In case anyone’s curious: I had a local extension to the Point class. When my extension was loaded after GeoRuby, it altered GeoRuby’s Point. But when the extension was loaded first, or without GeoRuby, it created its own Point class. The solution was to require GeoRuby and explicitly identify the Point class to be extended.)

    Anyway, uh… yeah! Cool plugin! Sorry for the interruption! Carry on!

    Erik Erik
    March 31, 2007 at 07:25 PM
  63. Brandon, thanks for the cool plugin! Just installed it and works like a charm.

    I am thinking of using the audits table as the source of input for a dashboard interface for my app. I am able to pull out several arrays of the relevant Audit records off the table and flatten them as one big array. E.g. latest list of messages, todo items, comments, etc. all in one array. However, before displaying them, Next I want to sort them according to created_at. I can update the audit.rb class directly and add the “def <=>(o)” method there but am wondering if there is a cleaner way of doing this?

    Meng Kuan Meng Kuan
    April 05, 2007 at 04:57 AM
  64. Meng,

    audits = Audit.find(:all, :order => 'created_at')
    
    #or
    audits.sorty_by(&:created_at)
    
    Brandon Brandon
    April 05, 2007 at 10:16 AM
  65. Brandon, This seems to be a great plugin. I have been trying to get it to work, but rails 1.2 doesn’t seem to like it when you use your models in the initialization of the ApplicationController. Has anyone else had this issue?
    class ApplicationController < ActionController::Base
    
      ...
    
      # Auditing - initalize autditing sweepers
      audit User, Account  
    
    protected
      include AuthenticatedSystem
    
      ...
    
    end
    
    class Account < ActiveRecord::Base
      acts_as_audited
    end
    
    class User < ActiveRecord::Base
      acts_as_audited :except => [:password]
    end
    
    The error:
    Expected C:/.../app/models/account.rb to define Account
    C:/.../vendor/rails/activesupport/lib/active_support/dependencies.rb:
    249:in `load_missing_constant'
    C:/.../vendor/rails/activesupport/lib/active_support/dependencies.rb:
    452:in `const_missing'
    C:/.../vendor/rails/activesupport/lib/active_support/dependencies.rb:
    464:in `const_missing'
    C:/.../vendor/rails/activesupport/lib/active_support/dependencies.rb:
    470:in `send'
    C:/.../vendor/rails/activesupport/lib/active_support/dependencies.rb:
    470:in `const_missing'
    C:/.../app/controllers/application.rb:19
    c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_re
    quire'
    ...
    

    I am running rails 1.2.3. What I found weird is that it fails on the second model and not the first. If I comment out the Account model in the audit statement in Application Controller, it works. Any suggestions?

    - Nicholas

    Nicholas Nicholas
    April 06, 2007 at 03:43 PM
  66. I cant even install this on Rails 1.2.3. The command line spits out the following errors…

    /usr/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/depende
    ncies.rb:266:in `load_missing_constant': uninitialized constant CollectiveIdea::
    ActionController (NameError)
            from /usr/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_su
    pport/dependencies.rb:452:in `const_missing'
            from script/../config/../vendor/plugins/acts_as_audited/init.rb:8:in `lo
    ad_plugin'
            from /usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/initializer.rb:40
    1:in `load_plugin'
            from /usr/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_su
    pport/core_ext/kernel/reporting.rb:11:in `silence_warnings'
            from /usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/initializer.rb:40
    1:in `load_plugin'
            from /usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/initializer.rb:18
    5:in `load_plugins'
            from /usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/initializer.rb:18
    5:in `each'
            from /usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/initializer.rb:18
    5:in `load_plugins'
             ... 6 levels...
            from /usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/generate
    .rb:1
            from /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in 
    `gem_original_require'
            from /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in 
    `require'
            from script/generate:3
    Steve Steve
    April 12, 2007 at 09:18 AM
  67. Steve,

    $ rails --version
    Rails 1.2.3
    $ rails test
    <truncated>
    $ cd test
    $ script/plugin install http://source.collectiveidea.com/public/rails/plugins/acts_as_audited
    + ./acts_as_audited/CHANGELOG
    + ./acts_as_audited/README
    + ./acts_as_audited/Rakefile
    + ./acts_as_audited/generators/audited_migration/USAGE
    + ./acts_as_audited/generators/audited_migration/audited_migration_generator.rb
    + ./acts_as_audited/generators/audited_migration/templates/migration.rb
    + ./acts_as_audited/init.rb
    + ./acts_as_audited/install.rb
    + ./acts_as_audited/lib/acts_as_audited.rb
    + ./acts_as_audited/lib/audit.rb
    + ./acts_as_audited/lib/audit_sweeper.rb
    + ./acts_as_audited/tasks/acts_as_audited_tasks.rake
    + ./acts_as_audited/test/acts_as_audited_test.rb
    + ./acts_as_audited/test/audit_test.rb
    + ./acts_as_audited/test/database.yml
    + ./acts_as_audited/test/fixtures/user.rb
    + ./acts_as_audited/test/schema.rb
    + ./acts_as_audited/test/test_helper.rb
    

    It “works for me™” on a fresh rails 1.2.3 project. Feel free to email or IM me with more details.

    Brandon Brandon
    April 12, 2007 at 09:35 AM
  68. hey Brandon, thanks for the plugin… The username column is not being filled in in the audit table. I think its because my user model uses login instead of username as the human readable string that identifies the user. so in the audit.rb I changed the line self.username = user to self.username = user.login. But it still doesn’t work. Also current_user is already defined “we use the acts_as_auth..” plugin. Iam a little new to ruby but I can’t figure out how to get the username column to be filled in correctly.

    Tom Tom
    April 26, 2007 at 04:22 PM
  69. Great stuff, thanks.

    If I don’t specify audit in ApplicationController, then the sweepers aren’t registered and so audits aren’t created with user information.

    But if I specify audit on the controller, then the :except option to acts_as_audited is ignored on the observed models.

    Would be nice to have my cake and eat it too. :-)

    Sheldon Hearn Sheldon Hearn
    May 01, 2007 at 07:14 PM
  70. Sheldon,

    You should be able to have your cake and eat it too. Specifying audit in the controller simply calls acts_as_audited on the models IF it hasn’t already been called.

    Brandon Brandon
    May 01, 2007 at 07:23 PM
  71. Ok lets see if I can ‘splain this. I am using the beast forum software in my app In the post and topic model it specifies that body and title are attr_accessible respectively. (these are the only places in our app we do this) So I excepted all the other fields but I get this error NoMethodError (You have a nil object when you didn’t expect it! You might have expected an instance of Array. The error occurred while evaluating nil.each): /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1671:in `attributes=’ /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1505:in `initialize_without_callbacks’ /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:225:in `initialize’ /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations/has_many_association.rb:13:in `new’ /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations/has_many_association.rb:13:in `build’ .//app/controllers/topics_controller.rb:25:in `create’ /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/abstract/database_statements.rb:59:in `transaction’ /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:95:in `transaction’ .//app/controllers/topics_controller.rb:24:in `create’

    I tried removing the attr_accessible on those filds but I got other errors. So I guess my question is why doesn’t it work even when I exclude all the fields except the attr_accessible fields.

    Thanks in advance, Tom

    Tom Healey Tom Healey
    May 04, 2007 at 11:48 AM
  72. Brandon, In addition to my previous post I was wondering why when I disabled the cache the acts_as_audited plugin was still “in effect”.

    Tom Healey Tom Healey
    May 04, 2007 at 12:12 PM
  73. Tom,

    There’s actually a Rails bug that prevents you from using attr_protected and attr_accessible on the same model, and acts_as_audited uses attr_protected :audit_ids to prevent malicious users from messing with the audit associations.

    I’m not sure what you mean by your second question.

    Brandon Brandon
    May 05, 2007 at 12:23 AM
  74. Hello,

    Has anyone used this plugin with Goldberg (link below)? I am currently getting an error within Goldberg only when I have auditing enabled for a particular model. If I disable auditing by commenting out of the application controller, I no longer get any errors. This error only ocurrs when using Goldberg related functionality but the rest of the application works fine.

    Below is a bit more details of the issue. Dave from Goldberg Forums is currently assisting me with this but if anyone has used Goldberg and has a workaround, any guidance will be greatly appreciated. Thanks.

    Goldberg http://goldberg.240gl.org/home “Goldberg is a Ruby on Rails generator that enables you to set up fully-featured websites within minutes”

    Steps to Reproduce: Administration > Setup > Controllers / Actions Choose Application and then a controller. Then click on ‘Add New Action’ and error occurs immediately. It will also occur if you try clicking on other parts of interface or reaching other pages.

    Environment: Rails v1.2.3 Goldberg v0.2.0 MySQL 5.0.26-community-nt WEBrick Plugins: userstamp and acts_as_audited.

    Sincerely,

    ANR

    Error 1: NoMethodError in Goldberg/controller actionsController#new_for You have a nil object when you didn't expect it! The error occurred while evaluating nil.controller_name Error 2: NoMethodError in Goldberg/controller actionsController#show undefined method `controller_name' for nil:NilClass
    ANR ANR
    May 16, 2007 at 02:22 PM
  75. I’m using acts_as_authenticated and userstamp with acts_as_audited. Has there been a resolution on how to get the current_user in your controller without interfering with these plugins?

    Doesn’t including AuthenticatedSystem in my ApplicationController bring the current_user method to all controllers?

    For userstamp I’m also setting User.current_user (as a cattr_accessor) so if this solution is better, I could use this as well.

    I’ve read through many of the comments but haven’t reached a conclusion. I’m running out of ideas. Anyone?

    John John
    May 29, 2007 at 11:25 PM
  76. John,

    I’m not familiar with userstamp, but acts_as_audited should “Just Work™” with acts_as_authenticated if you’re declaring audit MyModel in your controller.

    The comments on this post about User.current_user are no longer relevant. acts_as_audited was updated and that has been removed.

    Brandon Brandon
    May 30, 2007 at 09:36 AM
  77. Thanks for the info Brandon (and sorry for the multiple posts. I got a proxy error and my post timed out several times. It must have posted anyway).

    I’ve tried using the “audit MyModel” within my App controller and the controller in question. The only method I’ve been able to get the auditable plugin working is by adding acts_as_auditable to the model. In this case it adds a record, but doesn’t put any data in the user fields.

    I’m going to do some more debugging to figure out what I’m missing. Do you have any other ideas?

    John John
    May 30, 2007 at 11:17 AM
  78. John,

    Sounds like you’re running in development mode. Check out the caveats section in the post or the plugin’s README.

    Brandon Brandon
    May 30, 2007 at 11:23 AM
  79. Right on! I had the cache vars set to true but rails reset one of them to false at a later spot in the dev environment config.

    Everything seems to be working great. Thanks for the plugin! I remember implementing something like this in PHP about a year ago without a framework and it was a hassle.

    John John
    May 30, 2007 at 12:55 PM
  80. The topic seems to have been dropped, but way back in the early comments you mention possibly adding versioning (allow individual fields to be reverted). I definitely vote for this!

    I just took out acts_as_versioned from my app because it is simply too much hassle. Requires two database tables for every model which I got sick of maintaining. I also think acts_as_versioned gave me a big speed hit but I’m still testing that.

    Anyway, the “log the diffs” model is much better for versioning IMHO. Thanks!

    carlivar carlivar
    June 11, 2007 at 03:15 AM
  81. carlivar,

    Just for you, I’ve updated acts_as_audited to include some sort of revisioning. Check out my latest post for more info. I’d love to get your feedback.

    Brandon Brandon
    June 18, 2007 at 03:02 PM
  82. This works:

    class PatientDetail < ActiveRecord::Base

    acts_as_audited

    end

    if I add the following it ceases to work

    class ApplicationController < ActionController::Base # Pick a unique cookie name to distinguish our session data from others’ session :session_key => ‘_ClinicCare_session_id’

    audit PatientDetail
    protected
    def current_user
      @user = Goldberg.user ? Goldberg.user.name : '(not logged in)'
    end

    end

    if I comment out “audit PatientDetail” it will work again but does not pass the user through to the log. I have checked to see if there is a value for @user and there is one.

    I have run the tests and they pass.

    Any help is good help:)

    John Devine John Devine
    July 12, 2007 at 03:54 AM
  83. John,

    Please read the caveats section in the post and the plethora of other comments about the same issue. audit Model in the controller depends on Rails caching, and is therefore is disabled by default in development mode.

    Brandon Brandon
    July 12, 2007 at 04:12 AM
  84. would be cool to have the possibility to completely disable the saving of a delete-action, since this makes trouble, the referring objects are not existing anymore…

    hans reiser hans reiser
    July 17, 2007 at 06:48 PM
  85. Hi Brandon,

    Thanks for this plugin, I just have one quick question though.. I can’t seem to get the user columns filled in.

    If I have done this in my dev environment:

    config.cache_classes = true
    config.action_controller.perform_caching = true
    

    and this

    class MyModel < ActiveRecord::Base
      acts_as_audited
    end
    

    where should I put the

    protected
      def current_user
        @user ||= User.find(session[:user])
      end
    

    such that I may have the three user columns filled up? Sorry if this is a very lame question. I’m not too good with RoR. :)

    Thanks so much in advance!

    Kris Kris
    August 05, 2007 at 02:02 AM
  86. Kris,

    See the “Auditing in Rails” section above. It shows that you need to declare “audit MyModel” and define current_user in ApplicationController.

    Brandon Brandon
    August 05, 2007 at 08:05 AM
  87. Hi, I have one problem, I’m auditing an “Article” model wich has “Title” and “Text” fields. The problem is, when I create an article, the event is logged correctly, but in the “changes” column I have something like this:

    - title:

    - a title text:

    - some text

    So when I access the Article in this way: article.audits.first.changes I get {“text”=>[nil, “a title”], “title”=>[nil, “some text”]}

    so that article.audits.first.changes[:title] returns NIL

    What’s wrong? The problem is the same with every model and field, I always have a NIL object first.

    Thanks, and I hope that this will be useful for others with the same problem

    Bugbuster Bugbuster
    August 07, 2007 at 06:45 AM
  88. Bugbuster,

    For each modification, acts_as_audited stores an array with the old attribute and the new attribute. On create, the old attribute obviously didn’t exist, so it will always be nil. So to get to the new attribute, you could do article.audits.first.changes[:title].last

    Brandon

    Brandon Brandon
    August 07, 2007 at 08:53 AM
  89. There was a comment made about the ability to ignore delete / destroy actions altogether. I agree, but I found an option that I liked better for my situation, acts_as_paranoid. This plugin overrides destroy by setting the deleted_at timestamp column to the current time. It also overrides find and count to ignore “deleted” items. This still effectively deletes them, but allows this plugin to resurrect them. Hope this helps someone, and Brandon, thanks for the great plugin.

    Bill Pratt Bill Pratt
    August 09, 2007 at 12:22 PM
  90. I also noticed that this will create an audit trail even if no attributes are changed. This may be necessary for some instances, but I don’t care if nothing is actually modified. Changing line 167 in lib/acts_as_audited.rb to:

    self.audits.create(:changes => @changed_attributes, :action => action.to_s, :user => user) if !@changed_attributes.empty?

    will stop this.

    Bill Pratt Bill Pratt
    August 09, 2007 at 02:21 PM
  91. Regarding acts_as_paranoid, it seems that Rick does no longer endorse it.

    Still being a Ruby newbie, I managed to write a revive method that will revive destroyed records. In order to get this to work, I added a before_destroy callback that will add every attribute’s value to the @changed_attributes hash so that the full state of the model is audited. I’ve posted a diff here. If there’s a better way to do this, I’m all ears.

    Using acts_as_audited, is there a way to have the user attributes in the audits table be populated also when using script/console or are the sweepers depending on being used through a running app?

    Peter Peter
    August 11, 2007 at 07:33 PM
  92. Peter,

    There is a #revision method on Audit that will reconstruct the model for that revision using the audits.

    At this time, there isn’t a way to populate the user field from the console. It would probably be a nice addition though.

    Brandon Brandon
    August 17, 2007 at 11:12 AM
  93. Hi Brandon, I seems to gr8 plug in over acts_as_versioned in space saving. I have one problem that, If some change are made in the table and I dont know on what column changes are made, then how would I fetch this changes. Or rather display the changes in well manner. Because if I chack for any column with name then it give nil method error if for that column no changes are made. Please reply if anyone has the solution.

    Thanks

    Bhushan Ahire Bhushan Ahire
    August 23, 2007 at 03:20 AM
  94. If I am understanding this correctly, reverting to a specific version number actually reverts to the number preceding that number? (i.e. to revert to the first version I need to select revision 2, to revert to version 3 I select and save version 4)

    Or am I doing something incorrectly?

    task.revision(4) task.save This will give me version 3.

    BTW – This plugin is amazing.

    Bilson Bilson
    August 23, 2007 at 05:33 PM
  95. Hi Brandon

    I am using acts_as_audited for audit trail in production environment.

    In order to view the audits table within my apps, I had set up a controller with a ActiveScaffold config block.

    AS displays fine in List but when I click next or any of the pages in the pagination, I get the following message

    TypeError in Audits#update_table Showing vendor/plugins/active_scaffold/frontends/default/views/ _list_record.rhtml where line #7 raised:

    BigDecimal can’t be coerced into BigDecimal

    How can I workaround this BgDecimals issue. BTW, I have absolutely no issue in using Active Scaffold to display the other 200 _ tables in my apps . Only in the audits atble am I having the BifDecimnal issue.

    CH Chee CH Chee
    August 25, 2007 at 01:10 AM
  96. Ch Chee,

    If you would leave a real email address, you would have gotten my email the last time you posted explaining that I’m pretty sure the issue you’re running into is not an acts_as_audited issue. I’ve never used ActiveScaffold, so I can’t help you out.

    Brandon Brandon
    August 25, 2007 at 09:50 AM
  97. Hi Brandon

    TQ for responding !

    I have found the solution to the Big Decimal issue as it is actually an unfixed rails issue and Michael Raiden has a temporary solution which actually works !

    BTW, the email adress is my working email address and recently the spam filter had been tightened up. Were you getting the BW filter rejection ?

    CH Chee CH Chee
    August 25, 2007 at 10:21 AM
  98. nice to see this plug-in, I had created something very similar to this for my rails environment, with the exception of making it standalone gem usable at the model level, and not polymorphicly associating it with the audited class.

    Sal Scotto Sal Scotto
    August 31, 2007 at 03:25 PM
  99. you should add these to your exception filter ‘changed_on’, ‘updated_at’, ‘created_at’, ‘created_on’, ‘updated_on’, ‘type’, ‘position’, ‘lock_version’, ‘parent_id’, ‘lft’, ‘rgt’, ‘quote’, ‘template’]

    Sal Scotto Sal Scotto
    August 31, 2007 at 03:27 PM
  100. Sal Scotto,

    id, type, lock_version, updated_at and created_at are already ignored. I could see situations where people would actually want to audit the other fields, so I don’t think that ignoring them in the plugin is a good idea. You can add those for your specific model.

    Brandon Brandon
    August 31, 2007 at 03:52 PM
  101. Hello, first of all nice plugin. i have a problem like a few people here. i can’t get the plugin to log user data. caching settings are done- the plugin isn’t logging any user data on a linux production box, though.

    For authentication i’m using the latest active_rbac version. active_rbac provides a current_user function that is mixed in from active_rbac. current_user gives me a User record. does the plugin need the @user variable, or is a valid AR object from current_user enough? any hints to get it work ith the latest active_rbac version?

    the latest active_rbac demo version can be found at: svn://rubyforge.org/var/svn/active-rbac/active-rbac/trunk/demo

    would be great if could have look why it isn’t working as axpected.

    tia

    Daniel

    Daniel Daniel
    September 04, 2007 at 05:37 AM
  102. Hi,

    I’m trying to use this plugin with restful_authentication (should be the same as acts_as_authenticated).

    At first I was setting my config incorrectly (in my main environment.rb file instead of development.rb), but after fixing this mistake I get correctly logged user_id and user_type. However, I don’t get username logged.

    I don’t have username field in my User model – I’ve got custom full_name method that uses 2 fields – first_name and last_name.

    Is there any easy step by step guide how to make username work with custom field/method in User model?

    Thank you in advance

    Szymon Szymon
    September 14, 2007 at 10:08 AM
  103. Szymon,

    The username field is just for those that don’t have a user model and just want to store the user as a string (e.g. using HTTP basic auth). If you have a user model, it will set the user_id and user_type, which should be all you need.

    Brandon Brandon
    September 14, 2007 at 10:15 AM
  104. I modified the plugin a bit to do some things I need it to do in my application.

    Find all audits of an audited class acts_as_audited.rb
            def audits(*args)
              options = {:conditions => {:auditable_type => class_name}, :order => 'audits.version desc'}.merge!(extract_options_from_args!(args))
              Audit.find(:all, options)
            end
    
    Supply either one or an array of audited classes, along with optional arguments, and have audit records returned audit.rb
      def self.pool(classes, *args)
        classes = classes.is_a?(Array) ? classes : [classes]
        options = extract_options_from_args!(args).merge!({:conditions => ["audits.auditable_type in (?)", classes.map{|x| x.class_name}]})
        find(:all, options)
      end
    
    Brennan Dunn Brennan Dunn
    September 16, 2007 at 01:26 PM
  105. wow I make it work with acts_as_rateable and hmp =)

    Rustam Rustam
    October 09, 2007 at 09:18 AM
  106. So, since Restful Authentication defines login, password, and password as attr_accessible. Does that mean I can’t define my User model as auditable?

    thanx!

    skwasha skwasha
    October 14, 2007 at 11:43 PM
  107. Howdy Brandon?

    First off, great plugin, it is what I have been looking for and it fits my needs for the most part. However, I wonder if I could audit on association columns as well. I mean, I have a Requirement model class that “habtm” Projects model class. So, anytime, a user makes a change only to associate/disassociate which projects are affected/not affected, system essentially made a change in the associated column (Projects model) which means that “audit” did not pick any change. This is a dilemma, even though, I understand that the change occurred in the Projects model yet from the user standpoint, the change occurred on the Requirements.

    Please help! Any ideas to go about resolving this problem will greatly help.

    Thank you very much for considering. —Sachin

    Sachin Sachin
    November 15, 2007 at 08:40 PM
  108. Nice plugin =)

    ynw ynw
    November 25, 2007 at 10:41 AM
  109. Sachin,

    As I mentioned in a comment above, auditing associations is not a feature I needed, but I’d gladly accept a patch that implements it.

    To solve your problem, you could change the habtm to a has_many :through and audit the join a model.

    Brandon Brandon
    November 26, 2007 at 11:00 PM
  110. Does acts_as_audited work with Rails 2.0? I tried running the migration and get a missing acts_as_list method error, I assume because that is now a plugin in Rails 2….

    /opt/local/lib/ruby/gems/1.8/gems/activerecord-1.99.0.8178/lib/active_record/base.rb:1334:in `method_missing’: undefined method `acts_as_list’ for #<class:0x134aed0> (NoMethodError) from /Users/epugh/Documents/code/hightechcville/trunk/vendor/plugins/acts_as_audited/lib/audit.rb:14

    Eric Pugh Eric Pugh
    November 28, 2007 at 10:40 AM
  111. acts_as_audited does work with Rails 2.0.

    However, you have to go through a little bit of pain with acts