Rails Bug: Saving Only "changed" Attributes Hurts Serialization

Posted by John Wulff Sat, 09 Aug 2008 02:10:00 GMT

Really unhappy with this bug. I hope the Rails team agrees that this is a bug unlike my last “bug” report.

If you’re unfarmiliar with “changed?” attribute updating I recommend reading Living on the edge (of Rails) #14 before continuing.

Here it is, the nasty:

class Node < ActiveRecord::Base
  serialize :data
end
>> n = Node.create! :data => { :a => 1 }
=> #<Node id: 417950, data: {:a=>1}>
>> n.id
=> 417950
>> m = Node.find 417950
=> #<Node id: 417950, data: {:a=>1}>
>> m.data[:b] = 2
=> 2
>> m
=> #<Node id: 417950, data: {:b=>2, :a=>1}>
>> m.changed?
=> false
>> m.save!
=> true
>> m = Node.find 417950
=> #<Node id: 417950, data: {:a=>1}>

Changes to the hash are never saved and that makes me really, really sad.

I’ve opened a ticket.

Ruby's sym.to_i 1

Posted by John Wulff Mon, 21 Jul 2008 18:19:00 GMT

Give this a look:

>> :"5".to_i
=> 148447
>> :"5".to_s.to_i
=> 5
Hurts a little doesn’t it? Here’s the why:
Ruby-Doc.org: sym.to_i returns an integer that is unique for each symbol within a particular execution of a program.

Makes sense, sure. But damn was it painful to figure that one out the hard way.

For more fun with Symbols I suggest What is a ruby symbol? – symbols explained.

How I Deploy Typo Using Capistrano 9

Posted by John Wulff Wed, 30 Apr 2008 21:03:00 GMT

UPDATE April 10, 2009 The code below is now slightly depreciated because Typo has moved to GitHub. I’ve pushed updated code, that works with GitHub, to a new GitHub project. Check it out: http://github.com/jwulff/typo_deploy_with_capistrano/tree/master


I’ve been deploying all of my Rails apps with Capistrano to a MySQL, Nginx, Mongrel, God stack for a while.

I hadn’t been keeping my (this) Typo install up to date because it was such a pain to upgrade it compared to my other apps. Not anymore!

Here is my awesome Capistrano deploy task for Typo. (And my database.yml, god.config, and nginx.conf for good measure).

Any way it can be improved?

deploy.rb:
set :application,      'www.johnwulff.com'
set :typo_plugins,     [ 'delicious_sidebar', 'flickr_sidebar' ]
set :user,             'rails_admin'
set :deploy_to,        "/var/www/#{application}"
# If you change the repository be sure to flush the server's cached checkout.
set :repository,       'http://svn.typosphere.org/typo/tags/release_5_0_3'
set :typo_plugins_url, 'http://svn.typosphere.org/typo/plugins/'
# This will substantially speed things up and save typoshphere.or's bandwidth.
set :deploy_via,       :remote_cache

role :app, 'www.johnwulff.com'
role :web, 'www.johnwulff.com'
role :db,  'www.johnwulff.com', :primary => true

namespace :deploy do
  task :default do
    transaction do
      update_code
      typo_plugins.each do |plugin|
        run "cd #{current_release}; script/plugin install #{typo_plugins_url}#{plugin}"
      end
      web.disable
      symlink
      # Copy local database.yml to the new remote release.
      put File.read(File.join(File.dirname(__FILE__), 'database.yml')),
          File.join(current_release, 'config', 'database.yml')
      # Copy local nginx.conf to the new remote release.
      put File.read(File.join(File.dirname(__FILE__), 'nginx.conf')),
          File.join(current_release, 'config', 'nginx.conf')
      # Copy local god.config to the new remote release.
      put File.read(File.join(File.dirname(__FILE__), 'god.config')),
          File.join(current_release, 'config', 'god.config')
      migrate
    end
    # Stop God watches, load the fresh God config that was just uploaded, and
    # then start God watches.
    sudo "god stop #{application}"
    sudo "god load #{File.join deploy_to, 'current', 'config', 'god.config'}"
    sudo "god start #{application}"
    # Reload Nginx configuration without restarting Nginx.
    sudo "/etc/init.d/nginx reload"
    web.enable
    # Don't use sudo for cleanup.  This takes a bit longer than using sudo, but
    # it's not a good idea to give passwordless `sudo rm` permissions to
    # anyone.
    set :use_sudo, false
    cleanup
    set :use_sudo, true
  end
