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.

Skydiving

Posted by chris olsen on January 25, 2008

On a recent trip to Mexico I went skydiving for the first time and it was most amazing thing. I can honestly say that if I was a heroin junkie I would give it all up for a few skydives.

Here’s the video

Sky Diving from chris olsen on Vimeo.

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_val, next_set_index|
      # have we found the next value -> x2, y2
      if next_set_val.nil? == false and next_set_index > previous_index
        x2 = time_values[next_set_index]
        y2 = next_set_val
 
        x1 = previous_point[0]
        y1 = previous_point[1]
 
        # slope
        m = (y2 - y1) / (x2 - x1)
 
        return time_values[previous_index], m.to_i + y1  # mx + b ~> m(1) + y1 seeing as x is one point ahead of the previous point
      end
    end
  end
 
  # creates an array filled with dates from the beginning to the end of the date_arr passed in.
  # This allows the graphing module to correctly scale the data based on when it was obtained
  def fill_timespan(date_arr)
 
    #determine the start and end date
    start_date = date_arr[0]
    end_date = date_arr[-1]
 
    #create an array with all the dates between the start and end
    timespan_arr = []
    temp_date = start_date
    #for each day
    while temp_date.jd <= end_date.jd
      timespan_arr << temp_date
      temp_date += 1  #add a day to the temp_date
    end
 
    return timespan_arr
  end
 
  # Creates an array that matches the times[] array in the same way
  # that the orig_times[] and values[] matched
  def match_values(times, orig_times, values)
    chart_values = []
    times.each do |t|
      #determine if the date exists in the data
      index = orig_times.index(t)
      unless index.nil?
        #obtain the corresponding value
        chart_values << values[index]
      else
        chart_values << nil
      end
    end
    chart_values
  end
 
end

There are other options out there, such as Gruff Graphs, that are also available for rails that you may want to check out as well. The good thing about the Gruff Graphs is that they are free, where as the XML/SWF graphs kinda require a licensing fee. The reason I say kinda is that without a license they work fine, but if a person clicks on a graph they will directed to the XML/SWF site. The good thing is that their licensing fees are not outrageous, and at the time of writing they were going for $45 US for a single application or $550 for unlimited usage. The current prices can be found here.

That wraps everything up. I hope this helps a few of you and feedback is always appreciated.

Rails setup on Linode

Posted by chris olsen on September 13, 2007

******
An updated version of this post has been made here.
******
I like Rails. I like its elegance. I like how adhering to the DRY principle is made easy and I like the way that I can look at code written by someone else and instantly understand what they were doing. With all this likeness going around you would think that I am on cloud 9. The truth is there is something that makes me a little nervous about it, and that is deployment.

So how do you deploy a Rails application? Not too long ago I was browsing the internet for the answer to this question and in almost every response there was the lack of an actual answer, and the ones that consisted of an answer were clearly wrong. The truth is that there is no one answer, and you just have to accept that.

A couple of days ago Bryan Liles put together a nice video run through of setting up a VPS at Linode and gave some very good instructions on how to do so from the start to the finish. Personally, I like having documented instructions to allow for easy later reference and that is what this is. All the instructions are shown below along with some extras in case you are lacking an SVN repository server.

Before you start you have to decide where you are deploying to. If you would like to deploy to a VPS at linode that’s cool, but if you would like to deploy to a local server that’s cool too. The only catch is that the local server either has to provide you with a VPS within it, or you are able to re-install a fresh linux distro on it.

If you are working with a local server you will have to download the distro. All the instructions within this article are for Debian 4.0 so that is what I would recommend. Download the iso file, burn it to a CD, pop it in to server, re-boot and install.

If you have decided to go with Linode, sign in, click on the Dashboard, then click on the “Deploy a Linux Distribution” link. Here you will be able to select a distro and set up the disk partitions. The setup should only take a couple minutes.

Whether you are working on a local or remote machine all the instructions consist of remotely logging into the server with ssh.

#linode machine
ssh root@ip_address
#local machine
ssh root_name@ip_address

Server

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

apt-get update
apt-get upgrade

If you are installing to a local machine you will have to install openssh.

apt-get install openssh-server

For a local server you will also want a static ip address for the server, so you will have to make a couple tweaks to the interfaces file. Make a backup of the file then open the networks file with your editor of choice.

