Apr 3

Passing around arguments in rake tasks

Category: Development,Rails

So I had the need to pass arguments from one rake task to another.  This is easy to do from the command line, but gets a bit tricking when its being called from within another rake task.  So here is the task that is being called:

  1 namespace :database do
  2   desc %{Dump DB to create restore data in case of error}
  3   task :dump, :tables, :needs  => [:setup] do |t, args|
  4     filename = "#{DB_NAME}#{'-'+args.tables.values.join('-') unless args.tables.nil?}-#{DB_DUMP_TIME}.sql.gz"
  5     puts "Dumping the database to '#{filename}'"
  6 
  7     cmd = ""
  8     cmd << "echo 'SET AUTOCOMMIT=0;'|gzip> #{filename};"
  9     cmd << "echo 'SET FOREIGN_KEY_CHECKS=0;'|gzip>> #{filename};"
 10     cmd << "mysqldump -u #{DB_USER} -p'#{DB_PASSWORD}' "
 11     cmd << "-h #{DB_HOST} #{DB_NAME} "
 12     cmd << "--tables #{args.tables.values.join(' ')} " unless args.tables.nil?
 13     cmd << "|gzip>> #{filename};"
 14     cmd << "echo 'SET FOREIGN_KEY_CHECKS=1;'|gzip>> #{filename};"
 15     cmd << "echo 'COMMIT;'|gzip>> #{filename};"
 16     cmd << "echo 'SET AUTOCOMMIT=1;'|gzip>> #{filename};"
 17 
 18     if system(cmd)
 19       puts "Successfully ran databse:dump to restore run:"
 20       puts "rake database:reload DB_DUMP_TIME='#{DB_DUMP_TIME}'"
 21     else
 22       puts "rake database:dump was unsuccessful"
 23     end
 24   end

So this task is used to dump the database for our app. I added the ability to pass in an optional hash to allow it to only dump the specified tables. Here are the important lines to note.

task :dump, :tables, :needs  => [:setup] do |t, args|

The “tables” argument specifies what arguments are being passed in. The “t” and “args” block defines the task and the arguments. Your “tables” hash is now available through “args” values.

Now when I call this task from within another rake task, I can do something like this.

tables = {:table1 => 'users', :table2 => 'roles'}
Rake::Task["database:dump"].invoke(tables)

Note, I define the hash outside of the invoke because it is used by two other tasks, but it could be directly passed to the task as an argument.

No comments

Nov 15

Ubuntu Intrepid Sound Juicer MP3 support

Category: Linux,Ubuntu

After upgrading to Intrepid Kaudiocreator was no longer available, so I decided to give Sound Juicer a try. When I went to the preferences to change the encoding to MP3, I found that it was missing. So I clicked edit and found that the option was there for MP3 output. Turns out the option will not show up until you install this package – gstreamer0.10-plugins-ugly-multiverse. So just type the following and your set.

sudo apt-get install gstreamer0.10-plugins-ugly-multiverse
1 comment

Nov 6

Rails, Time Zones, and Scheduling

Category: Development,Rails

The time zone support in rails has vastly improved since the integrated TZInfo into Rails 2.1. However if you have any scheduled tasks stored in your DB, you’ll notice it quickly breaks down. Here is a short and sweet explanation:
I have an app that delivers system notification to users at a specified time they set. I have a field in my “reminders” table called “deliver_at” which is a datetime field. The data is stored as a UTC timestamp. Everything works as expected until daylight savings time, or the user changes their time zone. This is because the “deliver_at” timestamp has no context of what offset was used when the record was created. So when daylight saving rolls around your messages are now delivered an hour off. Or if the user moves to a new timezone, it will be off by x hours. Here is the solution I came up with.
I added a field to the “reminders” table called “offset”.

add_column :reminders, :offset, :decimal, :precision =>; 4, :scale => 2, :null =>; false, :default => 0.0

This field is recorded when the record is first created. You can override the attribute using write_attribute and read_attribute, but I was already doing a seporate update call, so I just stuck it there. *Important, make sure you are setting the Time Zone on login.