end
database.yml:
production:
  adapter: mysql
  database: johnwulff
  username: johnwulff
  password: *****
  host: localhost
god.config:
#!/usr/bin/env ruby
APPLICATION = 'www.johnwulff.com'
RAILS_ROOT = "/var/www/#{APPLICATION}/current"

%w{9900 9901 9902}.each do |port|
  God.watch do |w|
    pid_file = File.join RAILS_ROOT, 'tmp', 'pids', "mongrel.#{port}.pid"
    log_file = File.join RAILS_ROOT, 'log', "mongrel.#{port}.log"

    w.uid = 'rails_admin'
    w.gid = 'rails_admin'
    w.group = APPLICATION

    w.name = "#{APPLICATION}-mongrel-#{port}"
    w.interval = 30.seconds # default      

    w.start = "mongrel_rails start -d -e production -a 127.0.0.1 -c #{RAILS_ROOT} --user rails_admin --group rails_admin -p #{port} -P #{pid_file} -l #{log_file}"
    w.stop = "mongrel_rails stop -P #{pid_file}"
    w.restart = "mongrel_rails restart -P #{pid_file}"
    w.start_grace = 10.seconds
    w.restart_grace = 10.seconds
    w.pid_file = pid_file

    w.behavior(:clean_pid_file)

    w.start_if do |start|
      start.condition(:process_running) do |c|
        c.notify = 'john'
        c.interval = 5.seconds
        c.running = false
      end
    end

    w.restart_if do |restart|
      restart.condition(:memory_usage) do |c|
        c.notify = 'john'
        c.above = 150.megabytes
        c.times = [3, 5] # 3 out of 5 intervals
      end

      restart.condition(:cpu_usage) do |c|
        c.above = 50.percent
        c.times = 5
      end
    end

    # lifecycle
    w.lifecycle do |on|
      on.condition(:flapping) do |c|
        c.notify = 'john'
        c.to_state = [:start, :restart]
        c.times = 5
        c.within = 5.minute
        c.transition = :unmonitored
        c.retry_in = 10.minutes
        c.retry_times = 5
        c.retry_within = 2.hours
      end
    end
  end
end
nginx.conf:
upstream johnwulff {
  server 127.0.0.1:9900;
  server 127.0.0.1:9901;
  server 127.0.0.1:9902;
}

server {
  server_name johnwulff.com;
  rewrite ^/(.*) http://www.johnwulff.com/$1 permanent;
}

server {  
  listen  80;
  server_name  www.johnwulff.com;
  root /var/www/in_out_open/current/public;
  access_log  /var/www/www.johnwulff.com/shared/log/nginx.access.log  main;
  client_max_body_size  50M;
  # Redirect all traffic to maintenance.html if it exists.
  if (-f $document_root/system/maintenance.html){
    rewrite  ^(.*)$  /system/maintenance.html last;
    break;
  }
  location / {
    # redirect feed requests to feedburner, unless its the feedburner agent
    if ($http_user_agent !~ FeedBurner) {
      rewrite ^/articles.(rss|atom)$ http://feeds.feedburner.com/johnwulff;
      rewrite ^/xml/(atom|rss|rss20)/feed.xml$ http://feeds.feedburner.com/johnwulff;
      rewrite ^/rss$ http://feeds.feedburner.com/johnwulff;
      rewrite ^/feed/(atom|rss|rss20).xml$ http://feeds.feedburner.com/johnwulff;
    }
    # Forward header information so rails can make use of it.
    proxy_set_header  X-Real-IP  $remote_addr;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect false;
    proxy_max_temp_file_size 0;
    # check for index.html for directory index
    # if its there on the filesystem then rewite 
    # the url to add /index.html to the end of it
    # and then break to send it to the next config rules.
    if (-f $request_filename/index.html) {
      rewrite (.*) $1/index.html break;
    }
    # this is the meat of the rails page caching config
    # it adds .html to the end of the url and then checks
    # the filesystem for that file. If it exists, then we
    # rewite the url to have explicit .html on the end 
    # and then send it on its way to the next config rule.
    # if there is no file on the fs then it sets all the 
    # necessary headers and proxies to our upstream mongrels
    if (-f $request_filename.html) {
      rewrite (.*) $1.html break;
    }
    if (!-f $request_filename) {
      proxy_pass http://johnwulff;
      break;
    }
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   html;
  }
}

Install id3lib-ruby gem on OS X Leopard 5

