EdmontonOnRails

Posted by chris olsen on August 12, 2008

EdmontonOnRails is back with a vengeance! There was a previous attempt at getting Edmonton Rails developers together that didn’t make it too far, so hopefully things will be different this time around. The EdmontonOnRails site will be the place to keep up to date with news and upcoming events. If you, or others you know, live in Edmonton and want to join the group leave a comment below or email me (chris@chrisolsen.org) to let me know you are interested.

Avoid Inline Javascript with Rails and jQuery

Posted by chris olsen on February 15, 2008

Rails is a great framework and one that allows someone that is new to it get started quickly. I am sure there is many of us that remember the 15 minute blog tutorial, and I must admit, that seemed pretty damn cool the first time I saw it. However after using rails for a while it didn’t take long for some of the automation scripts to lose their appeal. I am now beginning to feel the same in regards to ajax helper methods and .rjs files. They were great at the start because making something cool with rails didn’t require me to learn a javascript library, I was able to just dive right in.

So to get away from all the needless javascript that is auto-created and embedded in the rendered html I decided it was time to read up on one of the many javascript libraries that are available. Even though rails, by default, comes with Prototype and Scriptaculous, I decided to check out jQuery for reasons that are irrelevant to this post.

To quickly test to see how well jQuery would work with Rails I figured that, at the very least, it would have to allow me to do the following:

  1. Provide the same functionality that the link_to_remote helper function does.
  2. Allow for simple form data submissions, ie. a filter from a search box
  3. Enable complex form data submittals

So let’s create a simple project that will just allow us to create a contact list.

rails MyContacts

Now let’s create a table to hold the contact information

script/generate model contact first_name:string last_name:string email:string phone_number:string

And to allow us to get a quick start let’s create some sample data

script/generate migration contact_data
class ContactData < ActiveRecord::Migration
  def self.up
    Contact.create(:first_name => "Joe", :last_name => "Smith", :email => "joe@example.com", :phone_number => "555-3432")
    Contact.create(:first_name => "Sally", :last_name => "White", :email => "sally@example.com", :phone_number => "555-8654")
    Contact.create(:first_name => "Mike", :last_name => "Green", :email => "mike@example.com", :phone_number => "555-6944")
    Contact.create(:first_name => "Mary", :last_name => "Brown", :email => "mary@example.com", :phone_number => "555-2346")
    Contact.create(:first_name => "Alice", :last_name => "Black", :email => "alice@example.com", :phone_number => "555-7866")
    Contact.create(:first_name => "George", :last_name => "Lucas", :email => "george@example.com", :phone_number => "555-1234")
    Contact.create(:first_name => "Jim", :last_name => "Anderson", :email => "jim@example.com", :phone_number => "555-4464")
  end

  def self.down
  end
end

Lastly, let’s create a controller

script/generate controller contacts index show
class ContactsController < ApplicationController
 
  def index
  	@contacts = Contact.find(:all)
  end
 
  def show
    @contact = Contact.find(params[:id])
  end
 
  def search
    filter = params[:filter]
    @contacts = Contact.find(:all, :conditions => ["first_name like ? or last_name like ?", "%#{filter}%", "%#{filter}%"])
    render :action => :index
  end
end

The first item in the required functionality list was to make simple get requests without using the link_to_remote. To do this lets show a list of the users with a link, that when clicked on will show their phone number and email. Below is the code that will allow for the list of contacts to be displayed. I also included the search form that will be used a little bit later.

<!-- contacts/index.html.erb -->
<h1>My Contacts</h1>
<%= link_to "Create Contact", new_contact_url %>
 
<% form_tag search_contacts_url do %>
	Search By Name: <%= text_field_tag :filter %> <%= submit_tag "Search" %>
<% end %>
 
<ul class="contacts">
  <%= render :partial => "contacts/contact", :collection => @contacts %>
</ul>
 
<!-- contacts/_contact.erb -->
<li><%= link_to "#{contact.first_name} #{contact.last_name}", contact_url(contact) %></li>
 
<!-- contacts/show.html.erb -->
<h2><%= "#{@contact.first_name} #{@contact.last_name}" %></h2>
<%= render :partial => "contacts/contact_details", :object => @contact %>
 
<!-- contacts/_contact_details.erb -->
<ul>
	<li>Phone #: <%= contact_details.phone_number %></li>
	<li>Email: <%= contact_details.email %></li>
</ul>
 
<!-- routes.rb -->
map.resources :contacts, :collection => {:search => :post}
 
