Echographia

making things better, making better things

Saturday, October 31, 2009

sometimes I’m not smart

You know those times when you’re looking all over the house for something and then somebody says, “Is that it in your other hand?”

Last night I spent at least an hour trying to figure out why I was getting a nil.rewrite error from Rails’s url_for method. I’ve seen hard-to-debug errors from there before, so I dug right down into it, but I couldn’t figure out why the @url variable was set in some cases and not in others… and then I determined that it actually was being set in all cases, but then it was mysteriously blank by the time url_for needed it….

And then I realized it was because I had a @url variable in my own controller action. And then I went to bed.

Most of the time I really love how dynamic and open Ruby is as a language, but sometimes private instance variables are just what you’d like to have had.

posted by erik at 11:39 am  

Saturday, August 22, 2009

assert_select from arbitrary text

A useful testing method in Rails is assert_select. In a functional test, you can use it to make sure the controller’s response has the HTML you expect:

should "display the suggestion as a link" do
  assert_select 'a[href=?]', @click_url, @display_url
end

… says there should be an <a> tag with the given href attribute and content. Clearer and less brittle than computing a string and searching for it.

But what if you want to assert something about the HTML structure of something that isn’t the controller response? Like, if you want to look for a link in the flash hash?

I just added this to my test_helper.rb:

class ActiveSupport::TestCase
  def assert_select_from(text, *args)
    @selected = HTML::Document.new(text).root.children
    assert_select(*args)
  end
end

Now in my tests I can use the full power of assert_select on an arbitrary string:

should "provide an undo/delete link" do
  assert_select_from(flash[:notice], 'a[href=?]',
    venue_path(assigns(:venue)), 'undo')
end
posted by erik at 8:57 pm  

Monday, August 10, 2009

bugmash

This weekend RailsBridge ran its first Rails BugMash:

The idea is simple: RailsBridge has a lot of energy. The Rails Lighthouse has a lot of open tickets. With the help of some Rails Core team members, we’re going to see what we can do to cut down the number of open tickets, encourage more people to get involved with the Rails source, and have some fun.

Definitely a success – reportedly at least 120 tickets were closed, I’m sure a lot of new people got involved, and the people on #railsbridge seemed to be having fun.
(more…)

posted by erik at 10:34 am  

Saturday, June 27, 2009

deploying Thinking Sphinx on DreamHost PS

The time had come to add a search engine to Touring Machine. I went pretty far down the road with Xapian/Xapit, but:

You may want to trigger [reindexing] via a cron job on a recurring schedule (i.e. every day) to update the Xapian database. However it will only take effect after the Rails application is restarted because the Xapian database is stored in memory.

I see the Xapit Sync project to fix this has since ceased to be vaporware. Well, maybe next time.

Anyway, so RubyTrends tells me the cool kids use Thinking Sphinx, and I want to be cool, but I’m running Touring Machine on the cheap – on a shared DreamHost server – and they don’t want me to run server processes, and although some guy on the Internet says it’s probably fine, I’m leery of defying them. But last week they were running a discount offer on DreamHost PS, their quasi-VPS service – no root, but you can run whatever you want, within the resources (memory, CPU) you pay for.

Sounds like a fine place to run the search engine, but I didn’t want to run the whole Rails app there – since I’ve already got a place to run it that is effectively free. (DreamHost is very cheap, and I run a bunch of sites on it.) So I set out to run a distributed site, with the web app running on DreamHost shared hosting, and the search engine running on DreamHost PS.

It took some setup, but I think I’ve got it working. Here are some notes. As usual, this isn’t a tutorial. You should read everyone else’s instructions – especially the official Thinking Sphinx documentation, and J. Wade Winningham’s post about Capistrano tasks. (I didn’t use his deploy.rb, though.)
(more…)

posted by erik at 5:47 pm  

Friday, June 12, 2009

where it’s at

With the infrastructure in place for the suggestions feature, it’s easy to add new kinds of suggestions – especially if someone else has already done most of the work. For example, given an address, we should be able to look up the names of businesses at that address, and suggest them as venue names.

Yahoo! and Google both do this automatically if you search for something they recognize as an address. But Google doesn’t seem to provide an API. Yahoo! Local does, and the YM4R gem already provides a nice Ruby wrapper for it. So:

  def make_suggestions_based_on_address
    businesses = Ym4r::YahooMaps::BuildingBlock::LocalSearch::get(
      :location =&gt; address.block,
      :radius =&gt; 0.001,
      :results =&gt; 20,
      :query =&gt; '*'
      )
    businesses.each do |business|
      add_suggestion(:name, business.title)
      add_suggestion(:url, business.business_url) unless business.business_url.blank?
    end
  end

You might wonder about the radius parameter. Technically, Yahoo! doesn’t provide the service I want; I want “tell me what’s at this address”, it has “tell me what’s near this address”. With a radius of 0, it returns up to 10 results. After a little experimentation, it looks to me like 0.01 is a tight enough radius to get me all the businesses at an address – or at least the top 10 – and none of the ones next door.

This is actually one of two ways I’m subverting the API here. It’s intended for search, not simple lookup, which is why it’s a little awkward to get a simple address. It’s also why Yahoo! provides multiple URLs for each business. The BusinessUrl is the actual URL for the business’s web site. There’s also a BusinessClickUrl, which “contains extra information that helps [them] to optimize [their] search services. Yahoo! requests that you display the BusinessUrl, but link to the BusinessClickUrl, so they can track usage.

