New SD.rb Talks Posted: Simple Sidebar Plugin & Ajax CSS Star Rating with ActsAsRateable

Plugins, Ruby on Rails No Comments »

My podcasts / vidcasts at SD Ruby have been posted to the podcast section.

Simple Sidebar Plugin

How to use Simple Sidebar plugin to DRY up sidebar content in applications.

SD.rb Vidcast - Simple Sidebar Plugin

Related Blog Posts:

http://blog.aisleten.com/2007/06/03/simplesidebar-if-you-have-sidebars-you-need-this-plugin/

Ajax CSS Star Rating with ActsAsRateable

How to build an Ajax-powered, CSS star rater using the ActsAsRateable plugin and Komodo Media’s CSS Star Rating Redux technique.

SD.rb Vidcast - Ajax CSS Star Rating

Related Blog Posts:

http://blog.aisleten.com/2007/05/03/ajax-css-star-rating-with-acts_as_rateable/
http://blog.aisleten.com/2007/05/17/find-the-top-5-highest-rated-objects-with-acts_as_rateable/

At this month’s meeting we’re going to be having our first Rails Roundtable so come and check it out.
SD.rb December Meeting Schedule

Atlanta Ruby Users Group

Ruby on Rails No Comments »

The Atlanta Ruby Users Group (ATLRUG) recently moved their meetings to a much friendlier location for me. Rather than heading up into the forbidden north of Atlanta, it is now being held in the convenient location of Tech Square in Midtown. Of course this probably means nothing to anyone reading this from outside Atlanta.

In any case, it was a really great group, and they were very welcoming to new people, of which there were very many. Following the free pizza and pre-meeting chit-chat, we saw two presentations.

Metaprogramming

First up was Stephen Touset with a talk on metaprogramming. This is what is going on behind the scenes with all the method_missing stuff. Probably the most well known example would be the dynamic finders (ie. “find_by_email_and_username”). Stephen delved deep into metaclasses, and it was fairly confusing. I’ve dealt with class_eval and such before, but mainly from a “copy someone else’s stuff and modify to suit” sort of way. As I said during the meetup, “It’s possible to do metaprogramming without knowing what you’re doing.”

Nginx

Mark Percival followed Stephen with a presentation on Nginx, a fast little web server. I’ve always been a fan of Apache, but mainly because I felt safe with it. Even though it was a pain to configure, I knew that if I ever had to ask “How do I do X on the web?” then the answer would be in Apache language.

However, it never occurred to me that it might be overkill. As Mark explained, if all your Apache server is doing is proxying to mongrel, then it’s a big hammer for a tiny problem. The memory footprint is fairly high, and the configuration is a pain. Face it, Apache is not very friendly.

Mark claimed that Nginx was friendlier. To be fair, the config file was shorter. However, it still looked fairly arcane to me. I guess easy is in the eyes of the beholder.

Still, the memory issues are something I can’t ignore. Obsidian Portal runs on a VPS from Rimuhosting, so memory is a scarce resource. If I can reclaim enough memory to run another mongrel instance, that would be quite a coup. However, the real offender on my VPS is Tomcat. What!?! Tomcat!?! Well, I needed something to run Solr, and Tomcat is the only servlet container I’m familiar with. So, even though it’s a memory hog, it’ll have to stay for now.

Overall thoughts

All in all, the meetup was great. As I said, everyone was very nice, and all seemed fairly down to earth. There were no pissing contests over who was the better hacker, and newbie questions were answered honestly and humbly. I can’t wait until next month’s meeting. Maybe I’ll even try to scrape something together to present.

Battling pingomatic, Technorati, and the other XML-RPC ping services

Promotion, Ruby on Rails 2 Comments »

So you want on Technorati, huh? You’ve got a website with blogs or at least something that could be loosely considered a blog, and you want more exposure? Well, you’ve come to the right place! Step on in and I’ll tell you all my secrets of getting XML-RPC pinging to work for you, driving the hordes of the Internet to your doorstep! *

Getting onto the syndication services is not that hard. Or, at least that what’s they say. I have had a devil of a time getting the Obsidian Portal adventure logs to show up. By any standard definition they are blogs, and therefore should not be excluded from the syndication services. Still, here I am after a month with very little to show for it. Worst of all, it’s extremely difficult to track down where I’m going wrong.

Under the assumption that someone out there is in the same boat, here are a few tips to help out. I’ve grouped my tips into two categories. “Concrete Advice” covers tips that could definitely make a difference and should be attempted first. “Shots in the Dark” are some ideas I had that may or may not do anything at all. But if you’re desperate…

Concrete Advice

Targets

Rather than searching out all the services, just use pingomatic. You can add any additional ping targets if you wish, but pingomatic has found some pretty good ones.

Testing

Actually testing your pinging is probably the hardest part. You send a ping out into the ether, get a response like “Thanks for the ping!” and then you wait. And wait. And wait.