self.update_attributes!(:delivered_at => Time.zone.now, :next_reminder_at => date, :offset => user_utc_offset)

Here is my user_utc_offset method:

def user_utc_offset
    TimeZone[self.user.timezone].utc_offset.to_f / 1.hour.to_f
end

I’m using floats because not all the offsets are whole numbers. Same reason I used a decimal field in the DB. Then when I calculate my next reminder delivery, I basically do this:

offset_adjustment = adjust_for_difference_in_offsets
		  case self.reminder.repeat
		      when "daily"
			  next_reminder = reminder.deliver_at + 1.day - offset_adjustment.hours
		      when "weekly"
			  next_reminder = reminder.deliver_at + 7.days - offset_adjustment.hours
		      when "monthly"
			  next_reminder = reminder.deliver_at + 1.month - offset_adjustment.hours
		   end

I have a case statement to calculate daily, weekly, and monthly reminders. Here is the method to calculate the offset difference.

def adjust_for_difference_in_offsets
    user_utc_offset - self.offset.to_f
  end

I use the above for the mailer, but we need to do the same for displaying the datetime stamp to the user. So I created a helper method that does like so:

def reminder_repeat_datetime(reminder)
    offset_adjustment = reminder.adjust_for_difference_in_offsets
    case reminder.repeat
      when "monthly"
        repeat = "#{reminder.deliver_at.strftime('%d')}th"
      when "weekly"
        repeat = "#{reminder.deliver_at.strftime('%A')}s"
      when "daily"
        repeat = "Daily"
      when "once"
        repeat = "#{reminder.deliver_at.strftime('%B %d, %Y')}"
    end
    "#{repeat} at #{(reminder.deliver_at - offset_adjustment.hours).strftime('%I:%M %p')}"
  end

A lot of the above is just pretty formatting of the datetime for the user. You could most likely just get away with something like the last line. That is called from my index view in my reminders controller like so:

<%= reminder_repeat_datetime(reminder) %>

So now no matter if the offset changes because of DST or the user changes their timezone, the offset will be calculated and applied to the datetime field.

No comments

Nov 4

My simple ruby backup

Category: Development,Linux

After using a Linux dev laptop for almost a year, I went back to a MacBook Pro for work. I had forgotten the pains of finding a good backup solution for a Mac. I needed something that would incrementally back up my work laptop and my home Linux box to the same external hard drive. After trying various solutions(Time Machine, CCC, iBackup, etc), they all seemed to either not work at all or hog up way too much room on the drive. So I decided to just write my own backup solution using Ruby and Rsync.

This initial release is very basic and only requires you have ruby and rsync installed. It supports Mac or Linux and can back up to any drive that supports hard links (ext2, ext3, hfs, hfs+). Future releases will include integration with s3 as well as Cron scheduling. For now I just wanted to share this with others having similar gripes.

Steps to getting up and running:
1. Download Ruby Backup Zip or Ruby Backup tarball.
2. Extract the contents of the above package.
3. Edit the backup_settings.yml.sample file and save it as backup_settings.yml
4. run the script via “ruby ruby_backup.rb” or “./ruby_backup.rb”

1 comment

Oct 23

Gem cache search depricated in boot.rb

Category: Development,Rails

I was sick of getting the following deprication warning running rubygems version 1.3.0 and a rails 1.2.6 project.

“Gem::SourceIndex#search support for String patterns is deprecated
./script/../config/boot.rb:20 is outdated”

So I made some modifications to the boot.rb file so that it checks for the rails gem similar to the rails 2.x apps.  The gem itself is still loaded in the 1.2 fashoin.  Here is a diff on the changes

