Latest Ruby blog posts 

Filed under: Blogroll on Tuesday, June 8th, 2010 by jeremy | No Comments

Since I launched my new main blog, I haven’t hooked up the syndication to bring over posts yet. So if you’re looking for anything ruby-related, here’s a list of what I’ve written lately:

Enjoy!

The New House 

Filed under: Blogroll on Saturday, March 28th, 2009 by jeremy | Comments Off

Another view of Schloss McWeilandYes, the rumors you’ve been hearing are all true: we closed on our first home Thursday evening! It’s been a long road, and we almost didn’t get there (Bank of America, you were awful). But we got a great interest rate and a deal on a home that should have cost twice as much.

And you know whom we have to thank? My late Grandma Chappell, who left us just enough money for a modest down payment in her will. Thanks, Grandma, for your wonderful gift and being a great grandparent for so long!

Here’s a breakdown of the positive points about the house:

  • Built in 1910, but completely stripped down and replumbed, rewired, with all new appliances, room, and heat pump (need to replace the refrigerator, though). Original moldings and panel doors, though!
  • A block from James River Park, with lots of water and rocks for Tela and beautiful Belle Isle accessible by paw.
  • A five minute drive from my office. It’s actually quicker to walk than to take the bus!
  • Two bedrooms and an office
  • Huge living room and dining room, with a great kitchen.
  • Utility room with room for Tasha’s kiln.
  • A garage with a separate shed, all cleaned out and ready for Tasha to wire up and turn into a studio.

Our new house in the March SnowHere’s what we need to do:

  • Fence in the front yard first, then the backyard, for Tela.
  • Put a gutter on the roof.
  • Buy a new refrigerator.
  • Put a new roof on the shed and generally turn the garage into a studio with wiring.
  • Paint the downstairs rooms.
  • Move in!

So our lease at our current place ends April 30, giving us plenty of time to get the place ready and move in. We’ll let you know when the housewarming party is, but until then just wanted to share the next phase of our lives with ya!

Quote of the Day 

Filed under: Blogroll on Tuesday, February 24th, 2009 by jeremy | Comments Off

I know it’s been a while. Sorry. Anyway, this is great:

Jesse Helms and his ilk inject a bit of fresh blood into art which, for a moment, can imagine itself an insurrectionary force. The sad irony is that Helms really believes art can change the world. The NEA liberals think that all art should be permitted because, after all, it’s only art.

- from the TAZ show, Feb 6 1993

Watch the whole thing here, it’s a hoot.

Tela in heaven 

Filed under: Blogroll on Friday, February 6th, 2009 by jeremy | Comments Off

My first real YouTube upload with my new iFlip camcorder:

Reprocessing attachment_fu images with RMagick 

Filed under: Blogroll on Thursday, February 5th, 2009 by jeremy | Comments Off

Attachment_fu is the shit, no doubt. But sometimes you want to do more than upload, resize, and thumbnail. Designers often have a specific vision that dictates a more complex workflow for incoming images. For these tasks, it may be necessary to reprocess the saved images – something you don’t necessarily need to hack attachment_fu to accomplish.

For example, my latest Rails project included a gallery page with a pretty standard layout: a series of thumbnails and an area to display the full size version of the selected thumbnail. However, the static mockup delivered by the designer had thumbnails that were black and white with a blueish tint, only turning color when you moused over them. On top of that, the thumbnails were often made from a manually defined cropping of the image. This meant that in addition to an administrative backend to allow uploading and management, I needed to provide a tool for selecting an area within the image for a custom thumbnail, not to mention figuring out where and how to do the tinting.

So here’s how I approached it: I created two STI models deriving from a common GalleryImage model, all of which are related to the GalleryItem that encapsulates the item name, description, etc:

class GalleryItem < ActiveRecord::Base
  belongs_to :full_image, :class_name => 'GalleryMainImage', 
                          :foreign_key => 'full_image_id', 
                          :dependent => :destroy
 
  belongs_to :custom_thumbnail, 
                    :class_name => 'GalleryThumbnail',
                    :dependent => :destroy
end
 
class GalleryImage < ActiveRecord::Base
end
 
class GalleryMainImage < GalleryImage
  has_one :gallery_item, :dependent => :destroy
  has_attachment :content_type => :image, 
                 :storage => :file_system, 
                 :max_size => 50.megabytes,
                 :resize_to => '457>',
                 :thumbnails => { :default_thumbnail => '68x68!',
                                  :gray_thumbnail => '68x68!' },
                 :path_prefix => 'public/gallery',
                 :thumbnail_class => "GalleryThumbnail"
 
  validates_as_attachment
end
 