Sometimes you will have to wait several hours for the blog or post to show up on Technorati. Sometimes it won’t even show up at all. Not knowing if the ping is working is the most frustrating part of the entire experience. That’s where weblogs.com comes in.

Weblogs.com is your testing buddy!

Ever hear of weblogs.com? Neither had I, until I started this journey. Apparently, they’re the wackos who came up with this crazy pinging idea in the first place. To boot, they provide the best way of testing whether or not your ping service is working. They have a list of the most recent pings they’ve received available as an XML file. So, here’s how the testing works:

  1. Send a ping to pingomatic. Verify that pingomatic responds correctly (ie. “Forwarding your ping to 16 services”).
  2. Get a cup of coffee or something.
  3. Download shortChanges.xml (use wget to avoid caching by your browser).
  4. grep for the URL of the blog you pinged.

Note: shortChanges.xml seems to be cached on the server side and updated every couple minutes or so, so keep checking if you’re not there right away. After 5-10 minutes, you should either be listed or your ping never made it.

If it’s there, then you can be absolutely sure of 2 very important things:

  1. Your ping to pingomatic was successfully received.
  2. Pingomatic forwarded your ping to 1 other service successfully.

That may not seem like much, but we can infer (ie. assume) a lot more, namely that pingomatic is forwarding your pings to all the other services. This means that whatever problems you’re having getting registered with the syndication services, it’s not related to your pinging process. So, if you’re still not showing up on Technorati, it’s time to do some more digging.

Are you valid?

Ok, now your ping is working, what’s next? Validate your site and your feed!

The first thing a syndication site will do is pull down your feed and spider your site. You want to be as welcoming as possible when that happens. That means having valid, well-formed XHTML for your site and a valid RSS/Atom feed. Both of these are easy enough to check:

They will tell you what’s wrong with your site. Get it whipped into shape so the syndication spiders find what was promised by your ping.

Extend your best foot forward

Since we’re using pingomatic, we have our choice of a regular ping or an extendedPing. Just go whole-hog and send the extendedPing. It allows you to specify both the site URL and the associated RSS/Atom URL. Send all the info you can to pingomatic, and let them decided what to forward on to the other guys, depending on who can accept it.

“Ping Test” is a crappy post title

When looking for your posts on the syndication sites, make it easier on yourself, and use a test post with easy to search for text. “Ping test” is going to lump you in with all the other people doing the exact same thing. Instead, stick in a string of nonsensical text like “flatly waking Oberon” that doesn’t show up in a Google phrase search. This will make your tests just a little bit easier to find.

Shots in the Dark

Tag, you’re it!

Add a few categories to your post in the RSS feed. A lot of spiders and search engines (as well as blog apps) seem to treat the category field as a place to dump social tags. So, you should too! Even if you have to hard code a few categories in there, go ahead and do it. For the Obsidian Portal adventure logs, every post is tagged with ‘games’, ‘gaming’, ‘rpgs’, and ‘roleplaying.’ Does it help them get picked up? I don’t know. That’s why it’s a shot in the dark.

Check your title tag for your blogs

For a while, I was adding “Obsidian Portal” to the title tag of every adventure log. For the fleeting moments when they were showing up on Technorati, it looked terrible. Suddenly, they all disappeared. It occurred to me that they might have been flagged as duplicates or too similar. Same domain and similar titles. Is that the case? Who knows?

Parting thoughts

I have not had much luck getting listed with Technorati or any of the other services. Sometimes it works, sometimes it doesn’t. After about a month of trying, I’m about ready to throw in the towel. I’ll keep the pings going, but I’m not going to devote much more time to testing and analyzing whether or not they’re working.

If you do manage to find the secret to getting listed, please speak up in the comments or write a blog post of your own!

References

*Author is full of crap and is still unable to get his site’s blogs listed on Technorati. If you know what he’s doing wrong, please post a comment!

Good luck to MyNextDive

Promotion, Ruby on Rails No Comments »

In an earlier post on the Atlanta Web Entrepreneurs meetup group, I mentioned that I had met some fellow Ruby on Rails hackers from here in Atlanta. They were working on a secret project and didn’t want to publicize it. I can definitely understand the feeling.

Fast forward a few months and now they’re tired of working in the dark. At a certain point, you just have to push it out there and sink or swim. In fact, their business depends on both sinking and swimming. Ha ha, lame. Anyways, if you’re a scuba diver or were ever interested in being one, check them out:

From the guys here at AisleTen, we wish the best of luck to MyNextDive May you find your niche and get your user base!

References

The search for credit card processing part 1 - TrustCommerce

Business, Plugins, Ruby on Rails 3 Comments »