cp /etc/network/interfaces /etc/network/interfaces.old
vi /etc/network/interfaces

The updates that you make will depend on how you have your network setup, but what is below will probably work for most people.

allow-hotplug eth0
iface eth0 inet static
address 192.168.1.202
network 192.168.1.0
gateway 192.168.1.1
broadcast 192.168.1.255
netmask 255.255.255.0

Then to ensure that ip address is updated restart the network

/etc/init.d/networking restart

We will also have to install a few other libraries. ***Write the password mysql asks you for down so you don’t forget it later. I know you aren’t supposed to write them down, but you have to trust me that it can save you a lot of headache later if you are overly creative with passwords and you suddenly can’t remember what you came up with later on in the setup.

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

We will also need the ruby gems. At the time of writing the below is the most recent, but you may want to check if a new release is available.

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

When I was setting things up the first time rails did not install properly when including it in the previous apt-get, so to keep on the safe side we will install it separately. If you get an error when running the following just run it again. This is a well known quirk, but is definitely not something that is overly obvious to someone who hasn’t seen it before.

gem install rails --include-dependencies

Let’s also install mongrel and the mongrel cluster.

gem install mongrel -y
gem install mongrel_cluster -y

To allow Capistrano to access the server on deploy grant permissions to mongrel (we will create this user in a bit). The export command is only called if you would like to use vi to make the updates. I believe Pico is the default.

export VISUAL=vi
visudo

Add the following line just under where root is granted all permissions. More information on this can be found here.

mongrel ALL=(ALL) NOPASSWD:ALL

Local

Let us first make sure that Capistrano v2.x 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

If you do not currently have an ssh key, you can create one with the commands below. If you would rather create an rsa key just replace the dsa with, you guessed it, rsa. Then we need to upload the public key to the server.

For for more info on the differences check this post out.

ssh-keygen -t dsa
scp ~/.ssh/id_dsa.pub root@ip_address:~/my_id

Now that the key is copied up onto the server we will make sure that it is in the proper folder. So back to the server we go. Can you now understand why accessing the server remotely is the best option?

Server

Since we earlier granted access to the mongrel user we really should create a user that goes by the name of mongrel. If you also set up Subversion on the server you will have to perform the following steps for each of the developers that will be working on the project (although there is other ways of granting access, but that is out of scope right now).

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

Time to setup a folder where all the subversion repositories will be kept.

** I am a little unsure of the last line below, but that is the only way that I could get things to work. The access denial is a common problem, but solutions that have worked for others would not work for me so this is what I’ve had to settle with for now.

mkdir /svn
mkdir /svn/project_name
svnadmin create /svn/my_first_project/trunk
chmod -R 777 /svn/project_name/trunk/db

Local

If this is a brand spankin’ new project we will have to create the root folder and check out the empty project from the server. The following will create a main project folder, then the svn command will create a trunk folder within it. We will then create a new project within the trunk folder by calling on the rails command and check in the required folders and contents.

cd ~/your_rails_project_dir
mkdir project_name
cd project_name
svn co svn+ssh://ip_address/svn/project_name/trunk trunk
rails trunk
svn add test/ app/ Rakefile db/ components/ vendor/ README Capfile script/ doc/ config/ lib/ public/

If you have an existing project that is very close to making you famous you will just have to checkout the latest version.

cd project_name
svn co svn+ssh://ip_address/svn/project_name/trunk trunk

It is now time to bring Capistrano into the picture.

cd project_name/trunk
capify .

We now have to make some adjustments to the deploy.rb file. Bryan provides a custom version of the file that will be used. So download it from here and replace the current one.

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

vi config/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 Brian was kind enough to provide them to us. So all we have to do is replace the current one on the server.

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

Server

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

Local

Now that everything that is checked in, and the server is setup, it is time to deploy. This is the part that I have yet to not get thrown some errors. Most of them are due to an incorrect password, a HostKeyMismatch, or permission issues. I thought I would give you a heads up on this so your heart doesn’t break like that first time you got stood up…I mean everyone has been stood up at least once…right?

cap deploy:setup deploy:cold deploy:migrate

