Archive for the ‘Rails’ category

Rails Fails To Update Serialized Columns On Save

June 28, 2008

One of the problems I hit earlier today, which literally cost 2.5 hours to resolve, was that I have a Rails model which uses a serialized column in it.  The column contains a hash of options for the object.  Fairly simple stuff.

As it turns out, after overwriting one of the parameters in the hash and saving it, the object in the database would be out of sync with the ActiveRecord object still in memory (even though save returned true), which lead to hilarity the next time someone grabbed the object from the DB and got partially out-of-date data.  (Inconsistent out-of-date data.  Failure.)

As it turns out, the issue is a new feature Rails 2.1 — partial updates of database records.  Rails keeps a list of which attributes are “dirty” and only updates those when you call ActiveRecord#save.  Unfortunately, touching the contents of a hash does not mark it as dirty.

I was absolutely clueless how this was happening (I stupidly upgraded to 2.1 on a whim, thinking that I hadn’t written any significant code yet so incompatibilities wouldn’t bother me — true enough, the only incompatibility was with my mental model of how ActiveRecord worked), but I successfully diagnosed the why — a dirty bit wasn’t getting set for the serialized object.  OK, no problem, the same thing happens at the day job with our Java system — which means I can use the same hacky solution to the problem.

def before_save

  options_hash_clone = options.clone  #shallow copy of options hash

  options = {} # sets dirty bit for options hash

  options = options_hash_clone # restores options hash to original content, ensuring save updates it in DB

end

which will, indeed, set the dirty bit. 

As it turns out, there is a cleaner way to do this if partial updates aren’t a requirement for your model:

#there are a number of places you could put this — the model class itself strikes me as decent

ModelNameGoesHere.partial_updates = false

A big thanks to this post for putting me on the scent of the problem.  Seems I missed quite a bit of discussion on Google about it since I was not hitting the right keywords apparently.  The fact that this is on by default appears to be one of those opinionated software practices where “opinionated” means “it is our opinion that you should be as familiar with the change log as the core team before you install a new production release”.  (Sorry if I sound bitter.  2.5 hours.)

Advertisements

Brilliant Bit of Javascript for Redirecting Downloaders

March 12, 2008

One of my uISV buddies, Ethan (king of language learning software), took me to task earlier today for spending so much time optimizing my download page when I could just eliminate it entirely and link the download direct to the download button in most cases.  I had always had these issues with that solution:

  • My users don’t necessarily know what to do with a window that pops up
  • If I do an HTTP Refresh or Javascript redirect, many browsers pop a security warning
  • I have to discriminate between Mac and PC users somehow
  • It is impossible to track that conversion for AdWords purposes, currently

Examining Ethan’s code made it really easy to avoid the first two issues:

