Feed on
Posts
Comments

nested error_messages_for

I think lots of folks have run into this problem of using error_messages_for to display errors of nested or subordinate objects. The current Rails method doesn't handle it well. In addition, there's the problem of not being able to specify the full message. So... I tackled this problem today and came up with a pretty good solution using recursion.

def nested_error_messages_for(object)
    object = instance_variable_get("@#{object}") if ([Symbol, String].member? object.class)
      unless object.nil? || object.errors.count.zero?     
        error_messages = object.errors.to_a.uniq.map do |key, value|
          object2 = object.send(key)
          if object2.is_a?(Array)
            object2.collect {|obj| content_tag(:li, nested_error_messages_for(obj)) }
          elsif object2.is_a?(ActiveRecord::Base)
            content_tag(:li, nested_error_messages_for(object2))
          elsif value.match(/^\^/)
            content_tag(:li, value[1..value.length])
          else
            content_tag(:li, "#{key.underscore.split('_').join(' ').humanize} #{value}")
          end
        end
        content_tag(:div,
          content_tag(:div, "#{object.class} has errors", :class => 'error_field') +
          content_tag(:ul, error_messages),
          :class => 'error_block')
      else
        ''
      end
  end

To use it, just replace your current call to error_messages_for with nested_error_messages_for. There's nothing extra that needs to be done in the controller as with some recipes. You'll also want to make it look good with some CSS.

I'm pretty sure there's more that could be done with this, but thought I'd present it here first and try to get some feedback before turning it into a gem. Let me know how it works for you.

I just implemented a statistics page for an application and found that I was using this pattern over and over again:

User.count(:conditions => ['created_at> ?', 30.days.ago])

Here's a simple extension I made to ActiveRecord to DRY it up:

module ActiveRecord
  class Base
      def self.count_since(time_ago)
        count(:conditions => ['created_at> ?', time_ago])
      end
  end
end

Put that snippet in a file in your lib directory. I called mine rails_extensions.rb. Then add require 'rails_extensions' to the bottom of your environment.rb file.

Now you can just do:

User.count_since(30.days.ago)

That's a little cleaner, don't you think?

Uninstall Apache

So, I'm sure many others have made the same mistake. You downloaded the latest Apache, did the 3 step:

  1. configure --prefix=/usr/local --enable-mods-shared=all --enable-ssl --enable-proxy
  2. make
  3. sudo make install

Doh! That's probably not what you wanted. Now you have stuff like

  • /usr/local/build
  • /usr/local/icons