If you are getting a HostKeyMismatch error, check if the .ssh folder and the .ssh/authenticated_keys file on the server in the /home/mongrel folder has the proper ownership. If they were created while not logged in as mongrel that is likely the problem. The following should fix that.

Server

chown -R mongrel:mongrel /home/mongrel/.ssh

I have also seen permission issues due to the mongrel user being unable to create the /var/rails folder. If that is the case we can create it ourselves, make the mongrel user the owner of it and ensure it is writable.

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

If you have gotten this far then congrats are in order. The last thing that has to be done is to ensure that the mongrel cluster will be started if the server is rebooted.

Server

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

Now let’s remove the public key that we uploaded earlier.

rm ~/my_id

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

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

cap deploy
# or
cap deploy deploy:migrate  # if any database updates were made

I have to give a huge thanks to Bryan Liles for putting the video together. Most of the steps on here are taken straight from the video with a couple of modifications where I had problems. Hope this will help a few other people become famous after deploying the next Facebook application.

5 things for productivity when working from home

Posted by chris olsen on August 26, 2007

Not to long ago I worked in my little cubicle just like many of you, but after a while I realized that I wanted to work on my own since I knew that is what I would enjoy the most. Most days at the office were the same, I would come in, grab a cup of coffee then get to work. I was able to clock many solid hours in a day. When I left my job to work on my own I was confident that my work ethics would make the transition a breeze…wow was I wrong. My productivity went down the toilet, mainly due to the fact that I no longer a boss to report to at the end of the day.

So here are 5 things that I found were crucial to getting things done in a day working from home:

Learn one thing at a time

I am the kind of person that always has new things that I want to learn, and with all this extra time I figured I could learn it all, and how wrong I was. I was learning little and spending a lot of time doing it. I was seeing no results as time passed and that was frustrating. Now that I revamped my approach to learning new things, I see dramatic results in a short time. This is not just great for my skill levels, but motivates me throughout the day.

Eliminate distractions

Distractions are all around us and we are so used to them that we don’t immediately see them for what they are, but think of them as necessities. I am referring to email, IM, phone, etc. At my office job all three of the mentioned items were a part of my work day. I had to keep email opened otherwise I could miss something important. IM was there to either ask someone work related questions or whether or not they wanted to go grab a coffee. The phone was right there to make sure that if anyone had a question for me I could answer it right away.

When working from home it didn’t take long to realize that these distractions were wasting a lot of time. I have read that it usually takes about 10-15 minutes for a person to get in the working mode and fully focus on a task, and if interrupted, can take another 10-15 minutes to get back in the game. With these costly transition times it became obvious that the distractions had to be better managed. Even though they are being labeled as distractions they are still required. I do have to keep in contact with clients and friends, but I find it best to schedule a couple of 15 minute blocks in the day where that is all I do, making it my new activity that has my focus. It is amazing at how much extra time in a day that you can get from this alone.

Say adios to multitasking

I remember job interviews where I was asked if I was a multitasker. Knowing they wanted to hear that I was, I would tell them exactly that. In truth I am anything but a multitasker and the same is true for most other people in the world. Sure I can do many things at once, but I will, 9 times out of 10, be more efficient if I am able to concentrate on one thing at a time.

A person has to take advantage of this and one good way of doing it is to work in time segments. Set a start time and for one hour and devote all your attention to it. You will get a lot done in this hour and when complete it is one more notch on your productivity post for the day.

Take advantage of downtime

I always have certain times in the day where I know I will get a lot done, and certain times where the opposite is true. Instead of packing myself full of caffeine to turn the downtimes into uptimes, I found it was best to perform the tasks that don’t require a lot of mental energy. Personally, I take my dog for a walk or run around 3:00 in the afternoon. This way I don’t lose any up-time, but am still able to get something important done.

Don’t set overly high goals for the day

Instead of putting 10 things on your daily to-do list and only getting 3 things done, doesn’t it make more sense to only put 3 things on the to-do list and getting them all done? Not only will this give you the feeling of a productive day, seeing as you are 3 for 3, but it will also require you to screen the items that are to be marked as to-do and eliminate time spend on lower priority tasks.

When writing this article I used everyone of the mentioned points, and the result, something that I hope will add extra time to your day, and at the very least, remind me what to do if I start wasting time again.

RubyOnRails: Forms 102

