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.
Posted in rails, ruby | No Comments »
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?
Posted in rails, ruby | No Comments »
So, I'm sure many others have made the same mistake. You downloaded the latest Apache, did the 3 step:
- configure --prefix=/usr/local --enable-mods-shared=all --enable-ssl --enable-proxy
- make
- 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:
- Make sure you have enough free space for a backup of /usr/local
$ sudo du -sh /usr/local
2.2G /usr/local
$ df -h
- Back it up
$ tar cvf /tmp/usr_local.tar /usr/local
- 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
- Inspect the file you just created and remove things that don't belong (e.g. mysql)
- 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.
Posted in apache, linux | No Comments »
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
Posted in ruby | 8 Comments »
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!

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™.
Posted in Facebook | No Comments »
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
Posted in Amazon, ruby | No Comments »
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.
Posted in programming | 2 Comments »
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}"
}
Posted in ruby | No Comments »
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 }
}
Posted in ruby | 6 Comments »
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.
Posted in news | No Comments »