<!-- web.css -->
body {background-color:#9FDD8F;}
#wrapper {margin:auto; width:760px;}
#centre {float:left; background-color:white; width:760px; padding:15px;}
.contacts {float:left;}
#contact_details {float:left; margin-left:50px;}
  #contact_details dt {float:left; width:60px;}
 
<!-- application.html.erb -->
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 
<head>
	<title>My Contacts</title>
	<meta http-equiv="content-type" content="text/html;charset=utf-8" />
	<meta http-equiv="Content-Style-Type" content="text/css" />
	<%= stylesheet_link_tag "web" %>
	<%= javascript_include_tag "jquery-1.2.3.min.js" %>
	<%= yield :javascript %>
</head>
 
<body>
	<div id="wrapper">
		<div id="centre">
			<%= yield :layout %>
		</div>
	</div>
</body>
</html>

If we test what we have out you will see that it works, but having to go to a new page to view the contact’s information, then click the back button to return to the contact list makes me feel rather nauseous. So let’s make the changes to insert the ajax functionality and bring things up to the web 2.0 standards.

Before we get started we will have to download the latest version of jQuery, which can be found on the main page here. Save the file in the public/javascripts folder.

Next we will create a helper method that will allow us to easily insert our custom javascript into the HEAD tag of the page. I have to thank Ryan Bates for letting me know of this method.

# app/helpers/application_helper.rb
def javascript(url)
  content_for :javascript do
    javascript_include_tag url
  end
end

The two blocks of code above allow us to easily fetch the javascript within the js.erb files, since that is where we are going to put it. To insert the javascript file add the following at the top of the index.html.erb file.

<!-- index.html.erb -->
<% javascript formatted_contacts_url("js") %>

This will generate the script tag that will make a request to the controller for the index.js.erb. To allow for this we will also have to update the index method in the Contacts controller. Now our dynamic javascript will be sent back to the client.

def index
 	respond_to do |format|
 	  format.html {@contacts = Contact.find(:all)}
 	  format.js  # returns the index.js.erb   # Add this line of code
 	end
 end

As was mentioned earlier, there were a few things that I wanted to make sure were do-able without too much work. The first one was to make an ajax request much like the link_to_remote helper function. So instead of directing a person to the contact details page, show the details on the contact list page. To do this we will have to make some updates to the controller code to allow it to handle the javascript requests.

def show
  @contact = Contact.find(params[:id])
  respond_to do |format|
    format.html
    format.js {render :partial => "contacts/contact", :object => @contact}
  end
end

As you can see, we are calling on the render method for the partial within the controller code, rather than from the rjs file, to where it normally resides. This will generate the html block of code that we will then insert into the DOM. Before we do that we have to create the javascript that will make the AJAX request, as well as handle the callback. To do this insert the following code in a new index.js.erb file.

// app/views/contacts/index.js.erb
$(document).ready(function(){
	$contact_details = $("<div id='contact_details'></div>").insertAfter($("ul.contacts"))
	$("ul.contacts a").click(function(){
		$.get($(this).attr("href") + ".js", function(data){
			$contact_details.html(data);
		});
		return false;
	});
});

I will am only going to give a brief explanation of the javascript. If you do want to get up to speed with jQuery, this book is very well written and within a few hours you will know your stuff.

On line 2 we bind the DOM loaded event with the inline function ie. the remainder of the code. In line 3 I insert a div tag just after the ul tag to allow the search results to be shown. The reason for this is the div tag that is inserted will only be used to hold the data returned from the ajax request, so it really doesn’t make a lot of sense to hard code it into the html. Line 4 binds click events to all the links within the ul.contacts tag. Line 5 is the function that is bound to the links within the ul tag. This makes a GET request to the same url that is contained within the link to which the event is bound. The second parameter to the $.get() method is the callback function that will insert the returned data into the contact_details div tag.

So far everything is working nicely. We are able to easily view the details for all of our contacts. The next step is to make the updates to allow us to search for contacts via AJAX requests, which can be done with the following code.

First, we will have to update the index.js.html file.

//bind the ajax method to the form
$("form").submit(function(){
	$.post("<%= formatted_search_contacts_url('js') %>", $(this).find("input").serialize(), function(data){
		$(".contacts").html(data);
	});
	return false;
});

Here we bind an inline function to the submit event of the form. The post function takes 3 parameters. The first is the url to post to. Since this code exists in an .erb file and can be dynamically rendered back to the client we are able to use one of the RESTful routes supplied by rails to create the url. Since we are making a javascript request we also have to properly format the url to allow the request to be properly handled by the server. The second parameter consists of the form data. There are a few different ways to pass the data, but the method used here is the easiest. The last parameter is the callback function that will insert the returned data into the page.

Before we go any further I should mention there is another way to do what was done above. There is an additional jQuery plugin that makes form submittal even easier that can be found here. To use this first download the file to the javascript directory, then add the additional javascript file to the head tag.

<%= javascript_include_tag "jquery-1.2.3.min.js", "jquery.form.js" %>

Below is the code that will now allow us to make post requests. The nice thing about this method is that it makes it easier to bind multiple functions to be fired on the beforeSubmit and the success events. I think this code is pretty self-explanatory so I won’t bother going over it. If you want to use the second method mentioned replace the previously mentioned code with the following.

$("form").ajaxForm({
    url: "<%= formatted_search_contacts_url('js') %>", 
		beforeSubmit: function() {alert("This is where we would show our cool little spinner");},
    success: function(data){
         $(".contacts").html(data);
	 return false;
    }
});

With these updates we are now able to search our contact list via AJAX, but there is one catch. If you perform a search to filter the contacts, then click on a contact to view their details you will then be directed to the details page. What is the reason for this you ask? Since our javascript is no longer embedded within a onclick in the link tag that means that responsibility has fallen onto our shoulders, but there are a couple ways to fix this.

The first way is to bind the new items returned in the search results. This method would require a little re-factoring of our current code and isn’t overly hard to do, but there is an easier way. Thanks to event-bubbling all we have to do is bind the event to a parent of the links rather than the links themselves. Update the index.js.erb file to include the following in place of the previous block that bound the click events to the links.

//bind ajax to view the contact details
$("ul").click(function(event){
	$link = $(event.target);
	$.get($link.attr("href") + ".js", function(data){
		$contact_details.html(data);
	});
	return false;
});

Now all the links works as they should after a search is performed.

The last item on my requirement list was to submit a more complex form. As it turns out it can be done using the same method as used in the search example, which is pretty cool. So I am pretty sure that it is safe to avoid repeating myself in another form example.

This wraps up this post regarding avoidance of inline javascript. The html that is returned to the user is clean and it will prevent the user from downloading needless amounts of data in the event that they are accessing the site and have javascript disabled or are unable to use javascript.

Learn RubyOnRails in 30 minutes

Posted by chris olsen on February 09, 2008

Recently, there was a couple articles posted (here and here), regarding how long it takes for a person to become familiar with a toolset. In light of these articles I found this job advertisement today and thought it was rather amusing.

Qualifications:

  • A thorough understanding of ASP.NET (or a similar server-side web platform, such as J2EE, PHP or Ruby on Rails), not only the language, but the community, libraries, resources and best practices
  • Experience with CSS, DHTML and JavaScript
  • Experience with AJAX (an asset)
  • A knack for usability design
  • A working familiarity of databases (SQL & Oracle)
  • Able to learn in half an hour, given access to a web browser, anything on this list
  • Familiarity with Macs (an asset)

I am really kickin’ myself for spending all that time reading books and working on whatever I could to help me learn a new language/framework, when all I had to do was to check out the internet for 30 minutes.

Google Charts with Rails

Posted by chris olsen on January 26, 2008

Nothing beats some nice charts to dazzle your clients. I guess this is another addition to a couple of previous posts that go over various charting tools that can be found here and here.

If you haven’t checked out Google Charts go here to check out the API. To use Google’s charts you have to encode the data with one of the three allowable formats.

  • Simple encoding uses the alphanumeric characters (A to Z, a to z, and 0 to 9) where A represents 0, B represents 1, and so on up to 9, representing 61 to provide a resolution of 62 different values. Allowing five pixels per data point, this is sufficient for line and bar charts up to about 300 pixels. Simple encoding is suitable for all other types of chart regardless of size. This type of encoding results in the shortest URL.
  • Text encoding uses floating point numbers between 0.0 and 100.0 to provide a resolution of 1,000 different values. Allowing five pixels per data point, integers (1.0, 2.0, and so on) are sufficient for line and bar charts up to about 500 pixels. Include a single decimal place (35.7 for example) if you require higher resolution. Text encoding is suitable for all other types of chart regardless of size.
  • Extended encoding uses pairs of alphanumeric (plus a few others that are discussed later) where AA represents 0, AB represents 1, and so on up to two periods (..) representing 4095 to provide a resolution of 4,096 different values. This is best suited for large charts with a large data range is required.

The good thing is, is that there are a few tools that are available for Rails that will do the work for us. I fooled around with the top two of them, although I am a little unsure of the status of either.

I had some issues with Google Charts on Rails as the labels would not render as they were supposed to. After looking at the source code it seemed that, by all the #TODO… comments that the development on this plugin may be discontinued.

With the first not working as I thought it would I moved onto the second. Google Charts worked very nicely and within a couple of minutes of reformatting the reporting data I had some pretty cool charts.

Since Google Charts works I never even bothered to test gchartb although I will put it on my to-do list.

So let’s create some graphs. First thing that we have to do is install the GoogleCharts gem

sudo gem install googlecharts

Once that is done we will have to add the following to the top of our controllers/application.rb file. You could also just add this line to the controller(s) that contain reporting methods.

require "gchart"  #the line to add
 
class ApplicationController < ActionController::Base
  ...

For the tests I created a Seller and Sale models with the following migration files.

# 001_create_sellers.rb
class CreateSellers < ActiveRecord::Migration
  def self.up
    create_table :sellers do |t|
      t.string :name
      t.timestamps
    end
  end
 
  def self.down
    drop_table :sellers
  end
end
 
# 002_create_sales.rb
class CreateSales < ActiveRecord::Migration
  def self.up
    create_table :sales do |t|
      t.decimal :amount, :precision => 10, :scale => 2
      t.date :sold_on
      t.integer :seller_id
      t.timestamps
    end
  end
 
  def self.down
    drop_table :sales
  end
end

And of course to test out the charts we will need some test data.

# 003_test_data.rb
class SampleData < ActiveRecord::Migration
  def self.up
    # create some sellers
    john = Seller.create(:name => "John")
    susan = Seller.create(:name => "Susan") 
 
    # add sales to sellers
    [john, susan].each do |seller|
      (0..20).each do |i|
        Sale.create(:amount => rand(1000), :seller_id => seller.id, :sold_on => DateTime.now + i.days)
      end
    end
  end
  def self.down
  end
end

Now we have to be able to access the data in a format that will work well with charts. To do this I created a sales_report method within the Seller model.

class Seller < ActiveRecord::Base
  def sales_report
    data = []
    labels = []
    Sale.find_all_by_seller_id(self.id).each do |s|
      labels << s.sold_on.to_date.strftime("%B %d")
      data << s.amount
    end
    {:labels => labels, :data => data}
  end
end

That is it for the dirty work, it is smooth sailing from here (said with a lot of sarcasm).

All we have to do now is access the data from the controller.

class SalesReportController < ApplicationController
  def show
    @report = Seller.find(params[:id]).sales_report
  end
end

and make sure that the routing is set up by adding the following to the router.rb file

map.resources :sales_report

To display it on the page I created a show.html.erb file with the following code.

<%= image_tag Gchart.bar(:title => 'Sales', :size => '600x200', :data => @report[:data], :custom => "chbh=6,2,0") %>

I did leave out the labels on the chart. Since I had so much data they were taking up way too much room. To add the labels you would just append :labels => @report.labels to the arguments in the bar method call.

Here is the final result.

Picture 1-1

It is pretty cool that Google provides us with the service free of charge. The downfall is that there is a daily limit of 50,000 charts that can be generated per site. I can’t think of anything that I have worked on that would exceed that number, but things can change. The other issue is that Google has control and at any time could discontinue supplying the service. Personally I can’t picture Google doing something like that, but anything can happen.

Although Google’s API and GoogleCharts make things pretty easy I hope this helps a couple of people.

Technorati Tags: ,

Custom Validation Messages

Posted by chris olsen on January 25, 2008


8 errors prohibited this account from being saved
There were problems with the following fields:

Maybe it is just me, but having error messages like the above seems a little to artificial to me. Maybe rails has standard error messages like the following so everybody changes them, who knows.

One thing I found when searching for customized error formatters is that they would only accept one model object reference, ie, they would only allow for

error_messages_for(:account)

and not

error_messages_for(:account, :user)

which is odd seeing as the overridden method does allow for many models.

Below is a custom method that will allow us to deliver the message with more of a person to person flavour.

def error_messages_for(*object_names)
  messages = []
  object_names.each do |object_name|
    object = instance_variable_get("@#{object_name}")
    if object && !object.errors.empty?      
      object.errors.full_messages.each do |message| 
        messages << %(<li>#{message}</li>) unless message =~ /is invalid/
      end
    end
  end
 
  if messages.size > 0
    content_tag(:div, 
    content_tag(:h2, "Uh-oh! We have some invalid fields.") +
    content_tag(:ul, messages),:id => 'errors')
  end
end

It will also prevent messages like “Users is invalid” (yes that is pluralized on purpose) that occur from having the validates_associated :user validator telling us that the associated model is invalid.

This isn’t groundbreaking, but I know I will be searching for this again sometime soon.

Rails setup on Virtual/Private Server

Posted by chris olsen on December 30, 2007

A while back I posted about how to set rails up on Linode. While the instructions were in the right direction, I notices a couple of errors when I was deploying a recent application. Instead of updating the previous post I figured I would start from scratch, but this time leave out a lot of the small talk and just get down and dirty. The previous article, as well as this one, may seem to be specific to Linode, but the steps should be applicable to any virtual or private server.

This is written assuming that you have already signed up to Linode and have installed the Debian 4.0 distro as well as have your project stored within a subversion repository.

SSH into the server.

#linode machine
ssh root@ip_address

Server

Once logged into the server we need to ensure everything is up to date.

apt-get update
apt-get upgrade

Install some of the required libraries.

apt-get install ruby1.8-dev ruby ri irb rdoc libmysql-ruby mysql-server nginx subversion build-essential libopenssl-ruby1.8 sudo

Install RubyGems (Check here for the most recent release, although at the time of this writing 1.0.1 was out, but was causing me problems. I ended up sticking with v0.9.4).

wget http://rubyforge.org/frs/download.php/20989/rubygems-0.9.4.tgz
tar xzvf rubygems-0.9.4.tgz
cd rubygems-0.9.4
ruby setup.rb

Install Rails without all the documentation, it will save you a lot of time.

gem install rails --include-dependencies --no-rdoc --no-ri

Install mongrel and the mongrel cluster.

gem install mongrel -y
gem install mongrel_cluster -y

Create a new user (mongrel) that will be used to deploy the app.

useradd -m mongrel
su - mongrel
mkdir ~/.ssh
chmod 700 ~/.ssh
cat /root/my_id >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Grant permissions to the user, mongrel.

export VISUAL=vi
visudo

Add the following line just under where root is granted all permissions.

mongrel ALL=(ALL) NOPASSWD:ALL

Create the folder where the project will be deployed to and set the proper permissions.

mkdir /var/rails
chown root:mongrel /var/rails
chmod g+w /var/rails

Local Machine

If you don’ have an ssh key created perform the following:

ssh-keygen -t rsa
scp ~/.ssh/id_rsa.pub root@ip_address:~/my_id

Ensure Capistrano is installed.

cap --version

If you see some output like $ Capistrano v2.x.x then all is good if not run the following:

sudo gem install capistrano -y

Capify your project.

cd folder_of_your_project
capify .

Next we need to create a file named “spin” in the script folder.

vi script/spin

Then insert the following and save it

mongrel_rails cluster::start

Let’s now add the new file to the project and give make it executable.

svn add config/spin
svn propset svn:executable on config/spin

Before deploying we have to make sure that the repository is fully up to date, so let’s check on the status, add any files that files that have not yet been added, then commit.

svn status
svn add
svn ci -m "Ready to deploy"

There are some changes that have to be made to the nginx.conf file and Bryan was kind enough to provide them to us. The following code also contains updates that I inserted. I will talk more about these later.

set :application, "my_app_name"

set :deploy_to, "/var/rails/#{application}"
set :user, "mongrel"
set :runner, "mongrel"

role :app, "liXX-XXX.members.linode.com"
role :web, "liXX-XXX.members.linode.com"
role :db,  "liXX-XXX.members.linode.com", :primary => true

set :svn_user, ENV['svn_user'] || "your_svn_user_name"
set :svn_password, Proc.new { Capistrano::CLI.password_prompt('SVN Password: ') }
set :repository,
Proc.new { "--username #{svn_user} " +
           "--password #{svn_password} " +
           "--no-auth-cache " +
           "http://your_svn_repos_project_path" }

namespace :init do
  desc "create database.yml"
  task :database_yml do
    set :db_user, Capistrano::CLI.ui.ask("database user: ")
    set :db_pass, Capistrano::CLI.password_prompt("database password: ")
    database_configuration =<<-EOF
---
login: &login
  adapter: mysql
  database: #{application}
  host: localhost
  username: #{db_user}
  password: #{db_pass}

production:
  <<: *login

EOF

    run "mkdir -p #{shared_path}/config"
    put database_configuration, "#{shared_path}/config/database.yml"
  end

  desc "create mongrel_cluster.yml"
  task :mongrel_cluster_yml do
    mongrel_cluster_configuration = <<-EOF
---
user: mongrel
cwd: #{current_path}
log_file: #{current_path}/log/mongrel.log
port: "8000"
environment: production
group: mongrel
address: 127.0.0.1
pid_file: #{current_path}/tmp/pids/mongrel.pid
servers: 3
EOF

    run "mkdir -p #{shared_path}/config"
    put mongrel_cluster_configuration, "#{shared_path}/config/mongrel_cluster.yml"
  end
end

namespace :localize do
  desc "copy shared configurations to current"
  task :copy_shared_configurations, :roles => [:app] do
    %w[mongrel_cluster.yml database.yml].each do |f|
      run "ln -nsf #{shared_path}/config/#{f} #{current_path}/config/#{f}"
    end
  end

  desc "Sync the uploads to the new current project"
  task :sync_uploads, :roles => [:app] do
    run "mkdir -p #{shared_path}/uploads"
    run "chown #{user}:#{user} #{shared_path}/uploads/"
    run "ln -nsf #{shared_path}/uploads #{current_path}/public/uploads"
  end
end

namespace :deploy do
  desc "stop mongrel cluster"
  task :stop do
    run "cd #{current_path};mongrel_rails cluster::stop"
  end

  desc "restart mongrel cluster"
  task :restart do
    run "cd #{current_path};mongrel_rails cluster::restart"
  end

end

after "deploy:setup", "init:database_yml"
after "deploy:setup", "init:mongrel_cluster_yml"
after "deploy:symlink", "localize:copy_shared_configurations"
after "deploy:symlink", "localize:sync_uploads"

There are some changes that have to be made to the nginx.conf file and Brian was kind enough to provide them to us. To make things easier for every one the nginx.conf settings are shown below.

user mongrel mongrel;
worker_processes 6;

error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
	worker_connections 1024;
}

http {
	include /etc/nginx/mime.types;
	default_type application/octect-stream;

	log_format main '$remote_addr - $remote_user [$time_local] $request '
	'"$status" $body_bytes_sent "$http_referer" '
	'"$http_user_agent" "$http_x_forwarded_for"';

	access_log /var/log/nginx/access.log;

	sendfile on;
	tcp_nopush on;
	tcp_nodelay off;
	gzip on;
	gzip_http_version 1.0;
	gzip_comp_level 2;
	gzip_proxied any;
	gzip_types text/plain text/html text/css application/x-javascript text/xml application.xml application/xml+rss text/javascript;

	# define your mongrel cluster here
	upstream mongrel_cluster {
		server 127.0.0.1:8000;
		server 127.0.0.1:8001;
		server 127.0.0.1:8002;
	}

	server {
		listen 80;
		client_max_body_size 100M;
		root /var/rails/YOUR_APPLICATION_NAME_GOES_HERE/current/public;
		access_log /var/log/nginx/commuitycms.access.log main;

		if (-f $document_root/system/maintenance.html) {
			rewrite  ^(.*)$  /system/maintenance.html last;
			break;
		}

		location / {
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header Host $http_host;
			proxy_redirect false;
			proxy_max_temp_file_size 0;

			if (-f $request_filename) {
				break;
			}

			if (-f $request_filename/index.html) {
				rewrite (.*) $1/index.html break;
			}

			if (-f $request_filename.html) {
				rewrite (.*) $1.html break;
			}

			if (!-f $request_filename) {
				proxy_pass http://mongrel_cluster;
				break;
			}
		}

		error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/rails/tissue_bank_trunk;
    }
	}
}