We have finally gotten to the point where we are ready to start offering subscriptions to Obsidian Portal. We don’t expect there will be a lot of interest, but it’s always a sort of chicken v. egg problem. If you don’t have paying subscribers, then it’s not worth the effort to make the features. Conversely, without the features, no one is going to pay. On second thought, I guess it’s not chicken and egg, it’s pretty clear: you need features or no one will pay. ;)

Asking for payment means you will need to be able to accept it. Currency on the web is passed almost exclusively via credit cards (except for PayPal…), so that’s the direction we need to go in. That requires us to select a credit card processor. For today, we will be looking at TrustCommerce.

I won’t go into the details of how credit card processing works, mainly because I don’t really understand it myself. Suffice it to say, there are a lot of middle-men, and they are all trying to take a cut. Each cut is either a percentage of the total charge or a flat fee or both. So, a typical fee structure might be $0.30 flat fee plus 2.5% of the total transaction.

Note: If you don’t care about the analysis and just want to see a rundown of their prices, then jump to the pricing.

Go easy on me; it’s my first time

When selecting a processing agent, our first priority right now is ease of use. We don’t expect there will be a lot of people signing up for our premium service, so we don’t want to expend a lot of effort on a payment system only to never see it used. Also, we’re willing to pay a higher rate to the processor since 3% of $30/month is a lot different than 3% of $30,000/month. I’ll pay 3% vs 2.5% if the 3% service takes 2 hours to implement and the 2.5% service takes 10. So, for us, ease of use trumps competitive pricing.

Since we’re talking about subscriptions as opposed to purchases, there is a recurring element to the payments. Since we want easy-to-implement solutions, we are scoping our search to only include the payment processors that offer a recurring service. This is a very important thing to note, especially if you’re in the same boat. A 1-time payment processor model (like Google Checkout) just will not work if you want to do subscriptions. The main reason is that you will have to store the users’ credit card info on your server in order to pass it to the payment processor each billing cycle. Do not do this! There are actual laws and regulations detailing what sort of security procedures you have to maintain in order to hold that sort of sensitive data. It’s much easier to simply pay someone else to deal with that crap. If you do choose to store their info in your database, you should be looking for a lawyer right now, not a payment processor.

Just plug in your credit card info

In Rails, ease of use means finding a plugin. I write a lot about plugins on this blog, so why should credit card processing be any different? Doing a quick Google search led me to the TrustCommerce subscription payment plugin.

Finding this bit of code brought a smile to my face, as I thought I had just finished 90% of the work. Sign up for an account, drop in the plugin, and wait for the money to roll in. Too bad there were a few red flags that derailed the money train.

Sitting by the phone

TrustCommerce does not list any pricing on their website. Instead, they say you have to sign up for a test account, and then you’ll be contacted. Not a big deal, I guess. So, I signed up for a test account.

The first red flag went up when I did not get an immediate callback. Sure, I signed up at 11:00pm Eastern Time, but that’s normal business hours in Internet time. In other words, if you’re an Internet company that requires phone contact, you had better have someone manning the phone at all hours. A lot of Web jockeys like me have a regular 9-5 job that precludes us from doing our business dealings during normal business hours. I want to deal with companies that understand this and have staff available during my normal business hours.

Red flags: 1

The ball sits in my court

The second red flag went up at their lackluster eventual response. My cell is in a dead zone at work, so whenever I leave for lunch, I get all my messages. On the day after requesting contact, I had a voicemail message from TrustCommerce. Still no pricing info, just a short message to call them back. Seeing as how I was busy, I couldn’t do it right away. Then I forgot. Dead silence on their end. No e-mails, no more calls, nothing.

Now a lot of people may disagree with me on this, but I think they should have been hitting my inbox and voicemail pretty hard. “Mr. Wedemeyer, we’re still interested in talking to you about blah blah.” or “Send us an e-mail with the best time to call you.” That’s how the mortgage people behaved when I used LendingTree. Sure, it was annoying, but you knew they wanted your business. To me, an anemic response indicates that someone isn’t really serious about recruiting me as a customer.

Red flags: 2

Little fish: prepare to get fried

When I finally did get in touch with someone from TrustCommerce, he was quite happy to answer my pricing questions. I don’t know if I’m allowed to post that info, but since they didn’t expressly forbid it, here you go:

Basic pricing

  • $95 1-time fee
  • $20 / month
  • $0.20 / transaction

Citadel (recurring payments)

  • $145 1-time fee
  • $10 / month
  • $0.10 / month / billing id (ie. subscription)

Holy crap! $240 just to get started, plus an additional $30 per month, just to be allowed to use their service? Seeing as how I expect Obsidian Portal to be making around $10 / month, at least until we can recruit more people, this is insane! I politely said thank you to the salesman, hung up the phone, and started writing this post.

I guess I see these huge front-loaded fees like this: If you’re making enough money that the fees don’t matter, then you already have a lot of subscribers, which means you’re already handling credit cards. Maybe their service is so great compared to the competition that it’s worth it for the big boys. But, if you’re a small time operator like me, forget about it.