class GalleryThumbnail < GalleryImage
  has_one :gallery_item, :dependent => :destroy
  has_attachment :content_type => :image, 
                 :storage => :file_system, 
                 :max_size => 1.megabyte,
                 :resize_to => '68x68',
                 :thumbnails => { :gray_thumbnail => '68x68' },
                 :path_prefix => 'public/gallery'
 
  validates_as_attachment
end

So, the important takeaways here are the following:

  • All thumbnails would be of class “GalleryThumbnail”.
  • An item has one GalleryMainImage with it’s own attachment_fu-generated thumbnails. These thumbnails are the “uncropped” thumbnails.
  • An item can have two more custom cropped thumbnails associated with it – a color one and a gray one.

Is it clunky the way attachment_fu generates thumbnails given how we’re setting this up (why does GalleryThumbnail have thumbnails?)? Yes. But note especially that the specific settings in the “has_attachment” line (such as resizing and thumbnail generation) are only applied to a full attachment model. Therefore, only the objects associated with “item.full_image” and “item.custom_thumbnail” go through the full swath of attachment_fu processing. In other words, just because a full image generates thumbnails of class GalleryThumbnail does not mean each of those GalleryThumbnail objects gets their own thumbnails – obviously, that would be stupid. It’s better to not think about the generated images attachment_fu delivers as “thumbnails” so much as different versions of the attachment.

The next step is doing the grayscaling. I determined the path forward on this first by consulting the designer who delivered the mockup. He went back into Photoshop and gave me an idea of what filters and processes were applied to the thumbnails he had created. Basically, he created a grayscaled version of the thumbnail and then applied a gradient map that mapped black to a shade of blue. It gave it a very interesting effect:

141744150_e76e7e0aed_default_thumbnailbandw141744150_e76e7e0aed_gray_thumbnail

It took a lot of digging, but I was able to find two RMagick methods that could reproduce this effect: quantize and level_colors. Now it was just a matter of finding out how to integrate them into the thumbnailing process. I was surprised that simple callbacks basically did the trick. I put a method in the base class for the images:

class PortfolioImage < ActiveRecord::Base
protected
  def update_gray_thumbnail!  
    return unless thumbnail.blank? # only perform this on a custom thumbnail or an original image
 
    # the code below looks dumb, but I think the trick is that RMagick
    # can only perform one operation per file load.  I don't get it.
    thumb = thumbnails.find_by_thumbnail("gray_thumbnail")
    Magick::Image.read(thumb.full_filename).first.quantize(256,Magick::GRAYColorspace).write(thumb.full_filename)
    Magick::Image.read(thumb.full_filename).first.level_colors("#201000", "#f7f7f7", false).write(thumb.full_filename)
  end
end

Remember: in our modeling, everything is a GalleryImage. What makes a given GalleryImage a thumbnail according to attachment_fu is that it has a non-nil response to the “thumbnail” message (if this were our gray_thumbnail, it would return “gray_thumbnail” in response to the “thumbnail” message). So by proceeding only if thumbnail returns a blank response, we guarantee that we work with a main attachment like a MainImage or custom thumbnail, and that we don’t work on any of their associated images.

So look at how we generate thumbnails for a GalleryThumbnail and GalleryMainImage: there’s a default_thumbnail and a gray_thumbnail. If you’re using attachment_fu with “:storage => :file_system”, then you have physical files in the project that you can modify to your heart’s content. The above method changes the actual file associated with the “gray_thumbnail”, which is initially saved as a color thumbnail. So if you put a hook in your GalleryMainImage and GalleryThumbnail models to make this alteration on saving the model, you should be money:

after_save :update_gray_thumbnail!

Attachment_fu regenerates thumbnails on every model save, so it’s important we reapply the RMagick processing each time.

So what about the custom cropping? Well, you’ll need a controller that can generate a new GalleryThumbnail to be associated with the GalleryItem. All I’ll say on that count is that you should look at some javascript cropping utilities; I’m using jquery so I used imgAreaSelect. Following this example I was able to create a tool letting the user drag a box over with previewing of the resulting thumbnail, passing the coordinates for cropping to the controller via a form submission. Then it was just a matter of cropping the image, which once again is merely a matter of manipulating the actual full-size image file saved in the public directory after the fact:

 def create
    item = GalleryItem.find(params[:portfolio_item_id])
    crop = item.full_image.crop(params[:x1], params[:y1], params[:w], params[:h])
    thumb = GalleryThumbnail.new
    thumb.uploaded_data = crop
    thumb.save    
    item.portfolio_thumbnail = thumb
    if item.save
      flash[:notice] = "Cropped custom thumbnail saved."
      redirect_to admin_gallery_item_path(item)
    else
      flash[:error] = "Error resizing"
      render :action => 'new'
    end

I have a “crop” method on GalleryMainImage defined thusly:

def crop(x, y, width,height)
    blob = StringIO.new(Magick::Image.read(full_filename).first.crop(x.to_i, y.to_i, width.to_i, height.to_i).to_blob)
    {'tempfile' => blob,
     'content_type' => "image/#{filename.split('.').last}",
     'filename' => "custom_#{filename}"}
  end