function SetUpRedirect()
{
var destination = “http://www.bingocardcreator.com/free-trial.htm“;
setTimeout(“window.location='”+destination+”‘”,3000);
return true;
}

If you stick that in the OnClick attribute of a link pointing at your favorite executable, three seconds after clicking the link and having the download initiate, the user’s browser goes to the download page in the background.  This causes no security warning, scores them as a download conversion with the appropriate code on the page, and presents graceful fallback behavior if they don’t know what to do with the window that just popped up, since you can give them instructions.

Ahh, but what to do about the difference between Windows and Mac computers, which need different installers?  First, we make a controller method to handle it in Rails:

def free_trial_download
    if request.user_agent.downcase =~ /mac/
      send_file “public/files/BingoCardCreator.zip”, :type => “application/zip”
    else
      send_file “public/files/BingoCardCreatorInstaller.exe”, :type => “application/exe”
    end
  end

 That essentially says “If I’m positive you’re using a Mac, initiate a download of the zip file.  Otherwise, initiate a download of the exe file.”  (Obviously since 92% of my downloads are PC users I want to err on the side of caution.) 

Then, with a simple route added to routes.rb:

map.downloadFreeTrial ‘free-trial/download’, :controller => ‘static’, :action => ‘free_trial_download’

we get a simple URL which is platform agnostic and which decides, on the server side, which version of the file to give them.  You can then decorate your links to the platform-agnostic URL with the code to redirect the page to the download page in the background, with Analytics click tracking, and what have you.  Easy peasy!  One less step in the conversion funnel, and instantaneous recovery of a large portion of the 20% of folks who bounce out of the funnel at the download page.

WARNING: send_file will cause your Rails process to block while that IO transfer takes place under certain older versions of Rails (not in 2.0 in my testing).  This will cause requests coming to the same Mongrel after the download to wait until the download completes to start, which if you have a 56k modem user could potentially cause your basic site access to be delayed for minutesNot good news!

My site has two Mongrels running, very few dynamic requests, and very small executables.  If your site doesn’t have this profile, instead of using send_file, 302 redirect the browser to the appropriate file and let your web server handle the request before Rails does.

WARNING NUMBER TWO: You don’t want bots hitting that action, so its time for a good-old robots.txt exclusion of it.  Note that deploying this sitewide will cause your free trial page to lose quite a bit of the juice you’re sending to it.  However, given that that page is typically linked far and wide on the Internet and doesn’t include much interesting content on it (which would distract from the conversion to the trial!), you can probably live with that tradeoff.

Quick request: if you run an obscure browser or a Mac, kindly use my OS-agnostic link and tell me if it works for you.  (You should get a prompt to download BingoCardCreator.zip )

Because You Can’t Quite Get Enough Transparency…

November 26, 2007

I really wanted to post how Daily Bingo Cards was doing statswise today, but probably will not have the time.  (The short version: the snowflake queries are loving me and owning a top 10 spot on every possible variation of “thanksgiving bingo cards” is worth 1.5X owning the 11th spot on [thanksgiving bingo cards] itself.  Don’t ask me how you can rank for a phrase that competitive in less than 2 months of work.)  While I know the analysis is the really interesting bit, for the stats geeks in the audience I decided to make my website stats public in real time.  Enter a Rails plugin named Sitealizer, about five minutes of work, and powie, stats for anyone.

Want to take a gander?  Daily Bingo Cards stats.  At the moment it should be showing search queries, referrers, and the like for about the last 24 hours.  You’ll note that it is hardly as tricked out as Google Analytics (one nice feature Analytics lacks: it tells you what crawlers are hitting your site and at what rates), but it is good enough to keep me more or less honest when discussing traffic numbers.

Rails SEO Tips 90% Completed

November 19, 2007

Too many projects, too little time.  I got most of my Rails SEO hints page completed tonight, after finally implementing more of the suggestions I was making in Daily Bingo Cards itself.

The Table of Contents

The page is still a bit of a work in progress, of course.  I intend to keep it updated and continue gradually expanding the content.  Plus it is 2 AM and I really have no effort to do make the code samples more pretty (what can you expect — I built them by hand in notepad — lots and lots of ampersands, let me tell you). 

If you have any comments about the article, feel free to leave them here.  If you know any Rails developers who might be interested in the resource, please feel free to pass it on to them.

SEO Tips for Ruby on Rails

October 29, 2007

I’m working on this article as another bit of linkbait, and its about 33% of the way finished at the moment, but I thought I would give you guys a sneak peek.  If you have any comments, please, feel free.  If you want to blog or otherwise link to it, go right ahead, although it is very much a work-in-progress at this point.

The excerpt:

There is much to love about the Ruby on Rails framework. Don’t Repeat Yourself. It Just Works. Massive productivity gains, happiness returning to the world of boring CRUD apps, and a certain sense of panache in programming. However, while Rails has sensible defaults it doesn’t get everything right out of the box. This article focuses on how you can improve the search engine optimization (SEO) of your Rails site the Ruby way and get a

  • more usable,
  • more popular,
  • and more profitable application — with less work!

You can read the rest of it at Rails SEO tips, located at Daily Bingo Cards.  Why did I put it over there?  Frankly, I expect this to make the rounds a few times in the Rails community, many of whom have their own blogs, and I expect it to get linked to heavily.  There isn’t a Definitive Rails SEO Resource yet, and that page has delusions of grandeur. 

My blog is PR5, has a few hundred inbound links, and has little direct impact on my monthly bottom line.  Daily Bingo Cards is PR0, has about two inbound links, and has the potential to double my take-home pay.  Choosing to get the links over there rather than over here was not a hard decision.  Granted, the inbound links will not be that targeted to start out, but they’ll greatly help get the trust-ball rolling while I wait a few weeks to start ranking for my targetted snowflake queries.

P.S. When I post this to the social networking sites, for the ones which value a little bit of controversy with their morning coffee, the title is going to be “Default Routes Considered Harmful, and Other Rails SEO Tips”.  If you’re in the less-geeky end of the pool the reference might not make sense to you, but trust me, Considered Harmful is a (heated!) conversation starter around the Slashdot set.  I’m not saying it just to be controversial, though — leaving the default routes in a publicly-accessible Rails application is a bad idea, for the reasons I go over in the article.

Marketing A Superhero Novel (Web Admin Hackery Galore)

August 23, 2007

Around the same time I started Bingo Card Creator, my little brother got bitten by the Do A Cool Project bug and decided to become a published fiction author.  He is writing a superhero novel and, aside from this post being a totally transparent attempt to give him some SEO juice to get him kicking, I’d like to recount what we’re doing to market a product that, strictly speaking, doesn’t exist yet.  This is mostly going to be a technical overview — if folks have interest, I’ll go over some of the softer side of marketing at a later date. 

 Keep in mind that I am a programmer, not a system administrator.  Don’t do these instructions on a production box without testing it first, OK?  They worked for me, but if they don’t for you my liability is limited to refunding the amount you paid for this blog post.

Priority #1 — Setup a website.  I’m sure you’re shocked.

Specifically, because my brother is more skilled in dialogue writing than in web server administration, I set him up with WordPress to make the barriers to content creation nothing.  He had already had a blog he used for writing on WordPress.com, so moving him to hosted WordPress wasn’t much work from his perspective (we could even import all the old posts).  From my perspective, it was a bit of work getting SuperheroNation.com to coexist peacefully with Kalzumeus’ Rails test site on a single 256 MB Slicehost VPS, but after a night it was up and running.  (You can do this to host Rails and WordPress on the same domain, but the instructions are different and I’ll give them to you at a later date). 

Why go with hosted WordPress rather than WordPress.com?  It gives the site room to grow, and gives my brother control — he can incorporate Google Analytics throughout via a plugin (ahem, DO IT), and when he is ready to start selling the book throwing up e-junkie’s Fat Free Cart, a nice custom skin, and other features is very easy.  If you’re just starting your own site, for SEO purposes if nothing else, put it on a domain you control rather than wordpress.com.  It is a pain in the butt to set up, but moving a blog OFF wordpress is infinitely harder than getting WordPress up and running somewhere else and then making the changes you want down the road.

Here’s what you need to do, assuming you’re serving Rails with Apache proxying to a Mongrel cluster (works like a beauty even at 256MB, incidentally, although I haven’t tested it under severe load):

 1)  First, you’ll want to get WordPress installed.  If you’re hosting two domains on the same box, I’d recommend distinct directory structures for them — the non-Rails domain here resides in /var/www/superheronation/, and the Rails stuff is in a completely different path set automatically by deprec (see below).  After creating the directory, go over to wordpress.org, download the zip file, unzip it, and stick it in a subdirectory named wordpress (so, in my example, /var/www/superheronation/wordpress) .  We’re putting it in wordpress rather than in something descriptively named because if we change blogging platforms later we can do most of the job just by changing one line in a config file.  (By the way, wondering what to call your blog’s main user-visible directory?  I like “blog”, but a high value keyword works better for SEO… at least for now.)