You'd have been better off going with Apache 2's default prefix which is /usr/local/apache2. The problem is there's no uninstall! If that's happened to you and you just made the mistake a short time ago, try this:

  1. Make sure you have enough free space for a backup of /usr/local

    $ sudo du -sh /usr/local
    2.2G /usr/local

    $ df -h

  2. Back it up

    $ tar cvf /tmp/usr_local.tar /usr/local

  3. Find out which files you just installed

    $ sudo find . -type f -newerct '60 minutes ago' > /tmp/uninstall_files.txt

    $ sudo find . -type d -newerct '60 minutes ago' > /tmp/uninstall_dirs.txt

  4. Inspect the file you just created and remove things that don't belong (e.g. mysql)
  5. Remove the files (don't get creative and add -r to rm -- you did backup, right?)

    $ cat uninstall_files.txt | sudo xargs rm

    $ cat uninstall_dirs.txt | sudo xargs rmdir

Now, take off that prefix and try the 3-step again.

Autotest CPU Fix

Update 1/15/2008
Autotest and rspec both posted updates today. Now, the way to fix this issue is slightly different:

Autotest.add_hook :run do |autotest|
  autotest.add_exception(/^\.\/vendor/)
  autotest.add_exception(/\.svn/)
end

Thanks Ryan and David for the updates!

I've been noticing in my current project that running autotest was constantly consuming about 25-30% of my cpu and causing my macbook pro to run really hot. I did a little googling and found this discussion on the topic.

For me, my problem was definitely the vendor directory (~3500 files in 4 plugins: rspec, rspec_on_rails, restful_open_id_authentication and active_merchant). I tried excluding the entire directory by adding this to my ~/.autotest file:

Autotest.add_hook :initialize do |autotest|
   autotest.exceptions <<%r%^\./(?:coverage|db|doc|log|public|script|vendor|previous_failures.txt)%
end

However, that didn't work. After some investigation, I found out that the rspec_on_rails plugin in my vendor directory was subclassing Autotest::Rspec and setting it's own exceptions string like this:

def initialize # :nodoc:
  super
  @exceptions = %r%^\./(?:coverage|db|doc|log|public|script|vendor\/rails|previous_failures.txt)%
...

See the problem? It dutifully calls super so AutoTest:initialize can do it's stuff (including calling my hook) and then wipes out the exception string with it's own.

So, I browsed the lib/autotest.rb code and found another hook. Adding this to my ~/.autotest now lowers the cpu usage to about 5%. Huzzah!

Autotest.add_hook :run do |autotest|
  autotest.exceptions = Regexp.union(/^\.\/vendor/, autotest.exceptions)
end

On Tuesday, Forbes posted a bizarre article, Fear Among Facebook Developers, that seems to suggest if you're not a big brand on the Internet then you should just pack your bags and go home.

The issue is that Facebook is going to start putting more of its own ads throughout the site. Big surprise. However, Facebook also allows apps to generally put whatever they want in the application canvas, including ads. If Facebook advertises something else outside the canvas then it's not that big a deal to me. As a developer, I don't really need them to provide me an "ad widget" as the author suggests; I can use Google Ads, The Deck or whatever.

If a small application developer is under the illusion that he can win a fight with a big-brand widget, that’s a bad plan, says Gerd Leonhard, chief executive of Sonific, which recently launched a music widget on Facebook.

Strange comment. "Sonific" doesn't sound like a big brand to me, and yet they just launched a music widget? Good for them. However, it seems to me like they're trying to scare off competition. Also, the net is the quintessential mechanism for some little guy knocking over "big brands". In fact, that's what Facebook is!

madlibs_car_logo.png

A couple months ago, we launched our first Facebook app for I'm In. Within a week about 1000 people had installed it to check it out. That's pretty cool, considering all we had to do was get it added to the Facebook directory. So, over the long run will it generate more transactions for I'm In? I think so, but time will tell. For a site like that, it wouldn't take too many added sales to cover the cost of building an app like Mad Libs™.

acts_as_amazon_product

Today we released a new Ruby Gem that makes integrating with Amazon E-Commerce Service (ECS) a snap. It's called acts_as_amazon_product and you can find it on RubyForge.

All that's necessary to integrate any of your existing models with Amazon is to add a require line to the top of your model file and then an acts_as_amazon_product line just inside your class definition.

require 'acts_as_amazon_product'

class Book <ActiveRecord::Base
  acts_as_amazon_product(
      :asin => 'isbn', :name => 'title',
      :access_key => '0123456',
      :associate_tag => 'assoc-20')
end

After this, you can access Amazon data in your controllers or views like this:

@book = Book.new(:title => 'Getting Things Done')
@book.amazon.isbn
@book.amazon.title
@book.amazon.author
@book.amazon.small_image_url

@book.amazon.get('itemattributes/foobar')

The code does not require changing any current database tables. It only requires adding one migration for a single new table used to cache responses from Amazon:

ActiveRecord::Base.connection.create_table :amazon_products do |t|
  t.column :asin, :string
  t.column :xml, :text
  t.column :created_at, :datetime, :null => false
  t.column :amazonable_id, :integer, :default => 0, :null => false
  t.column :amazonable_type, :string, :limit => 15, :default => "", :null => false
end

I just found out that the U.S. Patent office issued patent number 7,188,176 naming me as primary inventor for an "Apparatus, system, and method for maintaining a persistent data state on a communications network". I'm pretty stunned. I did the work for this back in 1998 for Priceline and they filed for the patent in 2000. So, I guess I'm now "officially" an Inventor.

Apache Log Quick Summary

Sometimes I set up a quick site for a client and want to summarize hits without setting up awstats. So, I wrote a quick and dirty script to sum up the hits per day in Apache's access_log file.

Example:

$ cklog access_log

04-15-2007: 21
04-16-2007: 2134
04-17-2007: 304
04-18-2007: 6960
04-19-2007: 951
04-20-2007: 412

Here's the script:

require 'date'

daily = Hash.new
File.open(ARGV[0] || "access_log", "r") do |file|
  while line = file.gets
    if line =~ /(\d{2}\/\w{3}\/\d{4}).*GET\s([^\?\s]+)/
      date = Date.strptime $1, '%d/%b/%Y'
      daily[date] = 0 if daily[date].nil?
      daily[date] + 1
    end
  end
end

daily.sort.each {|d, f|
  puts "#{d.strftime '%m-%d-%Y'}: #{f}"
}

Ruby Web Crawler

Here's a web crawler I wrote awhile back. It's pretty simple, but does the job. If you need something more, you might try rdig or Nutch.

You can run this as a stand-alone script and just pass in the URL to crawl as an argument.

require 'net/http'
require 'uri'

class SiteCrawler
  def initialize(url)
    @site_uri = URI.parse(url)
    @site_uri.path = "/" if @site_uri.path == ""
    @visited = Hash.new
    @queue = Array.new
    addPath(@site_uri.path)
    puts "Initialized site crawl for #{@site_uri}"
  end

  def addPath(path)
    @queue.push path
    @visited[path] = false
  end

  def getPage(path)
    begin
      uri = @site_uri.clone
      uri.path = uri.path + path if path != "/"
      puts "getting #{uri}"
      response = Net::HTTP.get_response(uri)
    rescue Exception
      puts "Error: #{$!}"
      return ""
    end
    return response.body
  end

  def queueLocalLinks(html)
    html.scan(/<a href\s*=\s*["']([^"]+)"/i) {|w|
      uri = URI.parse("
#{w}")
      if !@visited.has_key?(uri.path) and
         (uri.relative? or uri.host == @site_uri.host)
        addPath(uri.path)
      end
    }
  end

  def crawlSite()
    while (!@queue.empty?)
      uri = @queue.shift
      page = getPage(uri)
      yield uri, page
      queueLocalLinks(page)
      @visited[uri] = true
    end
  end
end

sc = SiteCrawler.new(ARGV[0])
@pages = Array.new
sc.crawlSite { |url, page_text|
  @pages <<url
  # SITE_INDEX <<{ :url => url, :context => page_text }
}

The Google Bus

Unbelievable... Working at the Googleplex gets sweeter all the time.

[Google] now ferries about 1,200 employees to and from Google daily--nearly one-fourth of its local work force--aboard 32 shuttle buses equipped with comfortable leather seats and wireless Internet access. Bicycles are allowed on exterior racks, and dogs on forward seats, or on their owners' laps if the buses run full.

Older Posts »