Posts Tagged ‘ruby’

Tip: Using Procs with Thoughtbot’s Paperclip

Thoughtbot’s Paperclip allows easy and reliable management of attachments. Using it is as simple as adding a few columns to your model and invoking the has_attached_file class method with the desired options.
One of Paperclip’s as-yet-undocumented features is the option to pass an anonymous function to the :url, :path, and :styles parameters of the has_attached_file class method.
In the example below, a Product has many ProductImages. No problem, except that I might not want every product image to have the same style attributes (e.g., a product could have a header image of ’200×400′ and a main image of ’300×300′). Certainly one way to handle this would be to create a distinct class for each type of product image (HeaderProductImage, MainProductImage, etc.).
But another, and perhaps more interesting solution, is to pass an anonymous function to the :styles parameter. That function can then return a customized style attribute for any kind of image:


class Product < ActiveRecord::Base
has_many :images, :class_name => "ProductImage"
end
class ProductImage < ActiveRecord::Base
belongs_to :product
has_attached_file :image,
:storage => :filesystem,
:path => ":rails_root/public/images/products/:attachment/:id/:style/:basename.:extension",
:url => "/images/products/:attachment/:id/:style/:basename.:extension",
# Because the :styles key can take a Proc object as an argument, we can specify the image
# styles dynamically based on the image's label.
:styles => Proc.new {|this|
defionition = ProductImageDefinitions.detect {|spec| spec[:label] == this.instance.label}
definition[:styles].merge(:thumb => '200x200>')
}
end


When Paperclip processes the attachment, it passes itself to the anonymous function. The model can then be accessed by calling the #instance attribute on the passed-in reference (see above).
The ProductImage class defines a #label attribute. The label acts as a key to a hash, ProductImageDefinitions, which specifies styles for various kinds of product images (you might define this somewhere in config/initializers).


ProductImagesDefinitions = {
'main' => {:styles => {:large => "1200x600!", :medium => "600x300!"},
'header' => {:styles => {:large => "400x600!", :medium => "200x300!"}
}


This allows for a high level of flexibility since adding a new kind of product image is simply a matter of modifying the ProductImageDefinitions hash. For a nice interface, you might write a module like this one to allow for the following manipulation of product images:


# Create product images.
@product.product_images.create(:label => 'main', :image => # Attachment object)
@product.product_images.create(:label => 'header', :image => # Attachment object)
# Access product images.
@product.available_images('main')
@product.available_images('header')


Not bad, right? The same kind of on-the-fly configuration can be applied to the :url and :path parameters. Thus does Paperclip’s support for anonymous functions enhance an already superb library.
For the record, I myself am still on the fence about whether this application of anonymous functions can be considered a good practice in terms of readability and maintainability.
But from the perspective of the sheer fun and practicality of the functional paradigm in Ruby, I’m going to consider it a worthwhile excursion.

Technology

Tip: Conditional Validations for ActiveRecord

Is isn’t always desirable, or even necessary, to validate every attribute in a given model with every save. For example, I recently had to build out a tabbed interface for editing products. Normally, when a user would press save, with the controller code calling update_attributes, errors would appear for attributes outside the current tab.
The need for intelligent, conditional validation begat the following module, which can be included in any ActiveRecord class:

module SelectiveAttributeValidatable
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :update_attributes, :check
end
end
module InstanceMethods
# Store the set of attributes passed in to #update_attributes.
def update_attributes_with_check(attrs)
@attrs_to_validate = attrs.keys.map(&:to_s)
update_attributes_without_check(attrs)
end
# Returns true if a given attribute was passed in to #update_attributes.
def should_validate?(attr)
if self.new_record?
attrs_to_validate_on_create.include?(attr)
else
@attrs_to_validate.blank? ||
@attrs_to_validate.include?(attr) ||
self.status == 'ENABLED'
end
end
# This method must be overwritten in the class
# where this module is included. Specify whichever
# attributes you want validated on create:
#   ['name', 'sku']
def attrs_to_validate_on_create
[]
end
# Allows the should_validate callback to be thrown.
def method_missing(method, *args)
if method.to_s =~ /^should_validate_([_\w]+)[?]/
return should_validate?($1)
else
super
end
end
end
end



After including this module, you’ll need to add conditions to your validations, as follows:

validates_presence_if       :description,  :if => :should_validate_description?
validates_numericality_of  :age,            :if => :should_validate_age?



Once you’ve implemented the conditions on your validation, any call to update_attributes will validate only the attributes included in that call.
Caveat
You probably want to make sure that your objects are eventually valid. In my case, this entails storing an enabled/disabled state on the object. This way, I can specify that when the object is enabled, all of the validations should be in effect.

Technology

Ruby Did My Taxes

Over the weekend, like many Americans, I did my taxes. I’m required to file a couple of different Schedule C’s for various side-businesses I have, and when it comes time to hunt for deductions, it sure is nice to be a programmer.

I had initially thought that I would be able to download the entire year’s bank transactions into a CSV file, but alas, my bank (and many others, I’m finding) only provide 45 days of history. They do, however, allow you to download PDF files of all of your statements, and so there I was staring at thousands of transactions in PDFs, dreading retyping them into Excel.

Enter my skills as a Rubyist. I sought out the PDF::Reader library, which allows you to hook its parsing engine to a custom callback and do what you want with it. This definitely parsed the PDF fine, but I had no context; no idea where I was in my statement, because there’s no callback for a “new line” character – it’s just a stream of words. I found that if I used Adobe Acrobat to save the files as text-accessible, then I started to have statements I was able to work with.

Now that I had ‘lines’, I was able to use the power of regular expressions to get the data I needed. The lines I was interested in started with a date and an amount, and the rest was just description for my transaction.

Here’s the warts-and-all code I used to compile the year’s worth of spreadsheet data. It’s truly “quick-and-dirty”, but it saved me tons of time. The next step, of course, would be to implement regular expressions against the ‘memo’ field of these transactions, and pre-suppose categories and deductions based on these patterns. But then again, maybe it’s time to just use Quicken and stop waiting until the last minute.

Technology