1b) Skip this step if not a Slicehost customer.  There is a problem with installing WordPress.org on Slicehost — by default, Slicehost setups don’t come with a mail server, and for reasons only God knows WordPress.org dies with a silent error if PHP can’t mail you your password during an install.  Don’t ask me what the security rationale for that one is.  Happily, WordPress.org is open source and you can quickly hack together a solution — go to the wp-admin/upgrade-functions.php file, comment out (put a # sign in front of ) this line:

wp_new_blog_notification($blog_title, $guessurl, $user_id, $random_password);

and this line:

$random_password = substr(md5(uniqid(microtime())), 0, 6);

replacing it instead with

$random_password = ‘for_love_of_little_apples_change_me’;

Now you can follow wordpress.org’s 5 minute install directions without blowing stuff up.  Or, you could, if your web server was actually serving up your blog yet.  Its not, since you haven’t told it that your second domain exists.

2)  Locate your Apache config file for your Rails installation.  If you did the setup for your Rails site using deprec (*highly* recommended — it will save your sanity and many days of tweaking config files, and it works beautifully with Slicehost), this will be in /usr/local/apache2/conf/apps/nameOfYourApp.conf .  Copy it to another .conf file of your choice.

3)  Working on your second copy, edit the VirtualHost declaration to read

<VirtualHost http://www.nameofyourdomain.com:80&gt;