Red flags: 240 + 30 / month

The search continues

Although I said pricing was not our top priority, the front loaded fees with TrustCommerce completely invalidate them as a viable option. It would be a very long time before we paid off the initial investment, and with our none-to-clear business prospects with Obsidian Portal, that’s a gamble I’m not willing to take.

In the next exciting chapter we will be looking at Amazon Flexible Payment System (FPS). This new web service from Amazon is meant to rival Google Checkout and PayPal. I’ve been extremely pleased with S3, and maybe they can do one better with FPS. Stay tuned to find out.

pluralize - native English speakers never say “1 hours ago”

Ruby on Rails 1 Comment »

The correct use of singular vs plural nouns is something the Rails community is very familiar with. Your ‘User’ object resides in the “users” table. A User has_many ‘blogs’ while a Blog belongs_to a single ‘user’ You all know this already.

So with this much attention being paid to number in the back-end code, it’s pretty sloppy to disregard it in the views. “You have 1 friends” or “Last update: 1 weeks ago” just looks bad. Sure, we could write the code to fix it fairly easily, but we have better (ie. more fun) things to work on, right?

Luckily, Rails’s text helpers ride to the rescue once again. The pluralize method does exactly what we’re talking about. Simply give it a number and a singular spelling, and it will use either the singular or plural form based on the number. If it guesses the wrong plural form (ie. ‘mouse’ vs ‘mice’), you can explicitly supply the plural version.

If you have a single blog, it will say “You currently own 1 blog.” For n > 1, it will say “You currently own n blogs.”

Simple and sweet, nothing more to say :)

Deploying subversion tags using Capistrano

Ruby on Rails 3 Comments »

When I heard that Capistrano 2 was being released without support for deploying subversion tags, I was a little miffed. I thought it was a major oversight with Capistrano 1, and I was hoping they would rectify it.

After a little hacking, though, I realized there was really no reason to be angry at Capistrano for this oversight. All I needed to do was write a tiny bit of code in the deployment recipe and I was good to go.

The main drawback to this approach is that the directory created on the production server is named with the timestamp of the deployment, rather than the tag name, which would be ideal. However, with the small amount of searching I did, I was unable to find a way to change this naming scheme. I have a hunch it might break the rollback functionality and is probably not worth the trouble.

Note 1: Part of my tagging for release process is to create a ‘latest’ tag in the tags directory that points to the latest tag (duh!) So, the code above asks which tag to deploy, but will deploy ‘latest’ by default if there is no input from the user. This is not strictly necessary for the tagging, but makes the deployment slightly easier (ie. just hit enter when prompted for a tag).

Note 2: This scheme should work with both cap 1 and cap 2, but I only tested it on cap 2.

attachment_fu + S3 + ruby tile cutter + Google Maps = Easy custom maps in Ruby on Rails

Plugins, Ruby on Rails 7 Comments »

For Obsidian Portal, we wanted the ability for users to upload their own maps. The simplest way to do this would be to allow them to upload an image, then display it statically. While not a terrible feature, it has some definite limitations.

What we really wanted was a way to allow users to upload maps and navigate around them as everyone is accustomed to with Google Maps. Luckily, the guys at Google make just such a thing possible. As they say, a picture is worth a thousand words, so here are some examples of what we’ve accomplished. I will add more as users begin uploading.

  • Kensing This is the main island for my D&D 3.5 campaign.
  • Caedwyr Isle. This is another D&D 3.5 campaign, run by one of our users.

Note: Sorry if any of those links are 404s. The users can delete the maps as they please.

Over the course of a 3-day weekend, I was able to go from nothing to everything. I had a lot of stumbling blocks, though, and with this tutorial it should go much faster.

So, without further ado, let’s get some background on the technology that drives Google Maps: tiles!

What’s a tile?

The data set for Google Maps is just a giant collections of 256×256 images called tiles. When you view a map in your browser, the smarts in the system determine what part of the Earth is visible on your screen and requests only the tiles for that portion. The tiles are then laid out in a grid pattern to make a seamless image. Moving the map around spawns off asynchronous requests back to the server to get more tiles. In this way, the maps load quickly, and allow unlimited scrolling.

By default, the map pulls the tiles from the main server at Google. This makes sense as most people using the API want to display actual images of the Earth, or the nicely made street maps that Google does. The clever guys at Google, however, made it possible to switch this up and request the images from anywhere, thereby allowing developers to serve up their own tiles, yet rely on Google Maps to lay them out and stitch them together appropriately.

Tile cutter? Already written…

Ok, so now we know that we can tell Google to get the images from somewhere else, but now we need to make the images. It’s a lot to ask from your users to take their images and cut them into 256×256 chunks. So, we will need a tile cutter, or program that slices an image into the tiles.