Posted by chris olsen on August 24, 2007

As I mentioned in a previous article (Forms 101), I had some difficulties coming to RubyOnRails from an asp.net background seeing as certain things that were non-existent in asp.net were rather important in Rails. I am referring to the the controls contained within the html form tags. There was only one set in asp.net that a person had to become familiar with to be able to make things happen. In Rails there is a few sets of methods that render out the desired controls and each set has a purpose.

If you are not familiar with the various form blocks that exist within the Rails framework I suggest you give the Forms 101 article a read first and then come back to this one. This topic is not a complex one, but when I was learning I could never find a single source of information that listed all the form control methods, so that is where this comes in handy.

There are common names used throughout all the various method types that you may or may not already be aware of. Below are a list of the common method names.

  • text_field
  • text_area
  • select
  • checkbox
  • radio_button

There are, to my knowledge, three types of methods that exist that are based on the above names.

The first one is to append the _tag suffix to the method name, ex. text_field_tag, checkbox_tag. Each of these methods requires one argument, the parameter’s name. In the example it should be noted that the id and name of the control match the parameter name.

<%= text_field_tag :first_name %>
 
#will render to the web browser as
<input type="text" id="first_name" name="first_name"></input>

The big difference with this method is that on the server side the form values are accessed directly from the params[] array, ie params[:first_name], params[:last_name]. Having the form values stored like this makes it easy to quickly access the values and pass them to a method call, ex. a login authentication method (Member.valid(params[:user_name], params[:password]))

The second type consists of names that match the method names that were listed above, ex text_field, checkbox, radio_button. This method requires two arguments, where the first is the model name and the second is the parameter name.

<%= text_field :member, :first_name %>
 
#will render to the web browser as
<input type="text" id="member_first_name" name="member[first_name]"></input>

The key things to note are the values of the id and name attributes of the input tag where the model name is now included in each. So what does this do? The name parameter allows the request object to hold all the form parameters in its own sub-array that can later be accessed through the params array, ex. params[:member][:first_name]. The benefit to this is that it makes it easy to fill a model with all the form values as shown below.

def create
	@member = Member.new(params[:member])
end

The last method type is more geared to be used within the form_for block. They are much like the previous type mentioned except they are called through the block’s parameter and only require one parameter name although the html output will be the same. The reason for this is the form_for’s block parameter that the text_field method is called from. This block parameter will create the corresponding form control that matches whatever value follows the form_for, ex. in this case :member.

<% form_for :member, :url => member_url() do |form| %>
form.text_field :first_name
<% end %>
 
#will render to the web browser as
<input type="text" id="member_first_name" name="member[first_name]"></input>

The main benefit of this type is that it prevents you from having to define the model that the form control is for for each form control, and less is better.

Now that you have a good idea of what each type is for, it is time for the key point. You can, for the most part, mix the the types of form methods that reside within the form tags. Although the form.text_field etc must only exist within a form_for, all the others can exist whenever and wherever. This then allows you to easily obtain data through the form that does not match the model without too much effort.

Just to top things off, below is a bunch of common form methods that a person should be familiar with, although I am sure there is a few that I am missing. Hopefully this may clear up thing for some, and serve as a reminder for others.