That is Apache speak for “If the web browser asks for anything under this domain, use the following options”.  You’re going to point the DocumentRoot to /var/www/superheronation/ or wherever you put this domain, and set the ServerName and ServerAlias from whatever your Rails domain is to whatever your WordPress domain is.  Now replace the catchall VirtualHost (VirtualHost *:80) with the same sort of name-based virtual host, in the nameOfYourApp.conf file.

4)  Now for the magic — we want Apache’s awesomely powerful rewrite engine to send the appropriate things to WordPress, while leaving the rest to Rails.  Here’s exactly what you need after RewriteEngine On for the seperate domain case:

# Let apache handle the PHP files – all requests that get past this rule
# are routed to the mongrel cluster (aka Rails)
#  – wordpress installation assumeed to be in ‘public/wordpress’
#  – Options: NC – case insensitive
#  –          QSA – query string append
#  –          L – last rule, aka stop here if rewriterule condition is matched

# Prevent access to .svn directories
  RewriteRule ^(.*/)?\.svn/ – [F,L]
  ErrorDocument 403 “Access Forbidden”

  # Check for maintenance file and redirect all request
  RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
  RewriteCond %{SCRIPT_FILENAME} !maintenance.html
  RewriteRule ^.*$ /system/maintenance.html [L]

  # OMIT THIS LINE if you have don’t want to automatically redirect everything from the domain to the blog.
  RewriteRule ^/$ %{DOCUMENT_ROOT}/wordpress/ [NC,QSA,L]

RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . %{DOCUMENT_ROOT}/wordpress/index.php [L]

 

  # Rewrite to check for Rails cached page
  #RewriteRule ^([^.]+)$ $1.html [QSA]

  # Redirect all non-static requests to cluster
  #RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
  #RewriteRule ^/(.*)$ balancer://deprec_will_fill_this_in_cluster%{REQUEST_URI} [P,QSA,L]

5)  Cleanup tasks:

Make sure Apache knows that .php files are first class citizens by editing /usr/local/apache2/conf/httpd.conf and replacing the line with DirectoryIndex to read

DirectoryIndex index.php index.html

And, at the way bottom of that file, comment out the Include statement and replace the NameVirtualHost directives with

NameVirtualHost http://www.rails_domain.com:80
Include conf/apps/rails_application_name.conf
NameVirtualHost http://www.wordpress_domain_name.com:80
Include conf/apps/wordpress_config_file.conf

That should be it.  You can now restart Apache (“sudo /etc/init.d/httpd restart”), and you should be able to access your WordPress blog and complete installation of it.  I’d HIGHLY recommend changing your Admin password, creating another Admin user with a non-Admin name, and changing your Preferences -> Permalinks to a non-default option which includes your post title, for SEO purposes (the 3rd option works nicely). 

6)  Don’t forget to update whoever holds your DNS records to point your domains to your slice’s IP address.  For me, this involves telling GoDaddy to use ns1.slicehost.net, ns2, and ns3 as DNS servers for superheronation.com, and then going into the Slicehost config and telling Slicehost to point superheronation.com and http://www.superheronation.com to the IP address of the slice I bought.  (Handily listed on the bottom of that screen.)