Luckily, a ruby tile cutter does exist, and it works perfectly with Google Maps straight out of the box. The tile cutter does not have a lot of options, but for basic tile cutting it’s perfect.

Tile server? We choose S3.

We’ve got tiles, but now we need to serve them out. Mongrel is great for Rails apps, but not so great for serving images. A lot of sites (including Obsidian Portal) use some funky rewrite rules to try to get Apache to serve the static images and such. Still, serving tiles is a fairly intense task. Every time the user moves the map or zooms in and out, it will make several image requests to the tile server. For a mongrel server, this means it’s serving images rather than handling Rails requests, which is a big no-no.

Riding to our rescue is Amazon’s S3 (Simple Storage Service). Every object stored there can be made publicly accessible, and S3 will serve up the content with the correct content-type header. That’s perfect! If we can build our URLs correctly, then S3 becomes our tile server, thereby offloading all the heavy lifting to them instead of our Mongrel server.

From what I’ve seen so far, S3 is an excellent tile server, at least in terms of speed. Tiles are served faster from S3 than Google’s servers, in my subjective experience. Further, it was even faster than Mongrel serving the tiles from localhost to localhost. Your maps will load quickly, and be very responsive.

Finally, utilizing S3 allows us to store an unlimited number of tiles, which is very important if you have several zoom levels. The ceiling is determined only by the limit on your credit card ;)

attachment_fu, bringing order to chaos

Every S3 bucket is a wild, untamed jungle, and things can easily get lost. Without directory structures or meaningful modes of organization, it can be very easy for objects to go in and never come out. Therefore, it’s very, very important to keep track of everything that is being placed in S3. For us, that means having a database record for every file in S3.

Luckily, that’s exactly how attachment_fu sees things as well. Every file managed by attachment_fu has a corresponding record somewhere, and when that record is deleted (using the destroy method), attachment_fu handles deleting the associated file. This is extremely handy for a map that may have 64, 256, or even 1024 associated tiles images.

Further, S3 communication is built right in to attachment_fu, so you, the developer, barely have to learn anything at all about interacting with S3. Just let the plugin do it.

Enough chit-chat! Show us some code!

Before you jump in and start coding, there are some steps to take first. I won’t go into detail, since it’s outside the scope of this article, but here they are:

  1. Sign up for a Google Maps API key
  2. Sign up for an Amazon S3 account
  3. Install attachment_fu
  4. Install RMagick (it’s needed by the tile cutter)

Get the modified tile cutter

The YM4R tile cutter is a command-line tool by default. Since it’s written in ruby, there’s really no reason not to make the calls directly, rather than resorting to using the shell. I slightly modified the tile cutter for this purpose, as well as to enclose it inside a module. I tried to retain its ability to be used from the shell, but I didn’t test extensively. It works for our purposes, though.

Get the modified tile cutter here: tile_image.rb Drop it into your /lib directory and everything will work like magic.

The MapImage and MapTile models

This is a model for a map with 3 levels of zoom. As required by the tile cutter, we create 3 different size images: 256×256 for zoom 0, 512×512 for zoom 1, and 1024×1024 for zoom 2. Since I’m lazy, I just let attachment_fu create the images by resizing the original to 1024×1024 and then using the built in thumbnail functionality to get the other sizes.

After the images are created and sized we use tile_image (via the get_tiles function) to create all the tiles in a subdirectory of /tmp. These are then used to create MapTile objects (shown below), which get uploaded back to S3.

Astute readers will note that any non-square image will get distorted by this. One solution is to pre-pad the image with extra space to make it square. That is left as an exercise to the reader ;) (Note: If someone comes up with a good way to do this with RMagick, send me the code and I’ll post it and credit you.)

Astute readers will also notice that I’m wasting time and bandwidth pulling the images back from S3 after uploading them, rather than breaking in to the attachment_fu upload cycle and tiling the images before they’re uploaded. I’m lazy and it works. Send the code if you know how to do it better.

Note: MapImage subclasses from Image, which is an abstract base class I use for all my images managed by attachment_fu. Single-table inheritance allows me to store multiple kinds of images in the same “images” table this way. This is not necessary to get the custom maps to work, it’s just how I like to do things.

The MapTile class is very simple, thanks to attachment_fu. The only real trick is to overwrite the base_path method. This allows us to modify the S3 key (ie “file path”) given to the MapTile image when it is stored. Instead of being stored using its own ID, we would like to store it using the ID of its parent MapImage. This is the structure that will allow us to serve up the tiles.

The View…now with added JavaScript!

Warning: I will be the first to admit that I don’t know squat about JavaScript. I’m learning as fast as I can, but it probably looks terrible to someone who really knows what they’re doing.

Most of this code was lifted, almost untouched, from the Mapki page on creating custom maps (see references below). The Google Maps code specific portions of creating custom maps has been understood for a long time now. Still, I’ll explain some of the things we’re doing that are special.

