{"id":213,"date":"2010-05-27T10:53:50","date_gmt":"2010-05-27T10:53:50","guid":{"rendered":"http:\/\/nicholshayes.co.uk\/blog\/?p=213"},"modified":"2010-05-27T15:40:27","modified_gmt":"2010-05-27T15:40:27","slug":"logging-activity","status":"publish","type":"post","link":"http:\/\/nicholshayes.co.uk\/blog\/?p=213","title":{"rendered":"Logging activity within a Rails application"},"content":{"rendered":"<p>I wanted to add a tool to log users&#8217; activity in my current application. I wanted something simple, and decided that the solution was to combine two techniques:<\/p>\n<p>One: making session data available at the model and observer level. <a href=\"http:\/\/www.zorched.net\/2007\/05\/29\/making-session-data-available-to-models-in-ruby-on-rails\/\">An article by Geoff Lane<\/a> gave me the information I needed to be able to achieve that.<\/p>\n<p>Two: use an observer to catch Model call backs and trigger an action in response. The Rails api documentation gave me the information I needed on creating an <a href=\"http:\/\/api.rubyonrails.org\/classes\/ActiveRecord\/Observer.html\">observer<\/a>.<\/p>\n<p>Here is a simple example as to how I was able to add an activity logger using these two techniques.<\/p>\n<p>First I created a new module:<\/p>\n<div class=\"codecolorer-container ruby default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"ruby codecolorer\"><span class=\"kw1\">module<\/span> MakeUserAvailableViaThread<br \/>\n&nbsp; <span class=\"kw1\">def<\/span> user_in_thread<br \/>\n&nbsp; &nbsp; <span class=\"kw4\">Thread<\/span>.<span class=\"me1\">current<\/span><span class=\"br0\">&#91;<\/span><span class=\"re3\">:user<\/span><span class=\"br0\">&#93;<\/span><br \/>\n&nbsp; <span class=\"kw1\">end<\/span><br \/>\n<br \/>\n&nbsp; <span class=\"kw1\">def<\/span> <span class=\"kw2\">self<\/span>.<span class=\"me1\">user_in_thread<\/span>=<span class=\"br0\">&#40;<\/span>user<span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw4\">Thread<\/span>.<span class=\"me1\">current<\/span><span class=\"br0\">&#91;<\/span><span class=\"re3\">:user<\/span><span class=\"br0\">&#93;<\/span> = user<br \/>\n&nbsp; <span class=\"kw1\">end<\/span><br \/>\n<span class=\"kw1\">end<\/span><\/div><\/div>\n<p>This was based on Geoff Lane&#8217;s UserInfo module. I made two changes: I used a more descriptive module name; and I changed the method name because current_user was already used in the controller by my authentication system (&#8216;restful_authentication&#8217;). The later stopped me logging in successfully until I spotted the problem.<\/p>\n<p>I then added these two lines to my ApplicationController:<\/p>\n<div class=\"codecolorer-container ruby default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"ruby codecolorer\">&nbsp; <span class=\"kw1\">include<\/span> MakeUserAvailableViaThread<br \/>\n&nbsp; before_filter <span class=\"re3\">:pass_current_user_to_thread_where_it_can_be_seen_by_observer<\/span><\/div><\/div>\n<p>Together with this private method:<\/p>\n<div class=\"codecolorer-container ruby default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"ruby codecolorer\">&nbsp; <span class=\"kw1\">def<\/span> pass_current_user_to_thread_where_it_can_be_seen_by_observer<br \/>\n&nbsp; &nbsp; MakeUserAvailableViaThread.<span class=\"me1\">user_in_thread<\/span> = <span class=\"kw2\">self<\/span>.<span class=\"me1\">current_user<\/span><br \/>\n&nbsp; <span class=\"kw1\">end<\/span><\/div><\/div>\n<p>So very similar to Geoff Lane&#8217;s code except with more descriptive method names.<\/p>\n<p>I then needed to add the observer:<\/p>\n<div class=\"codecolorer-container ruby default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"ruby codecolorer\"><span class=\"kw1\">class<\/span> ActivityLogger <span class=\"sy0\">&lt;<\/span> <span class=\"re2\">ActiveRecord::Observer<\/span><br \/>\n&nbsp; observe <span class=\"re3\">:account<\/span>, <span class=\"re3\">:license<\/span><br \/>\n&nbsp; <br \/>\n&nbsp; <span class=\"kw1\">include<\/span> MakeUserAvailableViaThread<br \/>\n<br \/>\n&nbsp; <span class=\"kw1\">def<\/span> after_update<span class=\"br0\">&#40;<\/span>record<span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw3\">puts<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;HEY! something changed: #{user_in_thread.full_name if user_in_thread} altered #{record.class.to_s} #{record.id}&quot;<\/span><span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; <span class=\"kw1\">end<\/span><br \/>\n<br \/>\n&nbsp; <span class=\"kw1\">def<\/span> after_create<span class=\"br0\">&#40;<\/span>record<span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw3\">puts<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;HEY! something changed: #{user_in_thread.full_name if user_in_thread} created #{record.class.to_s} #{record.id}&quot;<\/span><span class=\"br0\">&#41;<\/span><br \/>\n&nbsp; <span class=\"kw1\">end<\/span><br \/>\n<span class=\"kw1\">end<\/span><\/div><\/div>\n<p>However, nothing happened until I added this line to config\/environment.rb:<\/p>\n<div class=\"codecolorer-container ruby default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"ruby codecolorer\">config.<span class=\"me1\">active_record<\/span>.<span class=\"me1\">observers<\/span> = <span class=\"re3\">:activity_logger<\/span><\/div><\/div>\n<p>Note that config.active_record.observers only gets called at application start, so you may need to stop and start your application before the observer starts working.<\/p>\n<p>I now have a system I can extend to log activity. As shown above, only changes to accounts and licenses are logged, and the log just outputs to the console at the moment. However, now I have the mechanism, it is easy to create a new ActivityLog model and save the log entries to that in whatever format I fancy.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I wanted to add a tool to log users&#8217; activity in my current application. I wanted something simple, and decided that the solution was to combine two techniques: One: making session data available at the model and observer level. An &hellip; <a href=\"http:\/\/nicholshayes.co.uk\/blog\/?p=213\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[3],"tags":[],"_links":{"self":[{"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/213"}],"collection":[{"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=213"}],"version-history":[{"count":8,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/213\/revisions"}],"predecessor-version":[{"id":222,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/213\/revisions\/222"}],"wp:attachment":[{"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=213"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=213"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/nicholshayes.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=213"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}