August 15th, 2009 by Andrea Franz
If you run a rails application on multiple servers behind a load balancer, almost every time you make a request you have a response from a different host. If you want to know which host has sent back the response, you can use a middleware that adds the hostname in the page title of each request.
lib/hostname.rb:
class Hostname
TITLE_REGEXP = /(<title>)([^<]*)(<\/title>)/i
def initialize(app, hostname="")
@app = app
@title_suffix = " - on #{hostname}"
end
def call(env)
status, headers, response = @app.call(env)
add_hostname(response, headers) if headers["Content-Type"] =~ %r{text/html}
[status, headers, response]
end
def add_hostname(response, headers)
response.each{|s| s.sub!(TITLE_REGEXP, "\\1\\2#{@title_suffix}\\3") if s =~ TITLE_REGEXP}
headers["Content-Length"] = (headers["Content-Length"].to_i + @title_suffix.length).to_s
nil
end
end
To use it, add this line in your environment.rb:
config.middleware.use "Hostname", %x"hostname".chomp
When you don’t need it anymore you can simply remove the line above.
Filed under: Development,
Rails,
Ruby |
Comments Off
August 11th, 2009 by Andrea Franz
In the last days I switched a lot of old projects to use Cucumber, then I started writing test for Web App Theme. Here I haven’t models or controllers to test, because it’s just a generator, but I found enjoyable the use of plain text features to describe the ThemeGenerator behavior:
Feature: Layout generation
In order to create a great application
I should be able to generate a layout with Web App Theme
# script/generate theme
Scenario: Generate a layout
Given I have a new rails app
And I have no layouts
And I have no stylesheets
When I generate a theme
Then I should have a layout named "application.html.erb"
And I should have a stylesheet named "web_app_theme.css"
And I should have a stylesheet named "web_app_theme_override.css"
And I should have a stylesheet named "themes/default/style.css"
# script/generate theme admin
Scenario: Generate a layout with a name
Given I have a new rails app
And I have no layouts
And I generate a theme with name "admin"
Then I should have a layout named "admin.html.erb"
# script/generate theme --theme="drastic-dark"
Scenario: Generate a layout choosing a theme
Given I have a new rails app
And I have no stylesheets
And I generate a theme choosing the "drastic-dark" theme
Then I should have a stylesheet named "themes/drastic-dark/style.css"
# script/generate theme --theme=bec --no_layout
Scenario: Generate only stylesheets without layout
Given I have a new rails app
And I have no layouts
And I generate a theme without layout choosing the "bec" theme
Then I should have a stylesheet named "themes/bec/style.css"
But I should not have any layouts
# script/generate theme --app_name="My New Application"
Scenario: Generate layout with application name
Given I have a new rails app
And I have no layouts
And I generate a theme with application name "My New Application"
Then the layout "application.html.erb" should have "My New Application" as page title
# script/generate theme --type=sign
Scenario: Generate layout for signin and signup
Given I have a new rails app
And I have no layouts
And I generate a theme for signin and signup
Then I should have a layout named "sign.html.erb"
And I should have a layout named "sign.html.erb" with just a box
Here my steps:
Given /^I have a new rails app$/ do
generate_rails_app
end
Given /^I have no layouts$/ do
remove_layouts
end
Given /^I have no stylesheets$/ do
remove_stylesheets
end
Given /^I generate a theme$/ do
generate_layout(:theme)
end
Given /^I generate a theme with name "([^\"]*)"$/ do |name|
generate_layout(:theme, name)
end
Given /^I generate a theme choosing the "([^\"]*)" theme$/ do |theme_name|
generate_layout(:theme, :theme => theme_name)
end
Then /^I should have a layout named "([^\"]*)"$/ do |filename|
layout_exists?(filename).should be_true
end
Then /^I should have a stylesheet named "([^\"]*)"$/ do |filename|
stylesheet_exists?(filename).should be_true
end
Given /^I generate a theme without layout choosing the "([^\"]*)" theme$/ do |theme_name|
generate_layout(:theme, :theme => theme_name, :no_layout => true )
end
Then /^I should not have any layouts$/ do
layouts_count.should == 0
end
Given /^I generate a theme with application name "([^\"]*)"$/ do |name|
generate_layout(:theme, :app_name => name )
end
Then /^the layout "([^\"]*)" should have "([^\"]*)" as page title$/ do |layout, title|
layout_title(layout).should == title
end
Given /^I generate a theme for signin and signup$/ do
generate_layout(:theme, :layout_type => :sign)
end
Then /^I should have a layout named "([^\"]*)" with just a box$/ do |layout|
layout_with_box?(layout).should be_true
end
Basically I create a temp folder that I use as fake rails root, and there I launch the generator. After each feature I remove that folder.
And here my env.rb
$:.unshift(File.dirname(__FILE__) + "/../../rails_generators")
require "rubygems"
require "rails_generator"
require 'rails_generator/scripts/generate'
require "fileutils"
require "theme/theme_generator"
web_app_theme_root = File.join(File.dirname(__FILE__), "/../../")
tmp_rails_app_name = "tmp_rails_app"
tmp_rails_app_root = File.join(web_app_theme_root, tmp_rails_app_name)
Rails::Generator::Base.append_sources(Rails::Generator::PathSource.new(:plugin, "#{web_app_theme_root}/rails_generators/"))
module GeneratorHelpers
def generate_rails_app
FileUtils.mkdir(File.join(@app_root))
end
def remove_layouts
FileUtils.rm_rf(File.join(@app_root, "app", "views", "layouts"))
end
def remove_stylesheets
FileUtils.rm_rf(File.join(@app_root, "public", "stylesheets"))
end
def generate_layout(*args)
options = !args.empty? && args.last.is_a?(Hash) ? args.pop : {}
options.merge!({:destination => @app_root, :quiet => true})
Rails::Generator::Scripts::Generate.new.run(args, options)
end
def layouts_count
Dir[File.join(@app_root, "app", "views", "layouts", "**", "*.erb")].size
end
def layout_exists?(filename)
File.exists?(File.join(@app_root, "app", "views", "layouts", filename))
end
def stylesheet_exists?(relative_path)
File.exists?(File.join(@app_root, "public", "stylesheets", relative_path)).should be_true
end
def layout_title(layout)
File.open(File.join(@app_root, "app", "views", "layouts", layout), "r").read.match(/<title>([^<]*)<\/title>/)[1]
end
def layout_with_box?(layout)
File.open(File.join(@app_root, "app", "views", "layouts", layout), "r").read =~ %r|<div id="box">|
end
end
Before do
@app_root = tmp_rails_app_root
end
After do
FileUtils.rm_rf(tmp_rails_app_root)
end
World(GeneratorHelpers)
July 30th, 2009 by Andrea Franz
Many people found out a rails generator inside my web-app-theme project and asked me how to use it.
Here an example, starting from scratch with a new rails app that manages music Albums.
rails cool_albums
cd cool_albums
script/generate scaffold Album name:string artist:string date:date
rake db:migrate
After creating the first controller with a scaffold or with your hands, start creating a theme:
script/plugin install git://github.com/pilu/web-app-theme.git
script/generate theme application --app_name="My Cool Albums" --theme="drastic-dark"
The first argument (“application”) is the name of the layout that the generator will create (application.html.erb).
The –app_name option specifies the name used as page title, and with the –theme specifies which theme to use among all the available themes inside the plugin.
Now remove the default index.html created by rails and the layout created by the scaffold:
rm app/views/layouts/albums.html.erb
rm public/index.html
Add the following line in your routes.rb to set the default page of the application:
map.root :controller => :albums
Start the server