I am offsetting the map by 2 zoom levels. So, when we have a tile for zoom 0, we will display it at zoom 2. This will effectively give some buffer space around the image. If you display exactly according to the zoom level, it will look a little strange. At zoom 0 (all the way out), it assumes you’re looking at the entire world (which is roughly spherical), and therefore will place the same tile multiple times in a line. This works for a world map, but looks strange for anything smaller. So, we offset a bit by fooling the system into thinking that we’re zoomed in. That way, it will pad around our image with blank space.

Near the top, you will see that I am setting the Lat/Lng to a seemingly arbitrary number. This is related to the zoom offset. Tile location 0,0 is somewhere in the arctic ocean. When the map first displays, I want zoom level 0 (a single tile) to be visible in the center. I didn’t find a quick and easy way to calculate from tile X,Y to Lat/Lng, so I just started experimenting with values. (79.1, -135) works well for zoom offset 2, while (66.66666, -90) works well for zoom offset 1. If you want to offer arbitrary zoom offsets, you’ll need to come up with a better way of handling this. Send me the code and I’ll post it.

Finally, the real meat! GetCustomTileUrl is where all the magic happens. This is how we tell Google Maps to use our custom tile server instead of the default one. The parameters are an X,Y point and a zoom. Our tile cutter names the tiles perfectly for this, and we placed them in S3 according to the ID of their parent MapImage, so all we have to do is construct the URL based on this ID.

That’s It!

Now we’ve put all the pieces together. attachment_fu handles uploading and resizing, tile_image handles cutting the tiles, and S3 handles serving them out.

Issues

Image processing on the mongrel thread

If you were paying attention, you’ve realized that when uploading a map everything happens during a single HTTP request. By my estimation, uploading to S3, downloading the resized images, running the tile cutter, and then re-uploading the images takes between 5-10 seconds for a map with 3 zoom levels. In my case, the end total is 26 images that have to be created and moved. For any reasonable size site, tying up the web server for 10 seconds is out of the question. You can counter this with more mongrels, but that’s an approach that won’t scale. Plus, the time increases exponentially with more zoom levels. Tiling 6 or 7 zoom levels could take several minutes, even on a beefy machine.

Fortunately, there’s no reason the image processing and uploading has to be done on the main thread. Simply allow the users to upload the image and push the single image to S3. In the database, flag it as “not tiled” Then, asynchronously, have a script that periodically wakes and scans the database for maps that need to be tiled. It does the tiling work and uploading and then flips the flag in the database to “tiling finished.” For the user, they upload their map and then are taken to a screen that says, “We are currently preparing your map. Please wait a few minutes for the process to complete.” In reality, if the number of maps is low, and the zoom level is small, it could only take 10-20 seconds, assuming your script wakes up frequently enough to check.

Maps must be square

Because of the way the tile cutter works (or at least how I understand it), the starting image must be perfectly square. This can be dealt with by padding the uploaded image with a neutral (or transparent, for PNG) background until it has square dimensions, then send it to the tiler. Like I said before, if someone has a good way of doing this with RMagick, send the code and I’ll post it and give credit.

No map markers

This isn’t technically an issue, it’s just another feature I want :) Next up on the feature list is the ability to add markers to your map, and drag them around. By using Google Maps as the backend, this should be quite easy to do, at least I hope so. I’ll cover that in part 2, if I ever get around to it.

Resources

These are required resources for this to work at all. If for some reason you are unable to use any one of the following, the entire approach falls apart.

  • AWS::S3 Library - The main page for the Amazon S3 Ruby library. It has great examples and documentation.
  • Amazon S3 Homepage - Go here to sign up for Amazon Web Services (AWS). Make sure to also sign up for S3, since AWS is comprised of many services, and you have to sign up individually for each one.
  • Mike Clark’s attachment_fu tutorial - Pretty much the tutorial/howto for attachment_fu
  • YM4R - Includes the tile cutter. While not strictly necessary if you use my modified tile cutter, they still deserve the credit and so are listed in the required resources.
  • Google Maps API Key signup - You will need a Google Maps API key, otherwise you can’t use the map code. It’s free, so don’t worry about it.
  • RMagick - This is a ruby interface to the ImageMagick (or GraphicsMagick) libraries. It is needed for all the image resizing and cutting.

These are some resources that I found incredibly useful when trying to figure all this stuff out. They give some good background.

Credits

Thanks to Jordan Bethea (a player in my D&D campaign) for suggesting the feature, and thanks to Scott Turnbull for guiding me to MapWoW.com and giving me tips on how to implement it.

Thanks for reading, and if you liked the article, please consider Digging it or voting for it on your favorite social bookmarking site.

Tidy Stylesheets - dynamically add stylesheets to a view based on controller and action

Ruby on Rails 3 Comments »

