Saturday, November 15, 2008

Lockdown 0.6.0 released!

Here's the link to the news release on stonean.com.

btw, if you'd like to get this type of information from twitter, you can.

Thursday, November 13, 2008

Ruby: inject

Inject is a powerfully cool method provided by the Enumerable module. At first I was a little confused by the name. I didn't know what it meant. Here's a simple example:

an_array = [1, 2, 3, 4, 5, 6]

def x10(v)
v * 10
end

an_array.inject([]) do |sum, num|
sum << x10(num)
end

=> [10, 20, 30, 40, 50, 60]

So, by passing [] into the inject method, the variable sum is initialized as an empty array. Now the inject method will iterate over the array an add the value return by x10 to the new array (sum).

Calling inject on a hash:
  def convert_hash(hsh)
hsh.inject({}) do |sum, aray|
sum.merge( { aray[0] => some_conversion_method(aray[1]) } )
end
end

**Notice that inject when called on a hash will pass the key and value in one array: [key, value]. Hence the aray[0] and aray[1] calls.

Of course, since you are creating an entirely new hash, you could change the key if you wanted. This was just a simple example.

Anyway...just a short update. It's been a while since I shared. :)

Friday, October 3, 2008

Dynamic Content and Action Caching

I recently deployed a Rails application that, for various reasons, required caching dynamic content. All dynamic content is represented by a core "Content" model that contains a url attribute. In order for my app to show these pages I have the following as my last route:
  map.dynamic ':p1', 
:controller => "contents",
:action => "show",
:requirements => {:p1 => /[\w|-]*/}


As you can see, the contents/show is my catchall action. If I can find a content url matching :p1, I show the content.

Another feature of the application is that you can restrict access to this content to one or many user groups via my Lockdown gem. At a basic level, Lockdown works by testing for access in a before_filter it adds to all controllers. To ensure I don't expose protected content, I needed to use action caching to have my before filters execute before showing the page.

Action caching typically creates a cache file that corresponds to the action. However, in this case, the show action represents multiple urls. Here's how you get around that:
class ContentsController < ApplicationController
caches_action :show,
:cache_path => proc{|c| c.send(:cached_page, c.params[:p1], c.params[:page]) }

def show
#logic to render multiple content types
end

private

# This method will return my cache name
def cached_page(url, page)
page ||= "1"
"#{url}_#{page}"
end
end

If the url for the contents was "/about-us", the cache would be "about-us". If the content url was "/reunion-pictures?page=2", the cache would be "reunion-pictures_2". So forth and so on.

I was going setup a route that would turn "/reunion-pictures?page=2" to "/reunion-pictures/2", but for some reason, adding the route to do this broke the display of the page links in will_paginate. I'm not sure why and the url wasn't that important to me. The value provided by will_paginate was more important to me than pretty routes in this instance. I may revisit this issue later...but I need to get back on point.

So...I'm caching now, cool. Now I just have to account for changing content, enter the sweepers:
class ImageSweeper < ActionController::Caching::Sweeper
observe Image

def after_create(post)
expire_cache_for(post)
end

def after_update(post)
expire_cache_for(post)
end

def after_destroy(post)
expire_cache_for(post)
end

private

def expire_cache_for(record)
record.galleries.each do |gal|
for i in 1..((gal.artworks.length / 10) + 1)
expire_action("#{gal.url}_#{i}")
end
end
end
end

One type of dynamic content is a gallery that can contain multiple images. In order to make sure my galleries are kept up-to-date, I needed this sweeper to expire the caches of the galleries that contained the images I was creating/updating/deleting.

In order for this ImageSweeper to clean house, I needed to add the following to the ImagesController:
  cache_sweeper :image_sweeper, 
:only => [:create, :update, :destroy]


Now my galleries will stay current and I won't be locked into showing cached galleries that represent old/incorrect information.

The cached pages are ridiculously faster...and yes, that's a technical term similar to "ludicrous speed".

Saturday, September 13, 2008

Adobe Air + Dojo

I've been spending entirely too much time (days and days) reviewing javascript frameworks. What I'm looking for is something I can use within both Adobe Air and a standard web site.

If you search for Adobe Air + javascript frameworks the obvious winner would be Ext JS. I had already heard of this framework and have some friends using it with (apparently) no major gripes. I took this as a good sign.

So, from the discussions of using Air + Ext, I downloaded Aptana Studio. Long story short, I created a new Air project and noticed something not found in the Ext JS 2.2 download, the Air support for Ext. Me being me, I had to find out where these javascript libraries originated because I wanted to be certain the libraries were 'officially' maintained by either Ext or Adobe.

I do not know why, but Air libraries that were included in Ext 2.1 were not included in Ext 2.2. They have Ext copyright notices so I'm assuming Ext would maintain them, but I could not find a clear answer as to why they weren't included in 2.2. As good as Ext may be, this uncertainty over the Air support forced me to look elsewhere.

After many nights of looking I've settled on Dojo. It appears to be the most polished framework offering the most high-level components and Air support.

I'm just starting to dig into this, but it should be fun. Who knows, I may even change from Dojo to something else after digging deeper. I hope that will not be the case.

If you know the reasons behind the missing Air libraries in Ext 2.2 or feel strong about another framework, I'd love to hear why.

Update: Well, the Air libraries missing from Ext 2.2 are now back. Now I'll have to decide between Dojo and Ext

Thursday, August 14, 2008

Rails Routes

I don't frequently have the need to create anything more than the basic "map.resources :model" route. As things go, this means I don't remember how to anything other than the basic routes and forget the difference between :collection and :member and how that translates to urls and helper methods. So, I'm creating this page as a reference for myself so I don't have to google and piece together bits of information.

Basic:
map.resources :users

#Which gives you the following helpers:

users_path
users_url
new_user_path
new_user_url
user_path(@user)
user_url(@user)
edit_user_path(@user)
edit_user_url(@user)

#Accessed via:

/users
/users/new
/users/1
/users/1/edit


Nested:
map.resources :posts do |posts|
posts.resources :comments
end

#Which gives you the following helpers:

post_comments_path
post_comments_url
new_post_comment_path
new_post_comment_url
post_comment_path(@post,@comment)
post_comment_url(@post,@comment)
edit_post_comment_path(@post,@comment)
edit_post_comment_url(@post,@comment)

#Accessed via:

post/1/comments
post/1/comments/new
post/1/comments/1
post/1/comments/1/edit


Adding methods outside the standard REST methods.
map.resources :users, :collection => {:active => :get}

#Which gives you the following helpers:

active_users_path
active_users_url

#Accessed via:

/users/active


The member option means you're going to access a 'member' of the collection. Why this didn't stick the first time, I have no idea. I'm just slow I guess.
map.resources :users, :member => {:activate => :post}

#Which gives you the following helpers:
activate_user_path(@user)
activate_user_url(@user)

#Accessed via:

/users/1/activate


There will be more added as I run across more usage needs. Let me know if I messed anything up here.