Copy the file up to the server from your local machine.

scp ~/Desktop/smarticast_railsrumble/nginx.conf ip_address:~/nginx.conf

Server

Then backup the original version and move the new version in to replace the previous.

su - mongrel   #unless you are already logged in as mongrel
mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old
mv ~/nginx.conf /etc/nginx/nginx.conf

Update the nginx.conf file server { root /var/rails/project_name/current/public } value to contain the name of your project. Then restart the server.

/etc/init.d/nginx restart

While we are on the server let’s create the database.

mysqladmin -u root -p create database_name

The last thing that has to be done is to ensure that the mongrel cluster will be started if the server is rebooted.

mkdir /etc/mongrel_cluster
sudo ln -s /var/rails/my_app/current/config/mongrel_cluster.yml /etc/mongrel_cluster/my_app.yml
cp /usr/lib/ruby/gems/1.8/gems/mongrel_cluster-1.0.2/resources/mongrel_cluster /etc/init.d
sudo chmod 755 /etc/init.d/mongrel_cluster
sudo /usr/sbin/update-rc.d -f mongrel_cluster defaults

It is now time to reboot the server, and once it starts back up if the site is accessible that is a good thing. If errors are being thrown the best place to find the problem is by checking out the log files on the server.

Local

Time for the deployment.