Ok, the layout has been successfully created, but we need to apply a theme for each one of the views generated by the scaffold.
script/generate themed albums album --layout=application --with_will_paginate
With the first 2 arguments I specified the controller path (albums) and the model used (album).
The –layout options is used by the themed generator to know where to add the Albums menu link.
Since we want to use will paginate (we set the –with_will_paginate option), we need to change one line in our albums controller from:
to:
@albums = Album.paginate(:per_page => 10, :page => params[:page])
Here a trick to show form error messages inside the auto generated forms, you can add the following lines in your environment.rb:
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
if html_tag =~ /<label/
%|<div class="fieldWithErrors">#{html_tag} <span class="error">#{[instance.error_message].join(', ')}</span></div>|
else
html_tag
end
end
Ok, restart your server and you are done.

Feel free to fork the project from github to improve the generator or to add a new theme.
July 19th, 2009 by Andrea Franz
Today I’m working on an AIR application, and I need to create a PDF starting from a list of images. This PDF needs to have one image for each page. I used AlivePDF, a very useful ActionScript3 library for PDF generation, and I created a simple class that takes an array of image paths and creates the PDF I need.
Here the class I use:
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import org.alivepdf.pdf.PDF;
import org.alivepdf.display.Display;
import org.alivepdf.images.ResizeMode;
import org.alivepdf.layout.Layout;
import org.alivepdf.layout.Orientation;
import org.alivepdf.layout.Size;
import org.alivepdf.layout.Unit;
import org.alivepdf.saving.Method;
public class PdfGenerator extends EventDispatcher
{
private var pdf:PDF;
private var images:Array;
private var outFilePath:String;
private var completedPages:int;
public function PdfGenerator(images:Array, outFilePath:String)
{
this.outFilePath = outFilePath;
this.images = images;
}
public function generate():void
{
this.completedPages = 0;
this.pdf = new PDF(Orientation.LANDSCAPE, Unit.MM, Size.A4);
this.nextPage();
}
private function nextPage():void
{
this.completedPages < this.images.length ? this.loadImage(this.images[this.completedPages++]) : this.save();
}
private function loadImage(path:String):void
{
var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, this.onImageLoadComplete);
urlLoader.load(new URLRequest(path));
}
private function onImageLoadComplete(event:Event):void
{
this.pdf.addPage();
this.pdf.addImageStream(ByteArray(event.target.data), 0, 0, 0, 0, 1, ResizeMode.FIT_TO_PAGE);
this.dispatchEvent(new PdfGeneratorEvent(PdfGeneratorEvent.PDF_GENERATOR_PAGE_COMPLETE, false, false, this.images.length, this.completedPages));
this.nextPage();
}
private function save():void
{
this.pdf.end();
this.writeOutFile();
this.dispatchEvent(new PdfGeneratorEvent(PdfGeneratorEvent.PDF_GENERATOR_COMPLETE));
}
private function writeOutFile():void
{
var fileStream:FileStream = new FileStream();
fileStream.open(new File(this.outFilePath), FileMode.WRITE);
fileStream.writeBytes(this.pdf.save(Method.LOCAL));
fileStream.close();
}
}
}
Basically for each image it creates a new URLRequest and when the request is completed it adds a new page with the image. As you can see I also dispatch a custom Event called PdfGeneratorEvent. That because I need to create a PDF with a large number of images, and doing like this I can give a feedback to the user with a ProgressBar that display the generation progress.
package
{
import flash.events.Event;
public class PdfGeneratorEvent extends Event
{
public static const PDF_GENERATOR_PAGE_COMPLETE:String = "PDF_GENERATOR_PAGE_COMPLETE";
public static const PDF_GENERATOR_COMPLETE:String = "PDF_GENERATOR_COMPLETE";
public var totalPages:int;
public var completedPages:int;
public function PdfGeneratorEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false, totalPages:int=0, completedPages:int=0)
{
super(type, bubbles, cancelable);
this.totalPages = totalPages;
this.completedPages = completedPages;
}
}
}
And here the main file that uses this class:
public function createPdf(images:Array):void
{
var generator:PdfGenerator = new PdfGenerator(images, "/path/to/my/test.pdf");
generator.addEventListener(PdfGeneratorEvent.PDF_GENERATOR_COMPLETE, this.onPdfComplete);
generator.addEventListener(PdfGeneratorEvent.PDF_GENERATOR_PAGE_COMPLETE, this.onPdfPageComplete);
generator.generate();
}
public function onPdfPageComplete(event:PdfGeneratorEvent):void
{
progressBar.setProgress(event.completedPages, event.totalPages);
progressBar.label = event.completedPages + " of " + event.totalPages;
}
public function onPdfComplete(event:PdfGeneratorEvent):void
{
trace("completed")
}
July 12th, 2009 by Andrea Franz
Some weeks ago a spent a lot of time trying to fix a bug inside an AIR application I was working on.
I wrote a custom component that has an image as child. This image is draggable, and the container should act as a mask. The problem was that when I started dragging the picture, the picture went outside its container, like the following example.
Try dragging the red square outside the white one:
This movie requires Flash Player 9
I fixed this bug just setting the scrollRect property to the container:
This movie requires Flash Player 9
Only when I realized how to fix the bug I found another guy with the same problem and the same solution.
Here the code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application width="500" height="300" xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical" verticalAlign="middle" horizontalAlign="center"
applicationComplete="init()">
<mx:Script>
<![CDATA[
public function init():void
{
this.container.scrollRect = new Rectangle(0, 0, this.container.width, this.container.height);
this.draggableCanvas.addEventListener(MouseEvent.MOUSE_UP, this.mouseUpHandler);
this.draggableCanvas.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDownHandler);
}
private function mouseDownHandler(event:MouseEvent):void
{
this.draggableCanvas.startDrag();
}
private function mouseUpHandler(event:MouseEvent):void
{
this.draggableCanvas.stopDrag();
}
]]>
</mx:Script>
<mx:Canvas id="container" width="300" height="250" backgroundColor="white" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<mx:Canvas id="draggableCanvas" x="50" y="25" width="200" height="200" backgroundColor="#AA0000">
</mx:Canvas>
</mx:Canvas>
</mx:Application>