Peter Harkins has a great post on how to split stylesheets based on controller and action. The required code is very short and easy to integrate. It will add a few levels to your stylesheet directory structure, but any Rails-savvy developer should be able to very quickly figure out what’s going on.

This is very similar to how Symfony handles stylesheet selection, except it is done via configuration files. This is one of the very few things I like about Symfony, and I’m happy to see that someone has ported the functionality to Rails.

If you know of a better (or different) way to manage stylesheet complexity, please post a comment.

Integrating Beast forum into a Ruby on Rails app - part 1

Ruby on Rails 29 Comments »

At some point, it’s almost guaranteed that you will want a forum for your website. Forums make a great way to allow your users to contact you and each other, and their persistence allows for archiving of the answers so you (hopefully) don’t have to keep answering the same questions over and over. Moreover, most users are familiar with the forum concept and interface, thereby reducing the barrier to communication.

If all you want is to get a forum up as fast as possible, there are several free/open-source/commercial solutions. They will get you up and running very quickly, provide all the functionality you need, plus tons of features you’ll probably never use. If that’s what you want, stop reading and go get one.

Assuming you’re still here, then you’re probably in the same boat as me. You’ve got a pre-existing Ruby on Rails application, and you want to add a forum. Moreover, you want to add an integrated forum that allows your users to utilize the same login credentials as they use for your current app.

Enter the Beast! Beast is a pure RoR forum application that has everything you might expect from a forum, plus offers the glimmer of hope that you’ll be able to integrate it with your current app. Still, once you dive in, the glimmer starts looking pretty dim. I approached it several times and backed off before I finally had the confidence to finish it. Luckily for others, I made a lot of mistakes and am happy to share them.

The Goal

For now, my only goal is to unify usernames and passwords, preventing the users from having to register twice. Very important to note is the fact that I am NOT talking about a single sign-on. I am comfortable (at this point) with forcing the user to sign in twice. I simply want to make sure that the username and password are the same both times. Remember: KISS and baby steps.

Before you start

Before I describe the actual approaches, I would like to give some general advice on how to proceed, regardless of your approach. These following tips could potentially save you a lot of time in the very real case that something goes horribly wrong.

Create a development branch

You are about to attempt a potentially nasty bit of integration. You might need to hack your code (and Beast’s code) in very ugly ways. You may need to compromise your whole design. Worst of all, you might fail! At the end, you may realize that it just is not worth it, and you’re destabilizing your codebase too much to justify the payoff. Wouldn’t it be nice to simply write off the time and get back to work?

From another angle, perhaps you’re in the middle of the integration when a user finds a nasty bug on your live site. You need to fix the bug right now, but your codebase is in a totally unstable state. You can’t fix the bug without checking in your Beast changes, and they will break everything. Sounds like it’s going to be a long night…

If you do your work on a development branch, then switching back to the trunk is very easy. Simply check in all your changes on the branch, then wipe out your working copy and check out a fresh one from the trunk. All the work you did on Beast is still there (in the branch), but you’re working on the trunk, totally clean. You can continue main development (such as fixing nasty bugs), and get back to Beast when time allows. Finally, when you’re satisfied that Beast is ready, a simple merge-to-trunk is all it takes to bring all your changes into the trunk.

Bottom line: Do all your Beast integration work on a branch.

Grab Beast as a vendor branch

Beast is still under active development, and they just hit 1.0 Do you plan to stay current with it as it matures over time? I definitely plan to. However, integrating it with your application will almost definitely require you to modify it in some way. Heck, just installing it by itself will probably require modifications in order to get the text to say what you want.

Starting from a vendor branch will allow you to (hopefully) easily upgrade to newer versions, while keeping all the changes you had to make in order to get it working with your app.

We have already covered how to do a vendor branch and how to upgrade using a vendor branch, so go browse those posts if you don’t know what I’m talking about.

Please, I’m begging you, trust me on this one. Do the vendor branch and save yourself a huge headache when Beast 1.1 comes out…

The Approaches

I attacked the integration problem from three different angles. Two worked and one was a total failure. I’ll describe each one and its pros and cons. If I get anything wrong, or left something out, please do not hesitate to correct me in the comments. I really want this to be a valuable resource for the Rails community.

One commonality to note between all the approaches was that I never tried to fully integrate the applications from the perspective of controllers and routing. They each retained their own directory structure, they each will have their own root domain, and they will each require their own mongrel server, unless I can figure out how to get a single mongrel to serve both.

1. Separate databases; Users pulled from my app’s database

With this approach, I tried to keep separate the two databases, except for the Users. The RDocs for ActiveRecord::Base give instructions on how to force Rails to connect to a different database for a specific model. This seemed like an excellent way to handle the Beast integration.