cap deploy:setup

If all goes well on the setup let’s deploy.

deploy:cold

All code updates from here on just have to be checked into the subversion repository by one of the following:

cap deploy

That is it. Time for a celebratory beer.

Additional Notes

To retain uploaded files upon later deployments you will have to place these files in a folder that exists outside your project and create a sym link to the folder from within your project. That is what the following code is doing:

desc “Sync the uploads to the new current project”
task :sync_uploads, :roles => [:app] do
  run “mkdir -p #{shared_path}/uploads”
  run “chown #{user}:#{user} #{shared_path}/uploads/”
  run “ln -nsf #{shared_path}/uploads #{current_path}/public/uploads”
end
...
after “deploy:symlink”, “localize:sync_uploads”

*** Read update below before installing RMagick ***
Many of you will also need an image library installed to allow you resize images on upload. Although there are a few libraries out there I have had the most success with RMagick. Some people may argue that it is a little bloated, but I can always get it to work. If I ever write an amazing web app that has so much traffic that the only way I can keep the site alive is to use a lighter library then I will do exactly that, but until then I will stick with RMagick.

To install it you will need to ensure that you have a number of required libraries installed first.

sudo apt-get install build-essential libc6-dev libmagick9 libmagick++9-dev libmagic9-dev

Then install RMagick

