Be inspired and inspiring.

Posts Tagged: ruby

Text

I’m working on a little command line tool for generating HTML photo galleries and needed to resize some images with RMagick. Unfortunately there were a shortage of good, concise examples of how to use it. So, for my own future reference and yours, here’s how to resize an image to maximum dimensions while preserving its aspect ratio. You can provide a width and height to resize_to_fit, or just one value as I am here.

require 'rmagick'

# Read first image from file
image = Magick::Image::read(image_path).first

# Resize image to maxium dimensions
image.resize_to_fit!(250)

# Write image to file system
image.write(thumb_path)

# Free image from memory
image.destroy!

In this example, we’re reading an image, resizing it, and then writing it to a new path. Do make sure you write it to a new path so that you don’t overwrite all your images. That would be sad.

Alternatively, you can use resize_to_fill to have the image fill the dimensions provided. This is great for generating square crops of your images.

image.resize_to_fill!(250)

The destroy method also turns out to be quite important. Because the Ruby garbage collector doesn’t know how much memory is being used inside of ImageMagick, the library underlying RMagick, it doesn’t garbage collect often enough. This can turn into a big problem if you’re resizing a large number of images in a loop. Fortunately it’s easy to address.

image.destroy!

Lastly, if you’re working with JPGs and would like to change the quality, you can pass a block to write.

image.write(thumb_path) { self.quality = 75 }

RMagick is great and does all kinds of neat stuff. Now that you’re up and running, take a look at the rest of the instance methods for more cool image manipulation goodness.

Text

I spend a lot of time parsing and cleaning up text files with regular expressions, and there’s a problem that comes up fairly often that used to really puzzle me. How do you replace something that requires complicated, multi-step logic?

Everyone who has used regular expressions is pretty familiar with doing something like this:

>> "5553331234".gsub(/(\d{3})(\d{3})(\d{4})/, '(\1) \2-\3')
=> "(555) 333-1234"

And truth be told, you can do a lot of good stuff with that simple concept. The problem is that it isn’t always so easy as replace “this” with “that”. Many times you want to inject some logic into the replacement process. Thankfully we can can pass blocks to the regular expression methods in Ruby (.NET offers something similar by passing a delegate to Regex.Replace).

This allows us to do some really complicated matching pretty easily. Last week I needed to reformat hundreds of SQL statements that NHibernate was generating. Here’s what some of them looked like:

exec sp_executesql N'INSERT INTO Actions (CreatedAt, Description,
IsReversible, Name, ObjectClass, UserTransactionID) VALUES (@p0,
@p1, @p2, @p3, @p4, @p5)',N'@p0 datetime,@p1 nvarchar(9),@p2 bit,@p3
nvarchar(12),@p4 nvarchar(15),@p5 int',@p0='2010-04-03
00:36:25.1100000',@p1=N'logged
in',@p2=0,@p3=N'Authenticate',@p4=N'UsersController',@p5=5589

exec sp_executesql N'INSERT INTO UserTransactions (CreatedAt,
Description, UserID) VALUES (@p0, @p1, @p2)',N'@p0 datetime,@p1
nvarchar(30),@p2 int',@p0='2010-04-03 00:37:26.9600000',@p1=N'viewed
applicant Michael Smith',@p2=1

I needed these formatted as simple SQL statement without the exec sp_executesql with parameters nonsense. I needed to replace these:

(@p0, @p1, @p2)

With these:

@p0='2010-04-03 00:37:26.9600000', @p1=N'viewed applicant Michael
Smith',@p2=1

So it would look like this:

('2010-04-03 00:37:26.9600000', 'viewed applicant Michael Smith', 1)

Shew. As you can imagine a simple gsub like our first example wasn’t going to cut it. But, by using a block we can easily execute additional logic on each match. Here’s the code:

# Match each SQL statement that starts with exec sp_executesql
sql.gsub!(/exec sp_executesql.*?$/) do |match|
  # collect the params from the end of the statement into an array
  values = match.scan(/(@p\d)=N?(.*?)(?=(,@p\d)|$)/)

  # Remove the beginning and end of the statement
  match.gsub!(/exec sp_executesql N'/, "").gsub!(/',N'@p0.*?$/, "")

  # Replace each of the parameters with its corresponding value
  values.each { |v| match.gsub!(v[0], v[1]) }

  # Return our clean SQL statement
  match
end

You can see here that I’m using a block with gsub instead of providing a replacement string. This gives us a match object for each matching SQL statement where we can make additional replacements, and our statements end up looking like this:

INSERT INTO Actions (CreatedAt, Description, IsReversible, Name,
ObjectClass, UserTransactionID) VALUES ('2010-04-03
00:36:25.1100000', 'logged in', 0, 'Authenticate',
'UsersController', 5589)

INSERT INTO UserTransactions (CreatedAt, Description, UserID)
VALUES ('2010-04-03 00:37:26.9600000', 'viewed applicant Michael
Smith', 1)

While some additional explanation about what’s happening in the block would probably be useful, the point here is that this kind of complicated logic is possible and with very little code and hassle.

If your regular expressions are seeming impossible or overly complicated, see if you can make it happen with the regex 2 (or 3 or 4) step instead.

Text

Today I was tasked with comparing a spreadsheet of carefully verified data to what’s now on our production servers. After spending less than a minute staring at two spreadsheets in Excel and knowing I didn’t want to compare lots of rows of data with my eyes and scroll wheel finger, I decided it was time to get Ruby on the job. Here’s the outcome:

require 'rubygems'
require 'fastercsv'

def normalize(row)
  row[2].sub!(/^0+/, "") if row[2]
  row[3].sub!(/^0+/, "") if row[3]
  
  return row
end

old_rows = FasterCSV.read("old.csv")
new_rows = FasterCSV.read("new.csv")

old_rows.each { |row| normalize(row) }
new_rows.each { |row| normalize(row) }

additions = new_rows - old_rows
deletions = old_rows - new_rows

puts "Additions (#{additions.size})"

additions.each { |a| puts a.inspect }

FasterCSV.open("additions.csv", "w") do |csv|
  additions.each { |a| csv << a }
end

puts

puts "Deletions (#{deletions.size})"

deletions.each { |d| puts d.inspect }

FasterCSV.open("deletions.csv", "w") do |csv|
  deletions.each { |d| csv << d }
end

I’m using FasterCSV because it’s, um, faster, so make sure you have that gem installed. The script outputs two CSV files. One for additions, items that exist in the new file, but not the old one. And one for deletions, items that exist in the old file but not the new one. I have the normalize method in there to clean up some data. In my case I had leading zeros in some places but not others, so I removed those there.

So there you have it: tedium and eye strain avoided!