• May
  • 19th
  • 2008

Getting attachment_fu to play nice with acts_as_versioned 1

original info from: http://unixmonkey.net/?p=18#comment-3461

Thanks to: David Jones 

If you’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’t know about each other and will step on each other’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.

Acts_as_versioned was written by Rails Core Team member Rick Olsen (who also wrote attachment_fu and Restful_authentication 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.

Say I have a document table with fields like this:

id title description
1 rep08 2008 report

Acts_as_versioned will add a column “version”, and a separate table “document_versions”.

id title description version
1 rep08 2008 report 1

The document_versions table will look a bit like this

id document_id title description version
1 1 rep08 2008 report 1

Setting up acts_as_versioned is pretty simple, I got most of my introduction to it from urbanhonking.com

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.

After a few edits of the document, you’ll see the versioning information in the Document_versions table add up.

id document_id title description version
1 1 rep08 2008 report 1
2 1 rep08 2008 report changed 2
3 1 rep08 chgd 2008 report changed 3

Great! We can now use some of acts_as_versioned’s built-in methods for determining if there are older versions, and be able to view or even revert to them.

Now lets add the ability to upload a file to attach to a document record with attachment_fu.

Attachment_fu is another plugin that makes uploading files and keeping track of them in the database relatively simple.

A good intro to attachment_fu can be found on Mike Clark’s blog

Attachment_fu would require a few changes to our documents table:

id title description version filename content_type size
1 rep08 2008 report 1 rep08.jpg image/jpeg 2854

Don’t forget to add the same fields to your documents_versions table, too.

Once we’ve added the right file fields to the new and edit forms, and image_tag or download link on the show view, we’ve got working file uploads. Nice.

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…wait a minute? Where did my version 1 file go!

That’s right, attachment_fu deletes the old file when you add a new one (as it should if you aren’t versioning your data). Attachment_fu’s 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.

def rename_file end

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:

def attachment_path_id   “/#{id}/v#{version}/” end def partitioned_path(*args)   attachment_path_id + args.to_s end

This changes the public path from /0000/0001/rep08.jpg to /1/v1/rep08.jpg

Now, if we want to display the image, we cannot use the ‘public_filename’ method, because it is only given to the Document model, and not the Document_Version model.

That’s 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:

<% for version in @document.versions %>     Version <%= version.version %>   <%= image_tag(“/documents/#{@document.id}/v#{version.version/” + version.filename) %>   <hr />   <% end %>

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.

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.

def after_destroy   FileUtils.rm_rf(RAILS_ROOT + “/public/documents/#{id}/”) end

This translates into the shell command rm -rf and deletes our ID directory and everything inside it.

Hooray!

As a wrap up, lets look at our complete Document model:

class Document < ActiveRecord::Base   acts_as_versioned   has_attachment :storage => :file_system     def rename_file   end     def attachment_path_id     “/#{id}/v#{version}/”   end     def partitioned_path(*args)     attachment_path_id + args.to_s   end     def after_destroy     FileUtils.rm_rf(RAILS_ROOT + “/public/documents/#{id}/”) if id   end   end

I’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.

Download it here: Attachments_versioned (240kb .zip)

I hope this saves some work for someone who wants to leverage these two excellent plugins by Rick Olsen (technoweenie) on the same model without having them fight too much.

  • May
  • 19th
  • 2008

File Upload Fu

original info from: http://clarkware.com/cgi/blosxom/2007/02/24

Thanks to: Mike Clark 

Update: The Advanced Rails Recipes book includes an updated and extended version of this recipe. Thanks for your support!

A picture might be worth a thousand words, but how many lines of code does it take to upload one to your Rails application? Sounds like a fun feature to tackle on a Friday. Let’s upload some mug shots. You know, to identify the goofballs around the office.

1. Install an Image Processor

We’ll need to resize the mugshots during the upload process, and we’ll also want to generate thumbnails of the mugshots to use around the site.

Image processing of this kind is best handled by native code. This means you end up either building a library for your operating system or downloading a pre-built library specific to your operating system. Then you install a Ruby library (gem) that wraps the image processing library with a Ruby API. Either way, it’s the least fun step of the entire process, so let’s get it out of the way early.

Choose from one of the following libraries:

  • ImageScience: A light inline-Ruby library that only resizes images. (Wraps the FreeImage library.)
  • RMagick: The grand-daddy, both in terms of advanced image processing features and memory usage. (Wraps the ImageMagick library.)
  • minimagick: It’s much easier on memory than RMagick because it runs the ImageMagick command in a shell.

OK, so which library should you use? Well, given that we just need to resize images (and thumbnailing is just resizing), ImageScience is a perfect fit. If you have one of the others installed, go with it. Otherwise, spend a few minutes with the short and sweet instructions for installing ImageScience and FreeImage.

2. Download the attachment_fu Plugin

To make light work of the rest of this task, we’re going to use Rick Olson’s attachment_fu plugin. It’s a significant rewrite of his original acts_as_attachment plugin. That’s right, this isn’t Rick’s first rodeo, and it’s probably not his second. Seriously, I don’t know of a more experienced person then Rick when it comes to uploading files into Rails applications. (And he’s the king of good plugins!)

If you’re using acts_as_attachment, you might be wondering if it’s time to upgrade. It’s probably not one of those high-priority chores, but it’s definitely something you want to consider doing soon. Rick’s been working on attachment_fu for almost a year now, and polishing it smooth in production settings. It comes with a comprehensive set of tests, as well. And as you’ll see, it’s more flexible than its worthy predecessor. To top it off, the public API hasn’t changed. I converted an app this morning simply by renaming one declaration in a model from acts_as_attachment to has_attachment. Well worth the price of admission. One caveat: attachment_fu requires Rails 1.2+.

Here’s how to get it:

script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/
3. Write an Upload Form

Having installed all the supporting software, it’s time to write our app. Let’s start with the upload form in the new.rhtml file:

&lt;%= error_messages_for :mugshot %&gt;  &lt;% form_for(:mugshot, :url =&gt; mugshots_path,                        :html =&gt; { :multipart =&gt; true }) do |f| -%&gt;   &lt;p&gt;     &lt;label for="mugshot"&gt;Upload A Mugshot:&lt;/label&gt;     &lt;%= f.file_field :uploaded_data %&gt;   &lt;/p&gt;   &lt;p&gt;     &lt;%= submit_tag 'Create' %&gt;   &lt;/p&gt; &lt;% end -%&gt;

Pretty standard form stuff, with a couple important bits. First, I’m using RESTful named routes (mugshots_paths), but it works just as well with traditional routes. Second, the form uses the file_field helper. That helper generates a Choose File button on the form. It’s important that we call the attribute :uploaded_data, as its the attribute name that attachment_fu looks for when storing the image. Third, to allow the form to accept files as POST data, the form is generated with :multipart =&gt; true. Forget either of those finer points and you’re in for a long afternoon.

4. Write a Controller

The controller is oblivious to the fact that we’re uploading images, so it’s your typical overpaid middleman. The new action displays the upload form and the create action accepts the POST data.

def new   @mugshot = Mugshot.new end  def create   @mugshot = Mugshot.new(params[:mugshot])   if @mugshot.save     flash[:notice] = 'Mugshot was successfully created.'     redirect_to mugshot_url(@mugshot)        else     render :action =&gt; :new   end end
5. Write a Migration and Model

Next we need a MugShot model (and a corresponding database table) to store the uploaded file information. Let’s start with the migration file for the mugshots database table.

class CreateMugshots &lt; ActiveRecord::Migration      def self.up     create_table :mugshots do |t|       t.column :parent_id,  :integer       t.column :content_type, :string       t.column :filename, :string           t.column :thumbnail, :string        t.column :size, :integer       t.column :width, :integer       t.column :height, :integer     end   end    def self.down     drop_table :mugshots   end end

How did we come up with those column names? Well, we didn’t. By convention, attachment_fu will automatically store the uploaded file information (the meta-data, if you will) in these columns. That begs the question: Where does the actual file data get stored? To answer that we need to write the MugShot model.

class Mugshot &lt; ActiveRecord::Base      has_attachment :content_type =&gt; :image,                   :storage =&gt; :file_system,                   :max_size =&gt; 500.kilobytes,                  :resize_to =&gt; '320x200&gt;',                  :thumbnails =&gt; { :thumb =&gt; '100x100&gt;' }    validates_as_attachment  end

Here’s where the attachment_fu plugin makes you pump your fists in victory. Basically, at this point we’d rather not grovel around at the API level of whatever Ruby image library you have installed. We’d also like to program at a fairly high level and not worry about how the file information and data is stored. (We call those things implementation details when our boss is listening.)

In the has_attachment method we tell attachment_fu what to do with the uploaded image. The options are as follows:

  • :content_type - The content types that are allowed, which defaults to all content types. Using :image allows all standard image types.
  • :min_size - The minimum size allowed, which defaults to 1 byte
  • :max_size - The maximum size allowed, which defaults to 1 megabyte
  • :size - A range of allowed sizes, which overrides the :min_size and :max_size options
  • :resize_to - An array of width/height values, or a geometry string for resizing the image
  • :thumbnails - A set of thumbnails to generate, specified by a hash of filename suffixes and resizing options. This option can be omitted if you don’t need thumbnails, and you can generate more than one thumbnail simply by adding names and sizes to the hash.
  • :thumbnail_class - Sets what class (model) to use for thumbnails, which defaults to the current class (MugShot, in this example). You could, for example, use a different model class with a different set of validations.
  • :storage - Sets where the actual image data is stored. Options include :file_system, :db_file, and :s3.
  • :processor - Sets what image processor to use. Options include ImageScience, Rmagick, and MiniMagick. By default, it will use whatever you have installed.
  • :path_prefix - Path to store the uploaded files, which defaults to public/#{table_name} by default for the filesystem. If you’re using the S3 backend, it defaults to just #{table_name}.

We don’t really want folks uploading life-sized mugshots, so calling validates_as_attachment prevents image sizes out of range from being saved. (They’re still uploaded in memory, mind you.) As well, because we set an image content type, WinZip files won’t be welcome, for example.

6. The Most Wanted List

OK, so now we’re off to the races: select a mugshot file using the Choose File button on the form, the mugshot image is uploaded to the server, the file metadata is stored in the mugshots database table, and the actual file data is stored in the public/mugshots directory on the server.

Now we can show a line-up of thumbnails, with each thumbnail linked to the full-size image.

&lt;h1&gt;Most Wanted&lt;/h1&gt; &lt;% for mugshot in @mugshots -%&gt;   &lt;%= link_to image_tag(mugshot.public_filename(:thumb)), mugshot.public_filename %&gt; &lt;% end -%&gt;

The public_filename method gives us the public path to the full-size file or the thumbnail if passed the name of the thumbnail suffix (:thumb, in our case). Given that we’re using the filesystem as storage, this code ends up generating paths such as /mugshots/34/bad_man.png and /mugshots/34/bad_man_thumb.png. Those paths are relative to the $RAILS_ROOT/public directory on our server, by default.

But Wait, There’s More!

One of the big benefits of using attachment_fu is the choice of backend storage systems. Let’s say, for example, we want to store all of our mugshots on Amazon’s S3 Web Service.

1. Download the AWS::S3 Library
gem install aws-s3

I wrote up a how-to on the AWS::S3 library a few weeks back, if you need a refresher. (Oh, and you thought this was a coincidence?)

2. Configure attachment_fu

First, just change the :storage option on the MugShot model to use :s3. (You saw that coming.)

Then create the $RAILS_ROOT/config/amazon_s3.yml configuration file as follows:

development:   bucket_name: your_bucket_name   access_key_id: your_access_key_id   secret_access_key: your_secret_access_key   test:   bucket_name:    access_key_id:    secret_access_key:  production:   bucket_name:    access_key_id:    secret_access_key:
3. There Is No Step 3

Upload a new mugshot, and you’ll see that the image link on the most wanted list points to your image up on the S3 server.

Enjoy!

  • April
  • 30th
  • 2008

sudo: port: command not found

 

Published on July 31, 2007

Include the following lines in .bash_login (if you are using the bash shell)

export PATH=$PATH:/opt/local/bin
export MANPATH=$MANPATH:/opt/local/share/man
export INFOPATH=$INFOPATH:/opt/local/share/info

Then try:
sudo port -d selfupdate

If that works, you can install other packages by running:
sudo port install &lt;package_name&gt;

  • April
  • 30th
  • 2008

EZ deployments?

Situation:

I have a web application developed in Rails 1.2.6 (we’ll call for the document webapp), the development has reached to the point of deployment, I need to have this application on the Internet as fast as possible, I just buy a virtual dedicated hosting plan(for this document we’ll call it deploymentserver.com), so I have root access and a lot of installing and configuring work to be done.

Take note of the plugins you use in your app this is mine:

  1. Ruport
  2. Ruport-util
  3. documatic.

Eg:

Remote
$ sudo gem install documatic

Note: the work is done in 2 separate environments, one is my laptop (local development) and the other is my virtual dedicated hosting somewhere on the Internet (remote deployment).

In all this adventure we will refer to those simply as “Local” and “Remote”.

Read the rest of this entry »

  • December
  • 31st
  • 2007

Checkout an older Revision

>svn checkout -r 10 svn://projectx/trunk

For checking out a copy of the Project projectx  at revision 10.

  • December
  • 27th
  • 2007

Confirm the update to the repository

Following the commit we can use the log function to confirm that the repo has indeed been updated:

projectx> svn log README.txt

projectx> svn log –verbose README.txt

continue page 54

  • December
  • 27th
  • 2007

Updating the repository

having made our changes (and having run the unit tests) we’re ready to save our latest version to the repo.

projectx> svn commit -m “Se modifico el README se agrego un yeah”

The commit function is used to save any changes we’ve made back to the repository. Again the -m option is used to attach a message to the changes.

  • December
  • 27th
  • 2007

Making Changes

Check the status of one or more files:

projectx> svn status README.txt

M README.txt

This tell us that the file has been Modified Locally.

projectx> svn diff README.txt

Tell us which are the changes applied to that file.

  • December
  • 27th
  • 2007

Starting to work with our project

This apply to a new project or if you are joining to a team that has been working in a project for months and containing thousands of files:

  1. Decide where to put the working copies of the files in your local machine.
  2. Check the Project out of the repository into the location that you choose in the step no. 1.

I recommend to create a folder called work then tend to check all your projects somewhere under this directory.

So let’s start off creating a directory called work:

>mkdir /Users/alan/work

Now we’ll check out the source into our working directory.

> cd /Users/alan/work

work> svn co file:///Users/alan/Source/svn-repos/projectX/trunk projectX

  • December
  • 27th
  • 2007

Creating a Project

Let’s fill our newly created repository with a new project:

svn import -m “importing project X” . file:///Users/alan/Source/svn-repos/X/trunk

the -m option allows associate a message with this import.