sudo gem install Rmagick

*** UPDATE ***
I take back what I said about successfully installing an image library. Mini-Magick is super easy to install and is carries a much lighter load. I was a little amazed, and happy, at the amount of memory that was freed up.

Install it with the following

sudo gem install mini_magick

Now update the image processor that attachment_fu will use by adding the following as a parameter to the has_attachement call

:processor => :MiniMagick

*** END OF UPDATE ***

amCharts - Another flash based charting solution

Posted by chris olsen on September 20, 2007

Well it wasn’t too many posts ago that I was raving over Ziya Charts and how wonderful they were. Things haven’t really changed in that regard, but there is another set of charting tools that can be found at amCharts which are also able to produce professional looking charts.

Getting Started

Before we get started you will have to download the required files from amCharts here. Unzip the file and move the .swf, .js, and amline_settings.xml files to a new folder, named amCharts, within your projects’ public folder.

Run the following command to create the controller, model, etc that will be used in this example.

scaffold_resource trend_item

Let us now create a migration file to create the trend_items table in the database.

1
2
3
4
5
6
7
8
9
10
11
class CreateTrendItems < ActiveRecord::Migration
  def self.up
    create_table :trend_items do |t|
      t.column :value, :integer
    end
  end
 
  def self.down
    drop_table :trend_items
  end