I added all the missing Beast user columns to my Users table, created the secondary database, and updated the Beast User model to connect to my database. At first it worked, and I was pretty excited. However, as I navigated around the forums, it crashed almost immediately. Because there are several relationships between the User and other models, the system was attempting to run some joins. Perhaps other RDBMS’s can do cross-database joins, but it seems that MySQL cannot, at least not how I have it set up.

Bottom line: Didn’t work because MySQL freaked out about the cross-database joins.

2. Same databases; Beast tables renamed with beast_

My second attempt involved adding all the Beast tables to my current database, and then renaming all the Beast tables with a “beast_” prefix. It wasn’t strictly necessary as there were no naming conflicts, but I thought it would help with readability.

I left the users table and sessions tables untouched, as I already had a users table and simply added the Beast columns to it. The sessions table seemed like something special, so I decided not to mess with it.

To accomplish the renaming, all I had to do (I thought) was edit the migrations a little, then use the set_table_name directive in each of the model classes. When this was done, things looked like they would work.

What I didn’t realize was that much of the Beast database querying is done via specifying :conditions on a find() call. I didn’t look too closely, but perhaps the Beast team found they could not be served adequately by the find_by_x_and_y finders. The end result of this was that there were several places where the table names were listed explicitly inside the query conditionals. I ran several search/replace runs for “posts”, “forums”, and so on for all the Beast tables. Of course, these strings got several hundred hits throughout the codebase, and the replacement was a real chore.

Eventually I got Beast working, but I did not have a lot of confidence in the changes. I was not sure if I had caught everything, although this could be reasonably tested using the unit and functional tests. Most troubling to me, however, was the prospect of trying to integrate a new release. I had the sneaking suspicion that every time there was a new release of Beast, I would be hunting down table names inside of conditionals and replacing them by hand. Besides the tediousness, this was bound to produce bugs that I wouldn’t catch and would escape into production.

Bottom line: Works, but you’re in for a world of hurt when you want to upgrade to a new release of Beast.

3. Same database; Beast tables unmodified

As I mentioned earlier, I had no table name conflicts with Beast, so this was a viable option for me. Just add all the new Beast tables, and modify my users table to add the necessary fields.

This was a no-brainer, and took almost no time at all to accomplish. Run the migrations, make a few modifications to the authentication system, and you’re up and running. After trying the other two methods, I was able to do this one in about 5 minutes.

This is ultimately the approach I would recommend to anyone attempting the integration. Of course, it works best if you have no table naming conflicts. However, assuming that you do, I recommend only renaming the Beast tables that actually conflict. This will minimize the amount of find/replace hunting that needs to be done, both now and for future upgrades. Remember: KISS.

Bottom line: Works, is easy to do, and represents the least amount of risk when upgrading to new releases of Beast. This is my recommended solution.

Modifications to support acts_as_authenticated

Handling the database will be the hardest part, but if you are using acts_as_authenticated, you’ll need to make a few updates to the authentication that goes on.

It actually turned out to be very easy, but remember, this does not get you single sign-on, just unified usernames and passwords.

Modify the Beast User.rb, and remove or comment out the self.authenticate method. We will be replacing this. I think there might be a way to re-alias it, but deleting works just as well.

Replace the one you removed with the following method:

As you can see, this simply finds the User by their login, and checks their password the same way that acts_as_authenticated does.

Other than that, I modified reset_login_key! to change “password_hash” to “cypted_password” To be honest, I have no idea what this does, but the two fields are basically equivalent in the two authentication schemes (I think). Anyways, it seems to work.

Next steps

So why is this titled part 1? Because I want single sign-on, of course! The eventual goal is to unify the sign in interface so that the user signs in at one place and can then navigate through the entire site, including the forums, and never have to re-authenticate.

Part 2 (if it ever gets written) will deal with unifying the sign-on. This has been done before (see the references at the end of the post), but I’m just not ready to attempt it yet.

Update 2007-12-17 Adam from the comments has posted a great tip on how to easily get single sign-on. He says:

Single sign on is very easy. ALl you have to do is set your session domain to be the same by typing (for rails 2.0)

config.action_controller.session = { :session_key => “_session_id”, :secret => “secret_session” , :session_domain => “.mydomain.com” }

So now forums.mydomain.com and www.mydomain.com will use same cookie/session and you can host beast on a subdomain as a separate app. I then added a before_filter to application.rb that ensured session[:user] was populated otherwise i redirected to www.mydomain.com/login

I have not tried this yet, but telling the two Rails apps to use the same session is probably the best way to go.

Additional References

Beast Forums - A great place to ask questions about Beast…go figure ;)
Integrating Beast with another application - Someone else who did the same thing, but duplicated the User information across 2 databases

Call for participation

If I have gotten anything wrong, or you know a better way to do it, please post a note in the comments! This integration was fairly hard to do, and I made a lot of false starts. I want to make it smoother for those that follow.

Good luck, and don’t forget to make a development branch!

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in