The use of StringIO and returning a hash is just tricks to get attachment_fu to accept our cropped image as a parameter for “uploaded_data=”. And once the cropped image is passed into “uploaded_data=” and object is saved, the thumbnail will be generated using the cropped image – and grayscaled appropriately!

That’s pretty much it – I know this is really complicated, but I hope it helps somebody out there. Feel free to ask questions, and be advised that I may revisit this article to word things differently.

Duckpin bowling on Wednesday 

Filed under: Blogroll on Monday, February 2nd, 2009 by jeremy | Comments Off

In case you don’t get the Facebook invite, feel free to join Tasha and me for duckpin bowling at Plaza Bowl (523 E Southside Plz) on Wednesday at 6:30pm to celebrate my 30th birthday!

An RJS Redirect Matcher for rspec 

Filed under: Blogroll on Thursday, January 29th, 2009 by jeremy | Comments Off

You know what’s stupid? Clumsily checking for a javascript redirect in your RJS with this kind of shit:

it "should redirect to the collaborative quote screen" do
  xhr :post, 'attach', :attachment_id => '4023'
  response.body.should =~ /window\.location\.href = \"/collabquote\";"
end

Not only is this ugly, but it ties your test to a particular route, rather than allowing you to use your named route. So I whipped up a custom RJS redirect matcher in about 10 minutes following the guidelines in this post, and I was surprised how easy it was. It should be pretty self explanatory.

module RedirectViaRjsToMatcher  
  class RedirectViaRjsTo  
    def initialize(expected)  
      @expected = expected  
    end  
 
    def matches?(target)  
      @target = target
      @url = target.body.split('"')[1]
      @target.body == "window.location.href = \"#{@expected}\";"
    end  
 
    def failure_message  
      "expected redirect via rjs to #{@expected}, redirected instead to #{@url}"
    end  
 
    def negative_failure_message  
      "unexpected redirect via rjs to #{@expected}"  
    end
  end
 
  # Actual matcher that is exposed 
  def redirect_via_rjs_to(expected)  
    RedirectViaRjsTo.new(expected)  
  end
end

All you need to do is save this file in /spec/custom/redirect_via_rjs_to.rb and include it in /spec/spec_helper.rb like so:

require 'spec/be_the_same_as'
 
Spec::Runner.configure do |config|  
  config.include(RedirectViaRjsToMatcher)  
end

Voilla! It really is that easy, and turns that first spec into something a bit more readable and reusable:

it "should redirect to the collaborative quote screen" do
  xhr :post, 'attach', :attachment_id => '4023'
  response.should redirect_via_rjs_to quote_path(@quote)
end

UPDATE: Jim clued me into simple_matcher, which appears to be yet another very easy way to create custom matchers.

Behind the scenes at Richmond’s unbowling destination 

Filed under: Blogroll on Saturday, January 17th, 2009 by jeremy | Comments Off

plazabowl004So Matt O., I, and our wives rolled over to Plaza Bowl and Duckpins this past Thursday to duckpin bowl and drink PBR pitchers. We had a great time and ended up talking to the owner (it was a slow night). He asked us about any ideas we had for better marketing the place and we had a great conversation that really explored our love for the place.

Plaza Bowl is really just an incredibly fun destination – it’s laid back, and you don’t feel pressure to bowl 300. There’s now a stage where some of the lanes used to be, and bands play regularly. It’s got a great retro feel since none of the equipment has been manufactured for decades, and the owner maintains it himself.

In fact, we were thrilled that he invited us behind the scenes to observe the 1950s era machinery that runs the lanes, and I snapped some pictures on my iPhone. As programmers Matt and I were amazed at this complex mechanical state machine, and there’s an aesthetic to the vintage gears, chains, and conveyor belts that adds to the appeal. A steampunk’s dream!

I highly recommend that you check Plaza Bowl and Duckpins out. It’s difficult to communicate how awesome this place is, but I guarantee you’ll have a good time and be back. We want to keep this place in town, so show your love!

plazabowl002plazabowl005plazabowl001plazabowl003

Happy Holidays 

Filed under: Blogroll on Thursday, December 25th, 2008 by jeremy | Comments Off

Here’s a photo montage Tasha put together to express our wishes for the season. Enjoy your days of mirth!

Happy Holidays

All hail the Amen Break! 

Filed under: Blogroll on Wednesday, December 24th, 2008 by jeremy | Comments Off

Since I used to produce amateur electronic music, mostly drum and bass / jungle (you can sample some of my work here) I’m very familiar with the prevalence and importance of the Amen break. So it’s cool to see a short documentary that can chronicle its adventures and tie it into free culture and the tyranny of intellectual property law.