end

Since we are using REST we will also have to make the method that is returning that xml data available. So add the following to the routers.rb file.

1
map.resources :trend_items, :collection => {:recent => :get}

Add the additional method to the TrendItemsController class.

81
82
83
84
85
  def recent
    #render the data to an xml format
    @trend_data = TrendItem.find(:all)
    render :layout => false
  end

To make sure we get the xml format, line 84 prevents any html output from the layout. Since we are spitting out some data, so we need something to do something with this data, and who better then a recent.rxml file with a touch of the xml builder library.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xml.chart do 
  xml.xaxis do 
    @trend_data.each_with_index do |item, index|
     xml.value(index, :xid => index)
    end
  end  
  xml.graphs do
    xml.graph(:gid => 1) do
      @trend_data.each_with_index do |item, index|
       xml.value(item.value, :xid => index)
      end
    end
  end
end

The last thing that we have to do is insert the required html. Now there are a couple of quick ways to this. We could just copy and paste the javascript that is shown below into our page.

1
2
3
4
5
6
7
8
9
10
<script type='text/javascript'>
    var amline_path = 'amline/'; 
    var amline_settingsFile = 'amline/amline_settings.xml?10';
    var amline_dataFile = '<%= formatted_recent_trend_items_url(:xml) %>';
    var amline_flashWidth = '520';
    var amline_flashHeight = '400';
    var amline_backgroundColor = '#FFFFFF';
    var amline_preloaderColor = '#000000'
</script>
<script type='text/javascript' src='amline/amline.js'></script>

But of course we know that this should be packaged up into a nice helper function to make things a little cleaner. I admit something like this could be packaged up into a nice little gem, but I am a little rusty on creating those things so I am going to have to leave it like this…for now at least.

We could throw this into the ApplicationHelper module, but it would probably best to create a separate AmCharts helper don’t you think? To do that create a new file in the app/helpers folder called am_charts_helper.rb with the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module AmChartsHelper
 
  def render_chart(root_path, settings_file_name, callback_uri, width, height, bg_color, preloader_color)
    "<script type='text/javascript'>
    var amline_path = '#{root_path}/'; 
    var amline_settingsFile = '#{root_path}/#{settings_file_name}';
    var amline_dataFile = '#{callback_uri}';
    var amline_flashWidth = '#{width}';
    var amline_flashHeight = '#{height}';
    var amline_backgroundColor = '#{bg_color}';
    var amline_preloaderColor = '#{preloader_color}'
    </script>
    <script type='text/javascript' src='#{root_path}/amline.js'></script>"
  end
 