<% form_tag do  %>
 
	Text Field Tag:
	<%= text_field_tag :task, :title %>
 
	Text Field
	<%= text_field :task, :title, :size => 30  %>
	<br>
 
	Text Area:
	<%= text_area :task, :title, :rows => 5, :columns => 30 %>
	<br>
 
	Select with simple array:
	<%=
	options = [1,2,3,4,5,6,7,8,9]
	select :task, :points, options
	%>
	<br>
 
	Select with hash table:
	<%=
	options = {:one => "First", :two => "Second", :three => "Last"}
	select :task, :points, options
	%>
	<br>
 
	Select control bound to list of models:
	<%=
	#create some tasks
	if Task.count == 0
	["Learn RubyOnRails", "Create practice site", "Create real world site"].each do |title|
	Task.create(:title => title, :points => 10)
	end
	end
 
	tasks = Task.find :all
	#select control must be bound to list of 1x2 array or hash
	select :task, :points, tasks.map{ |t| [t.title, t.points] }, :prompt => "Select..."
	%>
	<br>
 
	Another select bound to a model:
	<%= collection_select(:task, :points, tasks, :id, :title) %>
	<br>
 
	Checkboxes:
	<%= check_box :task, :complete, {:style => "color: red; font-size: 20px;"}, "yes", "no" %> Is Complete (yes no)
	<%= check_box :task, :complete, {:style => "color: red; font-size: 20px;"} %> Is Complete (1 0)
	<br>
 
	Radio buttons:
	<%= radio_button :task, :complete, "complete" %> Complete
	<%= radio_button :task, :complete, "incomplete" %> InComplete
	<br>
 
	Date Select Helpers:
	<!-- http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000577 -->
	defaults to the current date:
	<%= date_select(:task, :completed_on) %>
	<br>
 
	Setting the min and max value within in the list:
	<%= date_select(:task, :completed_on, :start_year => 1970, :end_year => 2000) %>
	<br>
 
	Hidden Field(seriously there is on here):
	<%= hidden_field :task, :title %>
	<br>
 
	Password:
	<%= password_field :task, :title %>
	<br>
 
	Password Check:
	<%= password_field :task, :title_confirmation %>
	<br>
 
	File:
	<%= file_field :member, :picture %>
	<br>
 
	<%= submit_tag "Submit this form" %>
 
<% end %>

Floating Forms

Posted by chris olsen on August 23, 2007

Web applications have come a long way in the last couple years to the point where they are now beginning to replace some of the desktop applications that we all have come to love. Even though I am a web developer I never liked the way the applications were too jumpy, by that I mean that to perform simple tasks you had to select an item from a list that would direct you to a new page containing a form. After submitting the form you were then redirected back the list of items that you originally made the selection from. Not only was the application jumping from page to page to perform the task, but when directed back the initial page it required a complete update. This update would not be that bad, if you are working with a simple CRUD application, but if the page contains a lot of dynamic data from other tables the update could be somewhat costly if your site has high traffic.

When I used to develop primarily with asp.net, I created a web control that, through the use of some simple javascript and css, allowed for a div tag to float above the rest of the form and contain the form to perform the create/update operation. Unfortunately, asp.net seemed to lack the ability to, through the use of ajax, obtain the required form for the database table I wished to create/update. The only way that I was able to make things happen was to have a number of controls on the page, each containing the form for the various tables I wished to modify. Things got messy very quickly.

RubyOnRails makes this fairly simple to do even for a person without a lot javascript experience, and that my friends, is what what this article is all about. This article does assume some RubyOnRails experience. Although I will be going over the whole process I will not spend a lot of time explaining details that are unrelated to the creation of the floating windows.

First thing that we have to do is create our project.

Projects 510 $ rails FloatingWindow
  create
  create  app/controllers
  create  app/helpers
  create  app/models
  create  app/views/layouts
  create  config/environments
  create  components
  ...
  create  doc/README_FOR_APP
  create  log/server.log
  create  log/production.log
  create  log/development.log
  create  log/test.log

To keep things nice and simple we create a simple Task model with just enough fields to get the point across.

Projects 511 $ cd FloatingWindow/
FloatingWindow 512 $ script/generate scaffold_resource Task
  exists  app/models/
  exists  app/controllers/
  exists  app/helpers/
  create  app/views/tasks
  exists  test/functional/
  exists  test/unit/
  create  app/views/tasks/index.rhtml
  create  app/views/tasks/show.rhtml
  create  app/views/tasks/new.rhtml
  create  app/views/tasks/edit.rhtml
  create  app/views/layouts/tasks.rhtml
  create  public/stylesheets/scaffold.css
  create  app/models/task.rb
  create  app/controllers/tasks_controller.rb
  create  test/functional/tasks_controller_test.rb
  create  app/helpers/tasks_helper.rb
  create  test/unit/task_test.rb
  create  test/fixtures/tasks.yml
  create  db/migrate
  create  db/migrate/001_create_tasks.rb
  route  map.resources :tasks
FloatingWindow 513 $

Now add the following code to the db/migrate/001_create_tasks.rb file

class CreateTasks < ActiveRecord::Migration
  def self.up
    create_table :tasks do |t|
      t.column :name, :string
      t.column :created_at, :timestamp
      t.column :completed_at, :datetime
      t.column :points, :integer
    end
  end