Posted by John Wulff Sun, 16 Mar 2008 22:12:00 GMT

After a few hours of struggling to get id3lib-ruby to install on my Leopard machine I came across http://rubyforge.org/forum/forum.php?forum_id=18544:
Posted By: Robin Stocker
Date: 2007-10-28 18:12
Summary: Install on Mac OS X 10.5 (Leopard)
Project: id3lib-ruby

Here’s a quick tip for people having difficulties installing id3lib-ruby on the newest OS X:

http://trac.macosforge.org/projects/ruby/wiki/Troubleshooting#IcannotbuildrubymysqlonLeopardwithmysql.combinaries

In short, here’s how to install id3lib-ruby (after having installed id3lib):

sudo -s
ARCHFLAGS="-arch i386" gem install id3lib-ruby

Thanks a lot Robin, I was starting to go crazy…

static-gmaps: Google Static Maps API Interface 5

Posted by John Wulff Mon, 25 Feb 2008 21:32:00 GMT

I’ve written my first gem: static-gmaps. It provides a simple interface to the Google Static Maps API.

Usage

map = StaticGmaps::Map.new :center   => [ 40.714728, -73.998672 ],
                           :zoom     => 5,
                           :size     => [ 500, 100 ],
                           :map_type => :roadmap,
                           :key      => 'ABQIAAAA3HdfrnxFAPWyY-aiJUxmqRROGu07ZpoXmWqnENIWDDAF-b-TwhQ4JdQya7b6I5CihUtHtwmmBzWc0Q'
map.markers << StaticGmaps::Marker.new(:latitude => 40,
                                       :longitude => -73,
                                       :color => :blue,
                                       :alpha_character => :b)
map.url => 'http://maps.google.com/staticmap?center=40.714728,-73.998672&key=ABQIAAAA3HdfrnxFAPWyY-aiJUxmqRROGu07ZpoXmWqnENIWDDAF-b-TwhQ4JdQya7b6I5CihUtHtwmmBzWc0Q&map_type=roadmap&markers=40,-73,blueb&size=500x100&zoom=5'

Installation

sudo gem install static-gmaps

Documentation

http://static-gmaps.rubyforge.org

Update February 27, 2007: Added markers.

My Beautiful Controller Spec

Posted by John Wulff Fri, 15 Feb 2008 23:21:00 GMT

Today I spent a few hours refining my controller rspec pattern. I’m really pleased with it.

This controller:
class AssignmentsController < ApplicationController  
  def index
    if params[:person_id]
      @person = Person.find params[:person_id]
      @assignments = @person.assignments
      @title = "Assignments for #{@person}"
    elsif params[:course_id]
      @course = Course.find params[:course_id]
      @assignments = @course.assignments
      @title = "Assignments for #{@course}"
    else
      @assignments = Assignment.find :all
      @title = "All Assignments"
    end

    @ical_url = url_for({ :format => :ics }.merge(params))
    @rss_url = url_for({ :format => :rss }.merge(params))
  end
end
Gets this spec:
require File.dirname(__FILE__) + '/../spec_helper'

describe AssignmentsController do
  describe 'index action GET request with' do
    describe 'no parameters' do
      before(:each)                    do get 'index' end
      it 'should successfully respond' do response.should be_success end
      it 'should assign a title'       do assigns[:title].should == 'All Assignments' end
      it 'should assign assignments'   do assigns[:assignments].should == Assignment.find(:all) end
    end

    describe 'a valid person_id' do
      before(:each)                    do get 'index', :person_id => (@person = test_person) end
      it 'should successfully respond' do response.should be_success end
      it 'should assign a title'       do assigns[:title].should == "Assignments for #{@person}" end
      it 'should assign assignments'   do assigns[:assignments].should == @person.assignments end
    end

    describe 'a valid course_id' do
      before(:each)                    do get 'index', :course_id => (@course = test_course) end
      it 'should successfully respond' do response.should be_success end
      it 'should assign a title'       do assigns[:title].should == "Assignments for #{@course}" end
      it 'should assign assignments'   do assigns[:assignments].should == @course.assignments end
    end

    describe 'any valid parameters' do
      before(:each)                    do get 'index' end
      it 'should assign a rss_url'     do assigns[:rss_url].should == 'http://test.host/assignments.rss' end
      it 'should assign an ical_url'   do assigns[:ical_url].should == 'http://test.host/assignments.ics' end
    end
  end
end

Liberal use of nesting, alignment, and inline assignments, hooray.