end

With this alone we won’t yet be able to access the methods within this helper. To do so we will have to add the following line into the ApplicationController class.

1
2
3
4
5
6
class ApplicationController < ActionController::Base
  ...
 
  helper :am_chart
 
end

Now all we have to do is call the method within the rhml page we wish for the graph to be displayed on, in this case the trend_items/index.rhtml page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<h1>Listing trend_items</h1>
 
<%= render_chart("amline", "amline_settings.xml", formatted_recent_trend_items_url(:xml), 520, 400, "#FFFFFF", "#000000") %>
 
<table>
  <tr>
  </tr>
 
<% for trend_item in @trend_items %>
  <tr>
    <td><%= link_to 'Show', trend_item_path(trend_item) %></td>
    <td><%= link_to 'Edit', edit_trend_item_path(trend_item) %></td>
    <td><%= link_to 'Destroy', trend_item_path(trend_item), :confirm => 'Are you sure?', :method => :delete %></td>
    <td><%= trend_item.value %></td>
  </tr>
<% end %>
</table>
 
<br />
 
<%= link_to 'New trend_item', new_trend_item_path %>

To test everything out I entered some quick test data and was able to get the nice little chart shown below.

amCharts

My Preference

Both amCharts and Ziya Charts are simple to use once the styles are set up to your liking. There is a slight pricing difference between the two, where amCharts is more expensive for an individual site license, yet is a little less* than XML/SWF (Ziya) if you buy an unlimited. From what I have seen that is much more documentation for the Ziya (XML/SWF) charts, but from what I have seen on the amCharts forum, documentation is soon to come. If I had to make a decision right now I would probably go for the amCharts, but it is so close I could change my mind on that before I finish this post.

*~$180 at the time of writing

Technorati Tags: , , ,

Uploading images with attachment_fu

Posted by chris olsen on September 16, 2007

A while back I was trying to figure out what was the best way to upload images to the server. Rick Olson’s attachment_fu plugin was the best way that I could find. Not only is it easy to upload images to your server, but with a couple small changes you are able to upload images to Amazon’s S3 (Simple Storage Service) with the addition of a yml file and quickly setting the :storage parameter to :s3 that is passed to the has_attachment method.

I could list the steps on how to do this, but I think that Mike Clark does a fine job of that here.

One thing that I should mention is that the Firefox S3 Organizer add-on can help you out in creating an initial bucket as well as managing the existing files.

Ziya Charts for Rails

Posted by chris olsen on September 15, 2007

We are an information driven society and nothing puts a damper on things like having some statistics displayed for you to analyze in a tabular format. Don’t get me wrong, numbers are good, but for the most part people want to be able to look at the data and gain instant knowledge to what the numbers mean. This is obviously not a new concept, but often when creating an application if displaying information in a graphical format it may be crossed off the todo list.

There are a few different options out there for creating charts with Rails, but Ziya is on the top of my list. The displayed output has a professional feel and making things work can be done in only a couple lines.

To integrate Ziya charts into your project you will have to obtain the latest version from their repository.

ruby script/plugin install svn://rubyforge.org/var/svn/liquidrail/plugins/ziya/trunk

This will add all the required files to your project’s vendor folder.

It is now time to create our controller for the model that we wish to graph the data for.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class TrendItemsController < ApplicationController
  include Ziya
 
  before_filter :init
 
  def graph
    graph = Ziya::Charts::Line.new( nil, nil, "custom_bar" )
 
    # obtain the data we wish to show and insert into two corresponding arrays
    dates = @trend.trend_items.map {|item| item.taken_on}
    values = @trend.trend_items.map {|item| item.value}
 
    # custom method that 'massages' the data a bit.  I will talk about this in a bit
    graph_data = fill_empty_values(dates, values)
 
    # bind the data to the chart
    graph.add :axis_category_text, graph_data[0].map { |item| item.to_s(:short)}
    graph.add :series, "Weights (lbs.)", graph_data[1]
 
    # send the data back the browser in an xml format
    render :xml => graph.to_s
  end
 
  private
 
  def init
    @trend = Trend.find(params[:trend_id])
  end
end

Before anything we have to make the methods provided to use in the Ziya plugin available within the controller which is done by the include statement on line 2.

The next thing that has to be done is create a method that will be called via ajax to return the data in an xml format. If you are using REST this means you will also have to map the method to make it available in the routes.rb file.

1
2
3
4
5
6
ActionController::Routing::Routes.draw do |map|
  map.resources :trends do |trend|
    trend.resources :trend_items, :collection => {:report => :get, :open_report => :get, :graph => :get}
  end
  ....
end

The example above is what I used since I have an additional TrendsController for the trend model that has a “has many” relation to the trend_item model, so you will have to make the necessary adjustments to fit your model relations.

The last thing that has to be done is add the html embedded code required to make the chart appear. To do this I placed the following in the trend_items/index.rhtml file.

<%= ziya_chart(graph_trend_items_path(@trend), {:bgcolor => "#666666", :height => 250, :width => 400}) %>

It is a pretty simple method call where the first argument is the RESTful url that will call on the graph method in the TrendItemsController. You can also pass it an hash array of options that will be inserted into html <object> tag that is returned on the ajax call.