20,29c20,25
<       rails_gem = Gem.cache.search('rails', "=#{rails_gem_version}.0").sort_by { |g| g.version.version }.last
< 
<       if rails_gem
<         gem "rails", "=#{rails_gem.version.version}"
<         require rails_gem.full_gem_path + '/lib/initializer'
<       else
<         STDERR.puts %(Cannot find gem for Rails =#{rails_gem_version}.0:
<     Install the missing gem with 'gem install -v=#{rails_gem_version} rails', or
<     change environment.rb to define RAILS_GEM_VERSION with your desired version.
<   )
---
>       begin
>         gem "rails", rails_gem_version
>         gem_dir = Gem.path.select{|p| File.directory?("#{p}/gems/rails-#{rails_gem_version}")}.first
>         require "#{gem_dir}/gems/rails-#{rails_gem_version}/lib/initializer"
>       rescue Gem::LoadError => load_error
>         $stderr.puts %(Missing the Rails #{rails_gem_version} gem. Please `gem install -v=#{rails_gem_version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)

Hope this helps with those annoying warnings.

1 comment

Oct 16

VIM tricks: comment block of code

Category: Development,Linux

A friend of mine (Shovan) was asking how to comment out a block of code using VIM. This can easily be done using the “Visual Block” feature of VIM. To enter visual block mode use “Ctrl + v”. At this point you can use “j” or “k” to highlight the lines in which you want to comment out. Then use “Shift + i” to enter insert mode. Type the respective comment character (# for ruby and bash, // for php), then hit “Esc” to exit insert mode.

No comments

Oct 9

Generate a Rails version specific project

Category: Development,Rails

If you have rails 2.x installed but need to generate a rails 1.x project, you can use the version tag as follows:

rails _1.2.6_ project

Just replace the 1.2.6 with whatever version you need, and make sure you use underscores before and after the version.

No comments

Sep 15

Ruby sum array values for currency

Category: Uncategorized

I found this handy trick to take an array built from some hash values and spit out a currency. Here is what I used it for. Inside my model, I built some test items.

def self.test_items
    [{:price => "5.50", :weight => 10, :item_description => "Test item 1"}, {:price => "7.75", :weight => 15, :item_description => "Test item 2"}, {:price => "9.95", :weight => 20, :item_description => "Test item 3"}]
end

I wanted to get a “Total Price” in the view, so I did the following. I created a method in my ApplicationHelper that looks like so

def total_for_cart(prices)
    number_to_currency(prices.inject(0){|sum, price| sum.to_f + price.to_f})
end

The above uses the Ruby inject method from the Enumerable class to simply add the float values. I’m also using the rails helper method number_to_currency for pretty formatting. Finally my view looks like this:

<p>Total: <%= total_for_cart(items.collect{|i| i[:price]}) %></p>

That simply collects the price from the items array and passes it to my helper method. Hope its helpful!

No comments

Sep 10

Ruby IRB Rails script/console tricks

Category: Development,Rails

I use script/console to play with any new features that I’m doing before they even make it into my app. I love using irb and script/console, but there were a few things that frustrated me.
1. Exit and reload the console when I make model changes
2. Command history was blown away on exit

So I decided to take some time to see if I could come up with solutions for the above. Luckily I found solutions for both of my problems.
Issue #1. After you make changes simply type “reload!” at the prompt to reload the app. This solved most of my problems, but still if I exit I lose all my previous commands.

Issue #2. Create the file ~/.irbrc to configure your irb settings. Include the following settings:

require 'irb/ext/save-history'
IRB.conf[:SAVE_HISTORY] = 200
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-history"

Voila, you now have a saved history just like a bash console. Use the “Up” and “Down” keys to browse your old history.

No comments

Sep 8

Ubuntu SVN cannot set LC_CTYPE locale

Category: Linux,Ubuntu

I ran into this error on a fresh Ubuntu Hardy server I set up hosting a SVN repository.  The error messages may look like the following:

 
svn: warning: cannot set LC_CTYPE locale
svn: warning: environment variable LANG is en_US.UTF-8
svn: warning: please check that your locale name is correct

This won’t cause any problems with the functionality of Subversion, but it gets annoying.  So the problem is that the LANG that is specified (in my case en_US.UTF-8) isn’t installed.  So, to install it do the following:

 
apt-get install language-pack-en-base

This will ask you if you want to install language-pack-en also, so say yes to the prompt.  Then you will be set.  If you language is something other than English “en”, then install the appropriate locale package.

1 comment

« Previous PageNext Page »