The latest code actually uses both URLs, but I’m going to stop here for now.

posted by erik at 12:22 am  

Wednesday, June 10, 2009

how to help

Here’s how the suggestions feature for Touring Machine works so far.

UI

  • As the user enters data in the new venue form, if we’re able to make any guesses, we pop them up in a panel to the right of the form.
  • Each suggestion is a link. If a user clicks a suggestion link, the suggestion is copied into the corresponding form field, and the field is given focus, so the user can edit it. (For example, if the “page title” suggestion has additional text around the venue name.)

There’s also an “accessible” JavaScript-free UI, but it’s bad, so I’m going to pretend it’s not there.

Client and Server

I started drawing an interaction diagram, but it’s really standard Rails stuff:

  • Model, controller, and view produce the new venue form.
  • When any form input’s value changes, we submit the whole form (as an AJAX GET) to the controller’s suggest action.
  • The server responds with an RJS view that populates and shows the suggestions panel (if there are any suggestions) or hides it (if not).
  • The suggestions are links that call a JavaScript function that copies, highlights, and focuses.
  • Once the user is done editing, the create action just works as normal.

Model

In keeping with the Skinny Controller, Fat Model approach, the only interesting code is in the model. It took me a couple of tries to get a design I liked, but eventually I went with Rails-style magic:

  def make_suggestions
    @suggestions = {}
 
    methods.grep(/^make_suggestions_based_on_/).each do |method|
      attrs = method.sub('make_suggestions_based_on_', '').split('_and_')
      values = attrs.map {|attr| send(attr)}
      send(method) unless values.any?(&:blank?)
    end
 
    suggestions
  end

When asked for suggestions:

  • We look through the Venue object for methods of the form make_suggestions_from_attr_and_other_attr. (We support multiple attributes so that, for example, given a venue name and vague address – city and state – we can look up the exact address.) Call these “suggesters”.
  • We call each suggester only if we have non-blank values for all the named attributes (because how are you going to base a suggestion on the venue’s address if you don’t know its address?). Note that although I’m saying “attributes”, this code works for attributes, associatons, and in fact any method. This is important because a venue’s address is actually a has_one association.
  • The suggesters populate @suggestions, and then we return it.

A small thing I’m pleased by: The make_suggestions method works both as “create suggestion objects” and in the idiomatic sense of “make a suggestion” as “suggest something”.

posted by erik at 1:55 pm  

Tuesday, June 9, 2009

help a brother out

Lately I’ve been trying to help Touring Machine help me. Adding a new venue is a little tedious: Typing, or copy and pasting, a name, URL, description, address, sometimes phone and email, and tags. It’s not a big deal for one venue, but when I’m researching venues in a new city, it gets tiresome. And it’s only going to get worse as I add more fields to the database.

The frustrating part is, all that information is out there already, and it’s not hard to find. I’m usually just copying from Google results, the first or second hit, or a MySpace page:

MySpace.com - Mercury Lounge - 102 - Female - GO-GO-GOLETA, CALIFORNIA - www.myspace.com/mercurylounge

So why am I doing all the Control-C, Control-V?

If I give Touring Machine a URL, it should be able to guess the venue name (page title) and maybe an address or phone number, if they’re on the page. If I supply a name and city, it should get the address, and maybe URL, from a search engine. If they’re on MySpace, why not grab the “About Me” for a first-draft description? It should even be possible to scan the page for words that match popular tags – bar, cafe, restaurant, jazz, rock, folk.

Well, I’ve been working on it. The next few posts – unless I get sidetracked – will describe the design and implementation so far.

posted by erik at 11:39 am  

Saturday, May 23, 2009

Why I switched from acts_as_authenticated to Clearance, and why I didn’t need to

What happened, see, was that I finally started using my aforementioned old Rails app in earnest, and when I did, I discovered that… I couldn’t log out. I didn’t actually need to log out, but it bugged me. I dug around a little. I could log out in development mode, but not production mode. The logout method thought it was working, but when the next page loaded I was still logged in. Something to do with sessions and cookies.

Something down in the depths of the authentication system, and I thought, well, I could try to debug this ancient plugin that I didn’t write, and that has probably never been updated to newer versions of Rails… or I could try out one of those new systems I’ve been reading about. That have documentation, and support, and Rails 2.3 compatibility. How hard could it be?
(more…)

posted by erik at 4:02 pm  

Saturday, May 23, 2009

how I switched from acts_as_authenticated to Clearance

This week I updated the authentication system in an old Rails app from the pre-REST acts_as_authenticated plugin to thoughtbot’s shiny new Clearance gem. What follows isn’t a how-to (because really, who else is going to be making this switch?) but it might be useful for someone. May the Great Gazoogle guide them to these shores.
(more…)

posted by erik at 3:39 pm  

Monday, May 11, 2009

I uncorrupted a git database!

… but it was touch and go there for a while.

RubyMine is out, and I’d felt positive enough about the beta that I wanted to give it a try… but I didn’t want to do it in any existing working directory, in case it did stupid stuff with my files. So, simple enough, just clone the git repository:

[556] RubymineProjects $ git clone ~/Documents/workspace/tourette
Initialized empty Git repository in /Users/eostrom/RubymineProjects/tourette/.git/
fatal: object 3a37e547408f1f955337bade61807ef021608760 is corrupted

Fatal?!? I don’t like the sound of that.
(more…)

posted by erik at 5:45 pm  
« Previous PageNext Page »

Powered by WordPress