Most people who have their own small business end up giving a lot of their free (un-paid, that is) time to a task they were never formally trained to do: bookkeeping. Some people are naturally good at it and find it a nice change of scenery from their main business, others hate it with a passion. Personally, I fluctuate. One day I find myself leaving the office with a great sense of satisfaction, knowing that I just printed my own paychecks and closed out another month's worth of work in such a way that I won't be scrambling come tax time. Another morning might find me, smoke billowing out my ears, cursing a blue streak about an entire morning of work lost to bitch-slapping Quickbooks into submission (or QB bitch-slapping me, which is more often the case). Lately, the thing that's been making bookkeeping a real pain in the ass for me is the mundane, everyday task of keeping count of my hours.
When I switched to Quickbooks early this year for all of my bookkeeping, I also started doing invoices in Quickbooks. Previously, I had invoiced by hand, keeping count of my hours in a text file. To make invoice generation as simple as possible in QB, I also tried using the weekly timesheet feature. Over the last few months I've come to hate this feature.
To use it, I have to interrupt my workflow, go to another window, do a bunch of slowpoke clicking and key-poking.
That sort of interruption is costly for me, especially since, as a contractor, I try to keep track of my hours as exactly as possible. If I have to switch off a project for a five minute phone call, I don't want the client to pay for that. Seriously. I want to be able to take those five minute phone calls without having to spend another five minutes inside Quickbooks recording the fact that I stopped work for 5 minutes.
So today I wrote a little program that lets me keep my hours the way I like, in a text file, while making it very easy to do a monthly (or weekly or bi-monthly) invoice in QuickBooks.
Here's how it works.
1. I keep a little text file in ~/.hours/hours.txt. It's format is something like this:
Monday 7/2 # Bug-fixing and feature X
9:04 - 11:37 # Destroyed bugs 217, 381, and 113
12:02 - 6:23 # Implemented the hell out of feature X
Tuesday 7/3 # Unit tests
9:03 - 9:07 # Sent quick email
3:23 - 5:30 # Wrote some unit tests for feature Z
... and so on
I like to invoice monthly, so make a new one of these every month, and archive old ones as I enter a month.
2. Whenever I need to find out how much to invoice, or if I'm curious about whether I've been slacking this month, I run my little hours calculator program: Hours Summary
I can even give a rate to see how much money I've earned: Hours Summary with Rates
Before I took the morning off to write this little application, I did play around with the many time clock apps available. None of them ever really fit into my work flow, as a programmer. It sounds so silly, but I was always frustrated with having to leave my editor to record a new couple of minutes or hours of work performed.
With this text-based approach, I just keep a link to my current hours.txt file in my project (whether it's in TextMate or IntelliJ or Vim). Then I just tab over to that file, enter in a line and forget about it: How I record my hours
If you want to experiment with keeping your hours this way, again, here's the program. (If you find this useful, please drop me a line!)
#!/usr/local/bin/ruby
# == Synopsis
# Reads my silly timesheet format and spits out totals for days, so I can enter
# them into Quickbooks easily.
#
# == Usage
# ruby hours_calculator.rb [ -h | --help ] [ -f | --file hoursfile ] [ -r | --rate rate]
#
# hoursfile::
# A text file containing entries for hours worked during one or many days. In
# general, the format of these entries
# looks like this:
# Tuesday 6/26 # Big day
# 9:06 - 11:56 # Worked on XYZ component
# 1:12 - 2:29 # Wrote some customer support emails
# 2:55 - 3:52 # Twirled on my head juggling flaming torches yodeling yankee doodle
# 4:00 - 5:17
#
# Entries are separated by blank lines. If you don't separate days with lines,
# then multiple days will be processed as one.
#
# rate::
# If you provide a rate, then the sum of hours calculated will also be
# displayed as an invoice total.
require 'optparse'
require 'rdoc/usage'
require 'time'
class String #:nodoc:
def blank?
empty? || strip.empty?
end
end
class Numeric #:nodoc:
def duration(dec = false)
if dec
h = (self.to_f/3600.0)
sprintf "%.2f", h
else
h = (self/3600).to_i
m = ((self%3600)/60).to_i
sprintf "%d:%02d", h, m
end
end
end
class Day
attr_accessor :name, :duration, :slices, :comment, :date, :length
DAY_HEADER = /(\w+\s+\d+\/\d+.*?)(#*.*)/
def initialize(line)
md = DAY_HEADER.match(line)
raise NotDayLine if md.nil?
@date = Time.parse(md[1])
@comment = md[2]
if @comment
@comment.strip!
@comment.gsub!('#', '')
end
@slices = []
@length = 0
end
def <<(slice)
@slices << slice
@length += slice.length
end
def duration
@length.duration
end
class << self
def is_day_line?(line)
return DAY_HEADER.match(line)
end
end
end
class Slice
attr_reader :start_time, :end_time, :length, :duration, :duration_pp, :comment
SLICE = /(\d+:\d+\s*-\s*\d+:\d+.*?)(#*.*)/
def initialize(line)
md = SLICE.match(line)
raise NotSliceLine if md.nil?
@comment = md[2]
if @comment
@comment.strip!
@comment.gsub!('#', '')
end
st, et = md[1].split('-')
@start_time = Time.parse(st)
@end_time = Time.parse(et)
if @end_time < @start_time
@start_time = @start_time - (12*60*60)
end
@length = @end_time - @start_time
end
def duration
return @length.duration
end
class << self
def is_slice_line?(line)
return SLICE.match(line)
end
end
end
class TimeReport
attr_accessor :days, :rate
def initialize(days, rate = nil)
@days = days
@rate = rate
end
def to_s
s = "+--------------------+----------+--------------------------------------------------+\n"
s << "| Date | Hours | Comments |\n"
s << "+--------------------+----------+--------------------------------------------------+\n"
days.each do |day|
date = day.date.strftime(" %b %d (%A)").ljust(20)
hours = day.duration.rjust(9) + ' '
comment = day.comment[0..45]
comment << '...' if day.comment.length > 45
comment = comment.ljust(50)
s << "|#{date}|#{hours}|#{comment}|\n"
end
total_time = days.inject(0) { |sum, d| sum + d.length}
total = " #{total_time.duration.rjust(9)} over #{days.size} days (that's #{total_time.duration(true)})".ljust(60)
s << "+--------------------+-------------------------------------------------------------+\n"
s << "| TOTAL HOURS |#{total} |\n"
s << "+--------------------+-------------------------------------------------------------+\n"
if rate
total_dollars = (total_time/3600.0) * rate
total = sprintf("$%.2f", total_dollars).ljust(60)
s << "| TOTAL MONEY | #{total}|\n"
s << "+--------------------+-------------------------------------------------------------+\n"
end
s
end
def comments
s = ''
days.each do |day|
s << day.comment << '; ' unless day.comment.nil? or day.comment.blank?
day.slices.each {|slice| s << slice.comment << '; ' unless slice.comment.nil? or slice.comment.blank? }
end
s
end
end
fname = '~/.hours/hours.txt'
rate = nil
opts = OptionParser.new
opts.on("-h", "--help") { RDoc::usage }
opts.on("-f", "--fmt HOURSFILE") {|str| fname = str }
opts.on("-r", "--rate RATE") {|str| rate = str.to_f }
opts.parse(ARGV) rescue RDoc::usage('usage')
fname = File.expand_path(fname)
puts "Parsing file: #{fname}"
days = []
current_day = nil
File.open(fname) do |f|
f.each do |line|
next if line.blank?
if(Day::is_day_line?(line))
current_day = Day.new(line)
days << current_day
elsif(Slice::is_slice_line?(line))
slice = Slice.new(line)
current_day << slice
else
# puts "skipping junk: #{line}"
end
end
end
tr = TimeReport.new(days, rate)
puts tr.to_s
Friday, January 11, 2008
Friday, November 23, 2007
In-place file upload with Ruby on Rails
My friends often asked me how to upload file using AJAX, and usually they got answer “in no way”. Correct answer, but what if I need to upload file without full page reloading? And, of course, I want to use RJS in this case. Here I’ll explain what to do to get effect very similar to AJAX file upload (btw, Gmail uses this technique).
First of all, do you know, that form element has attribute target? If you specify it your form will be submitted into frame with name entered in target attribute. Of course, this frame can be iframe, and it can be hidden! Look at the following chunk of HTML code:
When you click the “Submit” button, form will be submitted to hidden iframe and controller’s action will be called. But resulting RJS will be returned into the hidden frame! We need to get full respond and execute it in context of whole page (parent window for our frame). There is one interesting technique exists: http://developer.apple.com/internet/webcontent/iframe.html, and it’s implementation as Ruby on Rails plugin here. Example of using:
class TestController < ActionController::Base
def upload_action
# Do stuff with params[:uploaded_file]
responds_to_parent do
render :update do |page|
page.replace_html 'upload_form', :partial => 'upload_form'
end
end
end
end
It’s simple, right?
First of all, do you know, that form element has attribute target? If you specify it your form will be submitted into frame with name entered in target attribute. Of course, this frame can be iframe, and it can be hidden! Look at the following chunk of HTML code:
When you click the “Submit” button, form will be submitted to hidden iframe and controller’s action will be called. But resulting RJS will be returned into the hidden frame! We need to get full respond and execute it in context of whole page (parent window for our frame). There is one interesting technique exists: http://developer.apple.com/internet/webcontent/iframe.html, and it’s implementation as Ruby on Rails plugin here. Example of using:
class TestController < ActionController::Base
def upload_action
# Do stuff with params[:uploaded_file]
responds_to_parent do
render :update do |page|
page.replace_html 'upload_form', :partial => 'upload_form'
end
end
end
end
It’s simple, right?
Easy PDF Generation with Ruby, Rails, and HTMLDOC
For a recent project we needed (and wanted) a simple solution to generate PDF files. Ideally, the solution would use HTML for the general layout and design of the generated PDF, working just like a normal view in Rails.
After testing a number of potential PDF solutions we came across a neat little library called HTMLDOC. What it does is take basic HTML and converts it to PDF, among many other output formats.
For the situation and solution we wanted it does a great job, especially for the price. To make it even easier to use, there is a also a Ruby HTMLDOC Gem to use along with it. Score!
To generate the PDF files we used plain old HTML, something we were familiar with. Exactly like creating a normal rhtml view.
Below follows our experience installing and using HTMLDOC to get PDF file generation out of our Rails application. This has been tested and used on Linux and MacOS X 10.4.9.
Note: You will need the proper tools installed to compile HTMLDOC. On MacOS X, that usually consists of installing the developer tools.
1. Installing HTMLDOC
The first thing we need to do is get HTMLDOC downloaded, compiled, and installed. Copy and past the following in your console:
curl -O http://ftp.easysw.com/pub/htmldoc/snapshots/htmldoc-1.9.x-r1521.tar.gz
tar zxvf htmldoc-1.9.x-r1521.tar.gz
cd htmldoc-1.9.x-r1521
./configure --prefix=/usr/local
make
sudo make install
2. Install the HTMLDOC Gem
Now that we have the HTMLDOC application ready to go, we want to install the HTMLDOC Ruby Gem to interface HTMLDOC with our Rails application:
sudo gem install htmldoc
3. Configuring Your Application
Next we need to configure our application. Open up your config/environment.rb file and add the following (to the end):
Mime::Type.register 'application/pdf', :pdf
require 'htmldoc'
Note: There is a way to make Rails handle the ‘.pdf’ extension format, but when we tried it, it kept asking us to download a file no matter what format we requested on every page. After many attempts at trying to rectify the issue, we eventually decided on the following solution:
4. PDF Renderer
We also added a method to our app/controllers/application.rb file to help DRY up the PDF generation, sort of like the render methods already included in Rails:
def render_to_pdf(options = nil)
data = render_to_string(options)
pdf = PDF::HTMLDoc.new
pdf.set_option :bodycolor, :white
pdf.set_option :toc, false
pdf.set_option :portrait, true
pdf.set_option :links, false
pdf.set_option :webpage, true
pdf.set_option :left, '2cm'
pdf.set_option :right, '2cm'
pdf << data
pdf.generate
end
Just pass it the same options you would pass render. Check the HTMLDOC Gem rdoc page for more options and configurations.
Example
Here is an example controller method:
def index
@items = Item.find(:all)
respond_to do |format|
format.html # index.html
format.xml { head :ok }
format.pdf { send_data render_to_pdf({ :action => 'index.rpdf', :layout => 'pdf_report' }) }
end
end
Pretty typical Rails, no? We tell it to explicitly use that action/view and to use a different layout file.
Now an example of a view:
Maybe it’s just me, but that sure beats using the examples using the PDF Writer plugin. At least from what I have seen.
Finally, to generate a link to the PDF, assuming you are using restful routes:
<%= link_to 'PDF', formatted_items_path(:pdf) %>
HTMLDOC may not be perfect, but I found it’s ease of use to generate nicely formatted PDF files far outweighed it’s limitations. I hope you found this useful.
After testing a number of potential PDF solutions we came across a neat little library called HTMLDOC. What it does is take basic HTML and converts it to PDF, among many other output formats.
For the situation and solution we wanted it does a great job, especially for the price. To make it even easier to use, there is a also a Ruby HTMLDOC Gem to use along with it. Score!
To generate the PDF files we used plain old HTML, something we were familiar with. Exactly like creating a normal rhtml view.
Below follows our experience installing and using HTMLDOC to get PDF file generation out of our Rails application. This has been tested and used on Linux and MacOS X 10.4.9.
Note: You will need the proper tools installed to compile HTMLDOC. On MacOS X, that usually consists of installing the developer tools.
1. Installing HTMLDOC
The first thing we need to do is get HTMLDOC downloaded, compiled, and installed. Copy and past the following in your console:
curl -O http://ftp.easysw.com/pub/htmldoc/snapshots/htmldoc-1.9.x-r1521.tar.gz
tar zxvf htmldoc-1.9.x-r1521.tar.gz
cd htmldoc-1.9.x-r1521
./configure --prefix=/usr/local
make
sudo make install
2. Install the HTMLDOC Gem
Now that we have the HTMLDOC application ready to go, we want to install the HTMLDOC Ruby Gem to interface HTMLDOC with our Rails application:
sudo gem install htmldoc
3. Configuring Your Application
Next we need to configure our application. Open up your config/environment.rb file and add the following (to the end):
Mime::Type.register 'application/pdf', :pdf
require 'htmldoc'
Note: There is a way to make Rails handle the ‘.pdf’ extension format, but when we tried it, it kept asking us to download a file no matter what format we requested on every page. After many attempts at trying to rectify the issue, we eventually decided on the following solution:
4. PDF Renderer
We also added a method to our app/controllers/application.rb file to help DRY up the PDF generation, sort of like the render methods already included in Rails:
def render_to_pdf(options = nil)
data = render_to_string(options)
pdf = PDF::HTMLDoc.new
pdf.set_option :bodycolor, :white
pdf.set_option :toc, false
pdf.set_option :portrait, true
pdf.set_option :links, false
pdf.set_option :webpage, true
pdf.set_option :left, '2cm'
pdf.set_option :right, '2cm'
pdf << data
pdf.generate
end
Just pass it the same options you would pass render. Check the HTMLDOC Gem rdoc page for more options and configurations.
Example
Here is an example controller method:
def index
@items = Item.find(:all)
respond_to do |format|
format.html # index.html
format.xml { head :ok }
format.pdf { send_data render_to_pdf({ :action => 'index.rpdf', :layout => 'pdf_report' }) }
end
end
Pretty typical Rails, no? We tell it to explicitly use that action/view and to use a different layout file.
Now an example of a view:
Showing: <%= pluralize(@items.size, 'item') %>
| Field1 | Field1 | Field1 |
|---|---|---|
| <%= item.field1 %> | <%= item.field1 %> | <%= item.field1 %> |
Maybe it’s just me, but that sure beats using the examples using the PDF Writer plugin. At least from what I have seen.
Finally, to generate a link to the PDF, assuming you are using restful routes:
<%= link_to 'PDF', formatted_items_path(:pdf) %>
HTMLDOC may not be perfect, but I found it’s ease of use to generate nicely formatted PDF files far outweighed it’s limitations. I hope you found this useful.
Capistrano - Deploying Your Rails Application
WHY USE CAPISTRANO? : Using Capistrano in conjunction with a Subversion repository makes deploying and updating your production application much easier and reliable. There is a little bit of setup involved, but once you have everything in place updating your live app is as simple as "$ cap deploy"!
1. Install Capistrano
The first step is to get the latest version of Capistrano with all its dependencies ("-y" is equivalent to "--include-dependencies") onto your local/development machine:
gem install -y capistrano
1.5. Setup SVN Repository
Once you have installed Capistrano and its dependencies, you will need a Subversion repository populated at least with a skeleton Rails app.
2. Setup your Rails application to work with Capistrano
Once you have checked out your code from the svn repository, change to the root of the Rails app on your development machine and run:
capify .
# This is the output
[add] writing `./Capfile'
[add] writing `./config/deploy.rb'
[done] capified!
Capistrano created two files: /config/deploy.rb and Capfile.
(Note: /config/deploy.rb might have been skipped if you already had a deploy.rb file in your app.)
The config/deploy.rb file will contain your application variables used to deploy your app, while the Capfile will contain your deployment tasks.
/config/deploy.rb
Add the following variables and values to your deploy file.
If you are upgrading from Capistrano 1.x you might not need to add anything.
set :application, "your_application_name" # Can be whatever you want, I use the project name from my SVN repository
set :domain, "your_url.com" # The URL for your app
set :user, "username" # Your username
set :repository, "svn+ssh://#{user}@#{domain}/home/#{user}/svn/#{application}/trunk" # The repository location for svn+ssh access
# set :repository, "http://svn.#{domain}/svn/#{application}/trunk"
# The repository location for http access
set :use_sudo, false
#users don't have sudo access
set :deploy_to, "/home/#{user}/apps/#{application}"
# Where on the server your app will be deployed
set :deploy_via, :checkout
# For this tutorial, svn checkout will be the deployment method
set :chmod755, "app config db lib public vendor script script/* public/disp*"
# Some files that will need proper permissions
# set :mongrel_port, "4444" # Mongrel port
# set :mongrel_nodes, "4"
# Number of Mongrel instances for those with multiple Mongrels
default_run_options[:pty] = true
# Cap won't work on windows without the above line, see
# http://groups.google.com/group/capistrano/browse_thread/thread/13b029f75b61c09d
# Its OK to leave it true for Linux/Mac
ssh_options[:keys] = %w(/Path/To/id_rsa)# If you are using ssh_keys
role :app, domain
role :web, domain
role :db, domain, :primary => true
Capfile
Add the following lines to the Capfile. If you are using Mongrel, make sure the namespace for FCGI and Mongrel Cluster of commented out or deleted.
load 'deploy' if respond_to?(:namespace) # cap2 differentiator
load 'config/deploy'
# ========================
# For FCGI Apps
# ========================
# NB: running the following :start task will delete your main public_html directory.
# So don't use these commands if you have existing sites in here.
namespace :deploy do
task :start, :roles => :app do
run "rm -rf /home/#{user}/public_html;ln -s #{current_path}/public /home/#{user}/public_html"
end
task :restart, :roles => :app do
run "#{current_path}/script/process/reaper --dispatcher=dispatch.fcgi"
run "cd #{current_path} && chmod 755 #{chmod755}"
end
end
# ========================
# For Mongrel Apps
# ========================
# namespace :deploy do
#
# task :start, :roles => :app do
# run "rm -rf /home/#{user}/public_html;ln -s #{current_path}/public /home/#{user}/public_html"
# run "cd #{current_path} && mongrel_rails start -e production -p #{mongrel_port} -d"
# end
#
# task :restart, :roles => :app do
# run "cd #{current_path} && mongrel_rails restart"
# run "cd #{current_path} && chmod 755 #{chmod755}"
# end
#
# end
# ========================
# For Mongrel Cluster Apps
# ========================
# namespace :deploy do
#
# task :start, :roles => :app do
# run "cd #{current_path} && mongrel_rails cluster::configure -e production -p #{mongrel_port}0 -N #{mongrel_nodes} -c #{current_path} --user #{user} --group #{user}"
# run "cd #{current_path} && mongrel_rails cluster::start"
# run "rm -rf /home/#{user}/public_html;ln -s #{current_path}/public /home/#{user}/public_html"
# run "mkdir -p #{deploy_to}/shared/config"
# run "mv #{current_path}/config/mongrel_cluster.yml #{deploy_to}/shared/config/mongrel_cluster.yml"
# run "ln -s #{deploy_to}/shared/config/mongrel_cluster.yml #{current_path}/config/mongrel_cluster.yml"
# end
#
# task :restart, :roles => :app do
# run "ln -s #{deploy_to}/shared/config/mongrel_cluster.yml #{current_path}/config/mongrel_cluster.yml"
# run "cd #{current_path} && mongrel_rails cluster::restart"
# run "cd #{current_path} && chmod 755 #{chmod755}"
# end
#
# end
3. Commit Application to SVN
Now that you have your deploy.rb file and Capfile configured, you need to make sure that you have committed the most recent changes of your app to your SVN repository before attempting any deployment.
4. Deploy Application
With your deploy.rb file and Capfile configured and committed to Subversion, you can now deploy your app. If this is the first time deploying your app using Capistrano you will need to run the following from your Rails root on your development machine:
cap deploy:setup
This will merely create the directory structure in your Rails account, based upon the configuration you set in the :deploy_to variable.
Next issue:
cap deploy:cold
This will checkout a copy of your app and copy it to the deploy directory. If you are using Mongrel, or a Mongrel Cluster, they will be started. FCGI is always running on Rails servers so you don't need to start FCGI.
At this point your app will be deployed and will be live.
Verify that the following directories exist on the server:
~/apps//shared/log
~/apps//shared/pids
~/apps//shared/system
If they do not, you can go ahead and create them with mkdir -p. If they do not exist, you'll run into problems running the reaper.
As you make subsequent changes to your app you can deploy them using:
cap deploy
This will restart FCGI, Mongrel, or the Mongrel Cluster used to serve your app.
If you have migrations that need to be run then run:
cap deploy:migrations
Capistrano has a number of other tasks, and allows you to customize your own tasks. Running cap -T from your Rails root will give print a list of tasks and how to view the description of these tasks. As mentioned above, Capistrano also allows you to set different methods for deployment. If you would like to export, instead of checkout, your SVN repository, use SFTP, or even zip or tar your app, and copy it to the server, Capistrano can make it simple for you.
1. Install Capistrano
The first step is to get the latest version of Capistrano with all its dependencies ("-y" is equivalent to "--include-dependencies") onto your local/development machine:
gem install -y capistrano
1.5. Setup SVN Repository
Once you have installed Capistrano and its dependencies, you will need a Subversion repository populated at least with a skeleton Rails app.
2. Setup your Rails application to work with Capistrano
Once you have checked out your code from the svn repository, change to the root of the Rails app on your development machine and run:
capify .
# This is the output
[add] writing `./Capfile'
[add] writing `./config/deploy.rb'
[done] capified!
Capistrano created two files: /config/deploy.rb and Capfile.
(Note: /config/deploy.rb might have been skipped if you already had a deploy.rb file in your app.)
The config/deploy.rb file will contain your application variables used to deploy your app, while the Capfile will contain your deployment tasks.
/config/deploy.rb
Add the following variables and values to your deploy file.
If you are upgrading from Capistrano 1.x you might not need to add anything.
set :application, "your_application_name" # Can be whatever you want, I use the project name from my SVN repository
set :domain, "your_url.com" # The URL for your app
set :user, "username" # Your username
set :repository, "svn+ssh://#{user}@#{domain}/home/#{user}/svn/#{application}/trunk" # The repository location for svn+ssh access
# set :repository, "http://svn.#{domain}/svn/#{application}/trunk"
# The repository location for http access
set :use_sudo, false
#users don't have sudo access
set :deploy_to, "/home/#{user}/apps/#{application}"
# Where on the server your app will be deployed
set :deploy_via, :checkout
# For this tutorial, svn checkout will be the deployment method
set :chmod755, "app config db lib public vendor script script/* public/disp*"
# Some files that will need proper permissions
# set :mongrel_port, "4444" # Mongrel port
# set :mongrel_nodes, "4"
# Number of Mongrel instances for those with multiple Mongrels
default_run_options[:pty] = true
# Cap won't work on windows without the above line, see
# http://groups.google.com/group/capistrano/browse_thread/thread/13b029f75b61c09d
# Its OK to leave it true for Linux/Mac
ssh_options[:keys] = %w(/Path/To/id_rsa)# If you are using ssh_keys
role :app, domain
role :web, domain
role :db, domain, :primary => true
Capfile
Add the following lines to the Capfile. If you are using Mongrel, make sure the namespace for FCGI and Mongrel Cluster of commented out or deleted.
load 'deploy' if respond_to?(:namespace) # cap2 differentiator
load 'config/deploy'
# ========================
# For FCGI Apps
# ========================
# NB: running the following :start task will delete your main public_html directory.
# So don't use these commands if you have existing sites in here.
namespace :deploy do
task :start, :roles => :app do
run "rm -rf /home/#{user}/public_html;ln -s #{current_path}/public /home/#{user}/public_html"
end
task :restart, :roles => :app do
run "#{current_path}/script/process/reaper --dispatcher=dispatch.fcgi"
run "cd #{current_path} && chmod 755 #{chmod755}"
end
end
# ========================
# For Mongrel Apps
# ========================
# namespace :deploy do
#
# task :start, :roles => :app do
# run "rm -rf /home/#{user}/public_html;ln -s #{current_path}/public /home/#{user}/public_html"
# run "cd #{current_path} && mongrel_rails start -e production -p #{mongrel_port} -d"
# end
#
# task :restart, :roles => :app do
# run "cd #{current_path} && mongrel_rails restart"
# run "cd #{current_path} && chmod 755 #{chmod755}"
# end
#
# end
# ========================
# For Mongrel Cluster Apps
# ========================
# namespace :deploy do
#
# task :start, :roles => :app do
# run "cd #{current_path} && mongrel_rails cluster::configure -e production -p #{mongrel_port}0 -N #{mongrel_nodes} -c #{current_path} --user #{user} --group #{user}"
# run "cd #{current_path} && mongrel_rails cluster::start"
# run "rm -rf /home/#{user}/public_html;ln -s #{current_path}/public /home/#{user}/public_html"
# run "mkdir -p #{deploy_to}/shared/config"
# run "mv #{current_path}/config/mongrel_cluster.yml #{deploy_to}/shared/config/mongrel_cluster.yml"
# run "ln -s #{deploy_to}/shared/config/mongrel_cluster.yml #{current_path}/config/mongrel_cluster.yml"
# end
#
# task :restart, :roles => :app do
# run "ln -s #{deploy_to}/shared/config/mongrel_cluster.yml #{current_path}/config/mongrel_cluster.yml"
# run "cd #{current_path} && mongrel_rails cluster::restart"
# run "cd #{current_path} && chmod 755 #{chmod755}"
# end
#
# end
3. Commit Application to SVN
Now that you have your deploy.rb file and Capfile configured, you need to make sure that you have committed the most recent changes of your app to your SVN repository before attempting any deployment.
4. Deploy Application
With your deploy.rb file and Capfile configured and committed to Subversion, you can now deploy your app. If this is the first time deploying your app using Capistrano you will need to run the following from your Rails root on your development machine:
cap deploy:setup
This will merely create the directory structure in your Rails account, based upon the configuration you set in the :deploy_to variable.
Next issue:
cap deploy:cold
This will checkout a copy of your app and copy it to the deploy directory. If you are using Mongrel, or a Mongrel Cluster, they will be started. FCGI is always running on Rails servers so you don't need to start FCGI.
At this point your app will be deployed and will be live.
Verify that the following directories exist on the server:
~/apps/
~/apps/
~/apps/
If they do not, you can go ahead and create them with mkdir -p. If they do not exist, you'll run into problems running the reaper.
As you make subsequent changes to your app you can deploy them using:
cap deploy
This will restart FCGI, Mongrel, or the Mongrel Cluster used to serve your app.
If you have migrations that need to be run then run:
cap deploy:migrations
Capistrano has a number of other tasks, and allows you to customize your own tasks. Running cap -T from your Rails root will give print a list of tasks and how to view the description of these tasks. As mentioned above, Capistrano also allows you to set different methods for deployment. If you would like to export, instead of checkout, your SVN repository, use SFTP, or even zip or tar your app, and copy it to the server, Capistrano can make it simple for you.
Export Rails ActiveRecords to CSV
For a recent “enterprisey” project I’m working on, we had to offer a variety of CSV exports for many of the models in our system. Ruby’s FasterCSV library is great for raw parsing and generation of CSV data, so I used that as the basis for a quick and dirty system to easily provide customizable exports.
The main features are:
* Transform an array of exportable records into a whole CSV file. (By default, FasterCSV transforms arrays into a single CSV row.)
* Export a whole table my calling .to_csv on the ActiveRecord subclass.
* By default, export all of an ActiveRecord’s columns except for created_at and updated_at.
* Allow simple customization of exportable columns by overriding the export_columns method in your ActiveRecord class.
* Allow multiple CSV formats by conditionally branching inside the export_columns method depending on the format parameter.
* Allow complete customization of the export by overriding the to_row method. (I haven’t actually needed this much customization yet.)
The simplest example:
Address.to_csv
Customizing the columns included in the CSV:
class Address < ActiveRecord::Base
def export_columns(format = nil)
%w[city state postal_code]
end
end
Multiple output formats:
class Address < ActiveRecord::Base
def export_columns(format = nil)
case format
when :local
%w[street1 street2 city state postal_code]
else
%w[city state postal_code]
end
end
end
Here’s the code:
require "fastercsv"
class ActiveRecord::Base
def self.to_csv(*args)
find(:all).to_csv(*args)
end
def export_columns(format = nil)
self.class.content_columns.map(&:name) - ['created_at', 'updated_at']
end
def to_row(format = nil)
export_columns(format).map { |c| self.send(c) }
end
end
class Array
def to_csv(options = {})
if all? { |e| e.respond_to?(:to_row) }
header_row = first.export_columns(options[:format]).to_csv
content_rows = map { |e| e.to_row(options[:format]) }.map(&:to_csv)
([header_row] + content_rows).join
else
FasterCSV.generate_line(self, options)
end
end
end
The main features are:
* Transform an array of exportable records into a whole CSV file. (By default, FasterCSV transforms arrays into a single CSV row.)
* Export a whole table my calling .to_csv on the ActiveRecord subclass.
* By default, export all of an ActiveRecord’s columns except for created_at and updated_at.
* Allow simple customization of exportable columns by overriding the export_columns method in your ActiveRecord class.
* Allow multiple CSV formats by conditionally branching inside the export_columns method depending on the format parameter.
* Allow complete customization of the export by overriding the to_row method. (I haven’t actually needed this much customization yet.)
The simplest example:
Address.to_csv
Customizing the columns included in the CSV:
class Address < ActiveRecord::Base
def export_columns(format = nil)
%w[city state postal_code]
end
end
Multiple output formats:
class Address < ActiveRecord::Base
def export_columns(format = nil)
case format
when :local
%w[street1 street2 city state postal_code]
else
%w[city state postal_code]
end
end
end
Here’s the code:
require "fastercsv"
class ActiveRecord::Base
def self.to_csv(*args)
find(:all).to_csv(*args)
end
def export_columns(format = nil)
self.class.content_columns.map(&:name) - ['created_at', 'updated_at']
end
def to_row(format = nil)
export_columns(format).map { |c| self.send(c) }
end
end
class Array
def to_csv(options = {})
if all? { |e| e.respond_to?(:to_row) }
header_row = first.export_columns(options[:format]).to_csv
content_rows = map { |e| e.to_row(options[:format]) }.map(&:to_csv)
([header_row] + content_rows).join
else
FasterCSV.generate_line(self, options)
end
end
end
Quick Hack: List all of Your Rails Controllers and Models
Let’s say you’re working on a small team and need to divvy up your models and controllers to do a code sweep.
You want to place these in a Google Docs spreadsheet or something. The code:
files = []
Dir["app/controllers/*"].each do |f|
f = f.gsub('app/controllers/', '')
files << f
end
Dir["app/models/*"].each do |f|
f = f.gsub('app/models/', '')
files << f
end
files.each do |f|
puts f
end
Note: does not handle nested subdirectories within your app/models/ or app/controllers/ directories.
You want to place these in a Google Docs spreadsheet or something. The code:
files = []
Dir["app/controllers/*"].each do |f|
f = f.gsub('app/controllers/', '')
files << f
end
Dir["app/models/*"].each do |f|
f = f.gsub('app/models/', '')
files << f
end
files.each do |f|
puts f
end
Note: does not handle nested subdirectories within your app/models/ or app/controllers/ directories.
How to obtain the IP address of the current user
Web applications can receive requests directly, via a CGI process, through proxy servers, relayed from front-end web servers, and so on. This can complicate how you might find out where the request originated if you, for example, wanted to limit an online poll to one vote per IP address. Luckily, Rails consolidates most of the ways to get this info into a single convenience method on the request object for us.
The Convenience Method: #remote_ip
Without the request.remote_ip method, you'd have to look for specific headers that are used to carry this data in the HTTP request beyond the server where the actual client's connection was terminated.
Rails' request.remote_ip method is pretty smart: it looks for and parses the headers HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR and REMOTE_ADDR and parse the value which are commonly used for this purpose.
Web applications can receive requests directly, via a CGI process, through proxy servers, relayed from front-end web servers, and so on. This can complicate how you might find out where the request originated if you, for example, wanted to limit an online poll to one vote per IP address. Luckily, Rails consolidates most of the ways to get this info into a single convenience method on the request object for us.
The Convenience Method: #remote_ip
Without the request.remote_ip method, you'd have to look for specific headers that are used to carry this data in the HTTP request beyond the server where the actual client's connection was terminated.
Rails' request.remote_ip method is pretty smart: it looks for and parses the headers HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR and REMOTE_ADDR and parse the value which are commonly used for this purpose.
1. Your IP address is <%= @client_ip %>
Further Reading
The request.remote_ip method is documented in the Rails Framework rdocs.
The Convenience Method: #remote_ip
Without the request.remote_ip method, you'd have to look for specific headers that are used to carry this data in the HTTP request beyond the server where the actual client's connection was terminated.
Rails' request.remote_ip method is pretty smart: it looks for and parses the headers HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR and REMOTE_ADDR and parse the value which are commonly used for this purpose.
Web applications can receive requests directly, via a CGI process, through proxy servers, relayed from front-end web servers, and so on. This can complicate how you might find out where the request originated if you, for example, wanted to limit an online poll to one vote per IP address. Luckily, Rails consolidates most of the ways to get this info into a single convenience method on the request object for us.
The Convenience Method: #remote_ip
Without the request.remote_ip method, you'd have to look for specific headers that are used to carry this data in the HTTP request beyond the server where the actual client's connection was terminated.
Rails' request.remote_ip method is pretty smart: it looks for and parses the headers HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR and REMOTE_ADDR and parse the value which are commonly used for this purpose.
1. Your IP address is <%= @client_ip %>
Further Reading
The request.remote_ip method is documented in the Rails Framework rdocs.
Subscribe to:
Posts (Atom)