Beautiful specs mean I’m that much more likely to actually write them…

Can it get any better?

Rake Task for Setting up TextMate Project 1

Posted by John Wulff Tue, 26 Jun 2007 20:32:00 GMT

desc "Creates a TextMate project."
task :mate do
  puts "Creating TextMate project..."
  system("cd #{RAILS_ROOT} && mate INSTALL README Rakefile app config db/migrate doc lib public script test vendor")
end
Sets up a TextMate project for you, leaving out unnecessary stuff (log, tmp, etc.) I hate saving TextMate project files. I much prefer to just use this task.
rake mate

Validation Assertions

Posted by John Wulff Tue, 26 Jun 2007 19:06:00 GMT

I like to test my validations. While I don’t really care to test the underlying code that runs the validations (I trust the Rails team to make sure they work as advertised) I do like the reassurance that I didn’t screw up. So, I’ve written this small plugin to help me test my validations.

It works simply. In one of your models you’ve got a validation like this:
class User < ActiveRecord::Base
  validates_presence_of :name
  validates_length_of :name, :in => 4..26
  validates_uniqueness_of :name
end
To test the validations:
class UserTest < Test::Unit::TestCase  
  # Assert that a User's name must exist for validations to pass.
  def test_validates_presence_of_name
    assert_validates_presence_of User, :name
  end

  # Assert that a User's name must be at least 4 characters in length for
  # validations to pass.
  def test_validates_minimium_length_of_name
    assert_validates_minimum_length_of User, :name, 4
  end

  # Assert that a User's name must be less than 26 characters in length for
  # validations to pass.
  def test_validates_maximum_length_of_name
    assert_validates_maximum_length_of User, :name, 26
  end

  # Assert that a User's name must be unique for validations to pass.
  def test_validates_uniqueness_of_name
    assert_validates_uniqueness_of User, :name
  end
end

That’s it. Now you can rest easy know that your validations are really, really, really in place.

To install the plugin:
script/plugin install http://validation-assertions.googlecode.com/svn/trunk/validation_assertions/

Google Code Project Page

Associations are the Best

Posted by John Wulff Sun, 10 Jun 2007 09:58:00 GMT

Imagine an application with simple Person and Article models. The Article model has a foreign key: person_id. In this application the best way to get the latest Article for a Person is:
class Person < ActiveRecord::Base
  has_one :latest_article,
          :class_name => 'Article',
          :order => 'articles.published_at DESC'
end
Why an association? Because associations are great. Sure, you’ve probably already got an association for a Person’s Articles. Something like:
has_many :articles,
         :order => 'articles.published_at DESC'
And, you could just leverage that association to get the same result:
person.articles.first
But that’s bad because you’d be loading all the Person’s Articles form the database just to snag the most recent one. You could also do something like this:
class Person < ActiveRecord::Base
  def latest_article
    articles.find :first,
                  :conditions => 'articles.published_at DESC'
  end
end

That’d be a lot better than grabbing all the Articles but it still wouldn’t be great. Multiple calls to person.latest_article would execute multiple calls to the database. Slow.

Using associations is sexier than a simple finder method because you get caching for free. After the first person.latest_article call subsequent calls hit the cache. Very nice.

Now, for the impetus of this post. I was implementing just what I’ve outlined here, only slightly more complex. Instead of Articles simply belonging to People, in my application Articles belong to People through Mentions.

In this scenario, the latest_article association shown above doesn’t work because you cannot (yet) have has_one_through associations. You can do has_many_through to your hearts content, just no has_one_through.

I’m not the first to want this feature.

Much like my ticket regarding the lame behavior of validates_presence_of, it looks like this won’t be resolved soon.

Oh well, I guess I’ll have to write a dreaded finder method.

RailsConf 2007 Highlights

Posted by John Wulff Mon, 21 May 2007 19:06:00 GMT

In no particular order:

Xen and the Art of Rails Deployment got me excited about going Xen.

Kickin’ Ass with Cache-Fu made Memcached approachable.

Standing on the Shoulders of Giants by Adam Keys was funny.

Your First Day with JRuby on Rails showed off deploying a Rails app as a WAR to GlassFish.

Custom Rails Helpers: Keeping Your Views DRY had some neat view tricks.

Jamis Buck and Michael Koziarski talked about The Rails Way.

Ze Frank, who I knew nothing about prior to the conference, turned out to not only be hilarious but brilliant.

And best of all, RailsConf 2008 will be in Portland!