And there you have it!  Two websites running off of two very different technology stacks on a $20 a month VPS.  They’ll both perform marvelously under load, too…  I hope.

Spending Money, Incurring Headaches

June 7, 2007

Kalzumeus is getting to the point where I want to start periodic deployments to the Internets to test it (yes, yes, that DOES mean I’ll be able to announce it publicly, sometime this month if development doesn’t hit snags), so I went shopping for a hosting service.  I eventually settled on TextDrive, which appears to have a decent reputation for Ruby on Rails hosting.  $124 later I was the proud owner of a new “startup” hosting package for a year.  I’ll be spending another $120 or so later to get a certificate for the domain but for now I don’t have any data that needs https so its a waste.

Sidenote: Its a wonderful age we live in where you can buy 95% of the tools you need to run a business for less than $10 a month.  The founder of Textdrive, a guy named Jason who clearly has some chops in the provisioning enterprise class server deploments department, reckons you should budget 10% of sales for infrastructure.  So, for example, if you plan on getting $10,000 a month in sales your monthly server/colo fees/electricity/etc bill should be about $1,000.  This may well be true for larger installations, but I’m pretty sure you can get by on about $30~$50 a month for $10,000 in sales as a uISV with some care given to app selection.  I’ll break down the math for you some other day, after I have an app and benchmark numbers I can point you to, to demonstrate that I’m not shooting smoke out of my hindquarters.

Anyhow, yesterday was a comedy of errors.  These are 100% my doing — I bit off a lot more than I could chew with technologies I do not have a full conceptual understanding of yet.  Example: I spent three hours trying to manhandle my local SVN repository onto the server.  The textdrive docs got me set up with a new repository in 3 minutes, but actually importing the information to it was a constant battle with Netbeans.  I eventually lost my most recent work to corrupted settings (“What do you MEAN I only have 3 class files?!”) after twiddling stuff with text editors, and had to cleansweep my local copy of everything, then restore from the local repository.  Then I got out Cygwin (bless you, Cygwin), killed all the .svn directories, deleted and remade the remote repository, and imported the newly-checked-out-locally repository into the remote repository.  One more open of Netbeans later and I was done.  Blaaaaah.  On the plus side, both Kalzumeus and Bingo Card Creator now exist safely outside of my hard disk.

Incidentally: its far, far simpler to set up hosting with GoDaddy than it is with TextDrive.  The difference in the level of control you get for TextDrive is night and day though.  I’m eventually going to have both Apache and Lighttpd running on the server, Apache proxying everything and making sure the /blog/ folder goes to WordPress, with Lighttpd putting things to the Rails application.  Try that on Godaddy… if you dare.

Then, despite the fact that it was running rather late, I wanted to get some actual coding done rather than losing a night just to wrangling with tools.  Again, 100% due to my own negligence, I lost a lot of time debugging why a transaction wouldn’t work.  Well, OK, 98% to my own negligence.  You see, Rails takes an awful lot of syntatic shortcuts which are deeply meaningful but documented in, I kid you not, exactly one throw away line in The Book.  And, unfortunately, the emphasis on “create code whose intent is crystal clear” gets lost in some of these shortcuts.

Example: build vs new vs create.  One throwaway line will teach you that create actually creates the object in your database while build/new just create it in memory, a sort of Key Distinction when you’re deciding “What do I need to guard with a transaction block?”  And, for more fun, build and new are interchangeable… except when they’re not.

@user = User.build #OK

@user = User.new #OK, same as above

@new_friend = @user.friends.build #OK

@new_friend = @user.friends.new # No new method on friends!  Whoops!

@new_friend = @users.friends.create # Works, but note different database behavior!

Anyhow, I will soon be establishing a blog at kalzumeus.com (you could visit there now, but its a fairly boring “coming soon” page that was autogenerated).  That will be getting most of my future programming/business oriented and professional development posts.  This way I can keep the blog for the actual product site focused on the customers rather than on myself or an interest the customers don’t share (“helping uISVS make money”).  I won’t be abandoning this blog in the near future, though.