graph = Ziya::Charts::Line.new( nil, nil, "custom_bar" )

In the first line of code in the graph function there is a value of “custom_bar” that is passed to the Ziya:Line when created. This is an optional parameter and, when passed in, allows us to customize the appearance of the chart. The value represents a custom .yml file located in the public folder of our project, in this case with the filename custom_bar.yml. If you want to put the file into a subfolder within the public folder you can and will just have to set the value passed in the Line object creation to “the_folder/custom_bar”.

Below is what I put together in somewhat replicating an example that I saw on the XML/SWF Charts site. It may look a little cryptic, but it will make better sense if you visit the reference section at the XML/SWF Charts site.

#Overriden bar chart styles
<%=chart :Line %>
 
  <%= component :chart_pref %>
    line_thickness:   2
    fill_shape:       false
    point_shape:      circle
 
  <%=component :chart_transition %>
    type:             dissolve
    duration:         0.5
 
  <%=component :chart_value %>
    alpha:            60
    position:         cursor
 
  # Change y axis thickness
  <%=component :chart_border%>
    left_thickness:   2
    right_thickness:  2
    bottom_thickness: 2
    top_thickness:    2
    color:            333333
 
  <%=component :chart_grid_v %>
    thickness:        0
 
  # Change x axis label colors
  <%=component :axis_value%>
    color:          cccccc
    alpha:          80
    min:            150
    max:            350
 
  <%=component :axis_ticks %>
    minor_color:    333333
    major_color:    333333
 
  # Change y axis label colors
  <%=component :axis_category%>
    orientation:    horizontal
    color:          cccccc
    alpha:          80
    orientation:    diagonal_down
    skip:           1
 
  # Change legend rectangle
  <%=component :legend_rect%>
    x:              40
    fill_color:     666666
 
  <%=component :legend_label %>
    color:          ffffff
 
  <%=component :series_color %>
    colors: FF6600, FFCC00, FF9900
 
  # Change chart rectangle
  <%=component :chart_rect%>
    negative_color: c0b15c
    positive_color: 333333
    negative_alpha: 30
    x:              40
    y:              25
    height:         160
    width:          310    
 
  # Add a chart title
  <%=component :draw%>   
    components:         
      - <%=drawing :text%>
        transition: slide_down
        delay:      0
        duration:   0.5
        bold:       true
        rotation:   270
        color:      ffffff
        alpha:      20
        size:       25
        x:          0
        y:          230
        text:       Weight
      - <%=drawing :text%>
        transition: slide_left
        delay:      0.5
        duration:   0.8
        bold:       true
        rotation:   0
        color:      ffffff
        alpha:      20
        size:       25
        x:          50
        y:          20
        text:       Months
    # custom method that 'massages' the data a bit.  I will talk about this in a bit
    graph_data = fill_empty_values(dates, values, true)

In one of the comments in the controller there was some custom code that, as I put, massaged, the data. What I meant by that was often when plotting data values wrt time there are gaps in the frequency that that values were obtaine. When data exists containing these gaps, problems arise when the data is displayed as the distance between each point displayed in the graph is equal, and not properly representing the actual length of time, this can be seen in Figure 1 and 2. As I mentioned earlier, people like to be able to get a feel for the data by quickly glancing at the charts, and in this case could give people false impressions. As you may have guessed, the fill_empty_values method call fixes this problem by simply inserting filling time points into the dates and values array.

Figure 1

Picture 1

Figure 2

Picture 2

The last parameter, which by default is true, defines where to insert additional points where there were previously none, which is shown be the additional points in Figure 2 in comparison to Figure 1. The reason for doing this is that when creating a line graph, lines will only be created for consecutive points. This means that if you are missing a point a gap will appear in the chart. Passing the value true, or leaving it blank, will create 1..n points to allow the line to be properly rendered.

To make this method available we will have to add an additional include to the controller class.

class TrendItemsController < ApplicationController
  include Ziya, ZiyaHelper

And finally we will have to create the ZiyaHelper class in the helper folder containing the following code.

module ZiyaHelper
 
  # Inserts values into the two arrays to prevent inaccurate scaling of
  # values lying on the x-axis.  
  def fill_empty_values(time_arr, val_arr, insert_averages = true)  
 
    time_arr.sort!
    time_values = fill_timespan(time_arr)
    chart_values = match_values(time_values, time_arr, val_arr)
 
    if insert_averages : chart_values = insert_average_values(time_values, chart_values) end
 
    return [time_values, chart_values]
  end
 
  private
 
  def insert_average_values(time_values, chart_values)
    #insert avg values for current nil values
    previous_point = nil
 
    chart_values.each_with_index do |val, index|
      # does the current point require a calculated value
      if val.nil?        
        x2, y2 = calculate_point(chart_values, previous_point, time_values, index)
        #set the missing value
        chart_values[index] = y2
        #the new point is set to the previous point for the next loop
        previous_point = x2, y2
      else
        previous_point = time_values[index], val
      end
    end
 
    return chart_values
 
  end
 
  # Returns the point that lies on the slope between the previous_point and the 
  # next point in the chart.
  # This prevents breaks in the line running through the points in line type charts
  def calculate_point(chart_values, previous_point, time_values, previous_index)
 
    #find the next value that is not nil
    chart_values.each_with_index do |next_set