{"id":18,"date":"2008-02-09T20:15:29","date_gmt":"2008-02-10T01:15:29","guid":{"rendered":"http:\/\/unixmonkey.net\/?p=18"},"modified":"2008-02-09T20:15:29","modified_gmt":"2008-02-10T01:15:29","slug":"getting-attachment_fu-to-play-nice-with-acts_as_versioned","status":"publish","type":"post","link":"https:\/\/unixmonkey.net\/?p=18","title":{"rendered":"Getting attachment_fu to play nice with acts_as_versioned"},"content":{"rendered":"<p>If you&#8217;ve ever wanted to keep track of revisions to document files or images in your Rails app, you are likely to want to use Acts_as_versioned, which is the authority on versioning database records, and Attachment_fu, which is the authority on uploading files with Rails.<\/p>\n<p>The problem is that they don&#8217;t know about each other and will step on each other&#8217;s toes without some changes. This article serves as a quick introduction to each, and shows how to make the two plugins get along like best friends.<\/p>\n<p><a href=\"http:\/\/svn.techno-weenie.net\/projects\/plugins\/acts_as_versioned\/\" title=\"techno-weenie.net\">Acts_as_versioned<\/a> was written by Rails Core Team member Rick Olsen (who also wrote <a href=\"http:\/\/svn.techno-weenie.net\/projects\/plugins\/attachment_fu\/\" title=\"svn.techno-weenie.net\">attachment_fu<\/a> and <a href=\"http:\/\/svn.techno-weenie.net\/projects\/plugins\/restful_authentication\/\" title=\"svn.techno-weenie.net\">Restful_authentication<\/a> among others) that essentially makes a mirror table of the one you want to version, and keeps every version of the record you are updating.<\/p>\n<p>Say I have a document table with fields like this:<\/p>\n<table>\n<tr>\n<td>id<\/td>\n<td>title<\/td>\n<td>description<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>rep08<\/td>\n<td>2008 report<\/td>\n<\/tr>\n<\/table>\n<p>Acts_as_versioned will add a column \u201cversion\u201d, and a separate table \u201cdocument_versions\u201d.<\/p>\n<table>\n<tr>\n<td>id<\/td>\n<td>title<\/td>\n<td>description<\/td>\n<td>version<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>rep08<\/td>\n<td>2008 report<\/td>\n<td>1<\/td>\n<\/tr>\n<\/table>\n<p>The document_versions table will look a bit like this<\/p>\n<table>\n<tr>\n<td>id<\/td>\n<td>document_id<\/td>\n<td>title<\/td>\n<td>description<\/td>\n<td>version<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>1<\/td>\n<td>rep08<\/td>\n<td>2008 report<\/td>\n<td>1<\/td>\n<\/tr>\n<\/table>\n<p>Setting up acts_as_versioned is pretty simple, I got most of my introduction to it from <a href=\"http:\/\/www.urbanhonking.com\/ideasfordozens\/archives\/2006\/02\/learns_to_use_a_1.html\">urbanhonking.com<\/a><\/p>\n<p>Now every time you update the original document, the changes are saved in your main documents table, and the version column is incremented by 1.<\/p>\n<p>After a few edits of the document, you\u2019ll see the versioning information in the Document_versions table add up.<\/p>\n<table>\n<tr>\n<td>id<\/td>\n<td>document_id<\/td>\n<td>title<\/td>\n<td>description<\/td>\n<td>version<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>1<\/td>\n<td>rep08<\/td>\n<td>2008 report<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>1<\/td>\n<td>rep08<\/td>\n<td>2008 report changed<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>1<\/td>\n<td>rep08 chgd<\/td>\n<td>2008 report changed<\/td>\n<td>3<\/td>\n<\/tr>\n<\/table>\n<p>Great! We can now use some of acts_as_versioned\u2019s built-in methods for determining if there are older versions, and be able to view or even revert to them.<\/p>\n<p>Now lets add the ability to upload a file to attach to a document record with attachment_fu.<\/p>\n<p>Attachment_fu is another plugin that makes uploading files and keeping track of them in the database relatively simple.<\/p>\n<p>A good intro to attachment_fu can be found on <a href=\"http:\/\/clarkware.com\/cgi\/blosxom\/2007\/02\/24\">Mike Clark\u2019s blog<\/a><\/p>\n<p>Attachment_fu would require a few changes to our documents table:<\/p>\n<table>\n<tr>\n<td>id<\/td>\n<td>title<\/td>\n<td>description<\/td>\n<td>version<\/td>\n<td>filename<\/td>\n<td>content_type<\/td>\n<td>size<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>rep08<\/td>\n<td>2008 report<\/td>\n<td>1<\/td>\n<td>rep08.jpg<\/td>\n<td>image\/jpeg<\/td>\n<td>2854<\/td>\n<\/tr>\n<\/table>\n<p>Don\u2019t forget to add the same fields to your documents_versions table, too.<\/p>\n<p>Once we\u2019ve added the right file fields to the new and edit forms, and image_tag or download link on the show view, we\u2019ve got working file uploads. Nice.<\/p>\n<p>Try to edit a record by attaching a new file, the new file is displayed and the record is preserved as an older version in the versioned table.  But if you try to view the old version&#8230;wait a minute? Where did my version 1 file go!<\/p>\n<p>That\u2019s right, attachment_fu deletes the old file when you add a new one (as it should if you aren\u2019t versioning your data). Attachment_fu\u2019s rename_file method is the one responsible for deleting (or renaming) the old file when a new one is added, so lets monkeypatch that in our model to not do anything.<\/p>\n<pre lang=\"rails\">def rename_file\nend<\/pre>\n<p>Now, it will only overwrite the file if the filename is the same. Lets store each version in its own folder to keep them from clobbering each other by monkey-patching the path files get written to in our model also:<\/p>\n<pre lang=\"rails\">def attachment_path_id\n  \"\/#{id}\/v#{version}\/\"\nend\ndef partitioned_path(*args)\n  attachment_path_id + args.to_s\nend<\/pre>\n<p>This changes the public path from \/0000\/0001\/rep08.jpg to \/1\/v1\/rep08.jpg<\/p>\n<p>Now, if we want to display the image, we cannot use the \u2018public_filename\u2019 method, because it is only given to the Document model, and not the Document_Version model.<\/p>\n<p>That\u2019s okay, because with our new path arrangement, we can reliably predict where the old versions of the files will be kept.  You can show them with some code similar to this in your views:<\/p>\n<pre lang=\"rails\"><% for version in @document.versions %>\n\n  Version <%= version.version %>\n  <%= image_tag(\"\/documents\/#{@document.id}\/v#{version.version\/\" + version.filename) %>\n  <hr \/>\n\n<% end %><\/pre>\n<p>Now, when we delete a record, attachment_fu only knows about the current document, and will leave behind orphaned files and folders from the old versions.  Lets fix that by having it get rid of the document id folder.<\/p>\n<p>Rails reserves some special methods (callbacks) for performing actions before or after other major actions, lets tap into that by defining a method that will magically get called every time we delete a record.<\/p>\n<pre lang=\"rails\">def after_destroy\n  FileUtils.rm_rf(RAILS_ROOT + \"\/public\/documents\/#{id}\/\")\nend<\/pre>\n<p>This translates into the shell command rm -rf and deletes our ID directory and everything inside it.<\/p>\n<p>Hooray!<\/p>\n<p>As a wrap up, lets look at our complete Document model:<\/p>\n<pre lang=\"rails\">class Document < ActiveRecord::Base\n  acts_as_versioned\n  has_attachment :storage => :file_system\n\n  def rename_file\n  end\n\n  def attachment_path_id\n    \"\/#{id}\/v#{version}\/\"\n  end\n\n  def partitioned_path(*args)\n    attachment_path_id + args.to_s\n  end\n\n  def after_destroy\n    FileUtils.rm_rf(RAILS_ROOT + \"\/public\/documents\/#{id}\/\") if id\n  end\n\nend<\/pre>\n<p>I&#8217;ve whipped up a sample Rails app demonstrating the points and code in this article. It uses Rails 2.0.2 with the sqlite3 database.<\/p>\n<p>Download it here: <a href=\"https:\/\/unixmonkey.net\/blog\/wp-content\/uploads\/2008\/02\/attachments_versioned.zip\" title=\"Attachments_versioned\">Attachments_versioned (240kb .zip)<\/a><\/p>\n<p>I hope this saves some work for someone who wants to leverage these two excellent plugins by Rick Olsen (<a href=\"http:\/\/weblog.techno-weenie.net\/\" title=\"weblog.techno-weenie.net\">technoweenie<\/a>) on the same model without having them fight too much.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you&#8217;ve ever wanted to keep track of revisions to document files or images in your Rails app, you are likely to want to use Acts_as_versioned, which is the authority on versioning database records, and Attachment_fu, which is the authority on uploading files with Rails. The problem is that they don&#8217;t know about each other [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13,14,15],"tags":[21,22,44,45,46],"_links":{"self":[{"href":"https:\/\/unixmonkey.net\/index.php?rest_route=\/wp\/v2\/posts\/18"}],"collection":[{"href":"https:\/\/unixmonkey.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/unixmonkey.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/unixmonkey.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/unixmonkey.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=18"}],"version-history":[{"count":0,"href":"https:\/\/unixmonkey.net\/index.php?rest_route=\/wp\/v2\/posts\/18\/revisions"}],"wp:attachment":[{"href":"https:\/\/unixmonkey.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=18"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/unixmonkey.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=18"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/unixmonkey.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=18"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}