1.29.2008

Another take on a jQuery.equalizeCols plugin

Using CSS, it is very difficult to create multiple columns with the same height. One must use Javascript to modify <div> height so multiple div share the same height.

This blog post offer a jQuery plugin to do that. However, this particular script cannot be applied many time to the same elements. I had to adjust the height of multiple column that have their content updated with some Ajax requests. So I needed to be able to readjust the height of the columns after the content has been updated. Using the script from the post, my column would grow after each update.

I did like how it was adjusting the height of the column. It add a div with the missing height to all the columns that needs to be adjusted.

So I came up with the following script. This script can be applied many times to the same elements without any adverse effects.

   1: jQuery.fn.equalizeCols = function() {
   2:     jQuery(this).filter(function() {return jQuery(this).find(".auto_fill").length == 0;}).each(function() {
   3:         jQuery(this).append(jQuery("<div class='auto_fill'></div>").height(0));
   4:     }).css("height", "auto");
   5:     
   6:     var max_height= 0;
   7:     jQuery(this).each(function() {
   8:         var self= jQuery(this);
   9:         var height= self.height() - self.find(".auto_fill").height();
  10:         max_height=height > max_height ? height : max_height;
  11:     });
  12:     
  13:     jQuery(this).each(function() {
  14:         var self= jQuery(this);
  15:         var height= self.height() - self.find(".auto_fill").height();
  16:         self.find(".auto_fill").height(max_height - height);
  17:     });
  18: }
 

On line 2-3, an empty <div> is added to each element that may have their height adjusted. The <div> has an initial height of 0 pixels. Note that then <div> will be added only if the containing <div> does not already have this empty <div>.

On line 6-11, the maximum height is calculated.

On line 13-17, the empty div height is adjusted so the total height of the containing div is equal to the previously calculated maximum height.

So to adjust the height of some elements, you just have to do this :

   1: $(".columns").equalizeCols();

1.22.2008

1.18.2008

Fixing attachment_fu on Windows

Like many others, I've encountered issue when developing Rails applications using attachment_fu on Windows. After doing some research, I've come up with the following solution to the problem.

The problem has two parts :
  1. Size is not included in the list error message,
  2. Timeout error when uploading to S3.

Fixing "Size is not included in the list" error message

Some people have reported that there is a timing issue, when trying to get the file size, with Tempfile on Windows. It seems that the size of the file is not properly reported by Windows after writing data to it. Proposed solutions for this problem include :
  1. Sleeping in a loop as long as the file size is 0,
  2. Reading back the entire file in memory.

I think I found a better and less patchy solution for this issue: forcing the OS to flush the file to disk before reading it's size.
Here is the code to do it :
require 'tempfile'
class Tempfile
def size
if @tmpfile
@tmpfile.fsync # added this line
@tmpfile.flush
@tmpfile.stat.size
else
0
end
end
end

Doing a flush is not enough... flush will flush the Ruby buffer but the file may not be immediately written to the disk by the OS. Doing the fsync ensure that the file is written to disk by the OS before continuing. After that, Windows will properly report the actual file size.

Fixing the Timeout error when uploading to S3

This issue is related to opening files for reading on Windows. On Windows, you have to open the file in binary mode. So patching attachment_fu is simple :
require 'technoweenie/attachment_fu/backends/s3_backend'
module Technoweenie
module AttachmentFu
module Backends
module S3Backend
protected
def save_to_storage
if save_attachment?
S3Object.store(
full_filename,
(temp_path ? File.open(temp_path, "rb") : temp_data), # added , "rb"
bucket_name,
:content_type => content_type,
:access => attachment_options[:s3_access]
)
end

@old_filename = nil
true
end
end
end
end
end

I've also included a fix from someone else (which was not enough in itself to solve my S3 upload problem):
module Technoweenie
module AttachmentFu
# Gets the data from the latest temp file. This will read the file into memory.
def temp_data
if save_attachment?
f = File.new( temp_path )
f.binmode
return f.read
else
return nil
end
end
end
end

Wrapping it up

So I put all this code in lib/attachment_fu_patch.rb and required it in environment.rb.

Problem fixed!

Note, I did not test it on other OSes, but these fixes should not have any adverse effects.

11.15.2007

Loading ActionMailer SMTP settings from the database


Here is a simple way to load your SMTP settings from the database instead of having to call ActionMailer::Base.smtp_settings= in environment.rb, development.rb or production.rb. Using the database to store these setting, while a bit slower, allow one to easily allow an administration to change the configuration without having to restart the Rails application.

The first approach I used was to just override the self.smtp_settings method in my ActionMailer::Base subclass. But that didn't work due to cattr_accessor not working as expected. So I had to also add class_inheritable_accessor :smtp_settings.

So I did the following :
class StuffNotifier < ActionMailer::Base
class_inheritable_accessor :smtp_settings

def self.smtp_settings
Hash.new {|h, k|
Preference["smtp.#{k.to_s}"]
}
end
end
I use Hash.new with a Proc because the stuff the Preference[] method access the database, if I construct an Hash with all the value loaded from the database, then the Hash will be constructed many times as ActionMailer::Base access self.smtp_settings many times when sending an email. Using an empty hash with a default Proc solved my problem. I also prepend "smtp." so it retrieves the "smtp.host" value from my system preference table.

If somebody knows of a better way to do it, feel free to add a comment or a link.




Storing System Preferences in the Database

Here is a simple way to store system preferences in the database for a Ruby on Rails application. This simple solution offers no caching, so accessing the same preference many times will result in many database request. Suggestions for per request caching are welcomed.

Using the code bellow, one can access preferences like this :
admin_email= Preference["admin_email"]
First, let's start with a migration that will create a table in which we can store many types of preferences :
class CreatePreferences < ActiveRecord::Migration
def self.up
create_table :preferences do |t|
t.column :setting, :string
t.column :type, :string
t.column :value, :string
end
end

def self.down
drop_table :preferences
end
end
Now, we create the model. Because we want to be able to get many types of preferences (and eventually validate their value in different ways), we create a base Preference class and many subclasses for all the types of preferences we want to have. Some subclass will override the value and value= methods to convert the value to the proper type :
class Preference < ActiveRecord::Base
validates_presence_of :setting

def self.[](setting)
setting= self.find_by_setting(setting)
setting.nil? ? nil : setting.value
end
end

class UrlPreference < Preference
end

class EmailPreference < Preference
end

class StringPreference < Preference
end

class IntegerPreference < Preference
def value()
self[:value].nil? ? nil : self[:value].to_i
end

def value=(v)
self[:value]= v.nil? ? nil : v.to_s
end
end
Note that we could add validation for the value attribute in UrlPreference and EmailPreference to validate the format of the value.

AdSense Links