Recruiters are hilarious

May 8th, 2008
I just got a call from someone (Josh? Jason?) at a recruiter. It took him a few seconds to tell me he was, in fact, a recruiter, but once he did, I realized I didn't want to talk, told him, "No thanks. Bye." and hung up the phone. I could hear him keep talking on the other end as I pulled away my phone, but hung up none-the-less. Not two seconds after I put down my phone did he call back, and as such, went to my voicemail box. I couldn't wait to hear what the message might entail. It went something like this:
Hey, Adam. It's [what's-his-name] again. I'm going to send you a check in the mail for $1.55 for etiquette lessons, because it's obvious your mother never taught you that it's rude to hang up on someone.
Wah wah. Have fun at you crappy recruiting job, Jessie. Or maybe it was Jeff?

A case for methodizing your constants

April 4th, 2008

I was testing a bit of Rails code today and came across a spot where I wanted a unit test to make sure my method was going to properly handle an upper limit on associated objects. Think of it like this:

class Release < ActiveRecord::Base
  has_many :tags
  
  MAX_TAGS = 15
  
  def associate(tag)
    self.tags << tag unless self.tags.size == MAX_TAGS
  end
end

At 15 max tags, either I'm going to clutter up my tests with dummy data, or I'm going to drive myself insane creating needless fixtures. I use Mocha to stub out methods in my tests, so I immediately asked myself, "How can I stub a constant?" You can go ahead and alter the constant in your test case using const_set, but that requires that you set it back at the end of your test. Another option is to methodize the constant and stub it out like you would any other method:

class Release < ActiveRecord::Base
  has_many :tags
  
  MAX_TAGS = 15
  
  def self.max_tags
    MAX_TAGS
  end
  
  def associate(tag)
    self.tags << tag unless self.tags.size == Release.max_tags
  end
end

Now, in your test case, you can just stub out your new class method and, provided you're now accessing the constant in your original method through its new wrapper method, you're all set:

class ReleaseTest < ActiveSupport::TestCase
  def test_associate
    Release.stubs(:max_tags).returns(1)
    
    assert_no_difference 'release(:first).tags.reload.size' do
      release(:first).associate(tags(:new))
    end
  end
end

Start Simple, End With a Flourish

April 3rd, 2008

As I continue developing new sites, I find myself falling into development patterns. I'm trying to foster some of them, but there's one habit in particular I'm trying to break: I realize that when I began my most recent project, it was littered with JavaScript-y goodness in an effort to get the "wow" factor from users. As time has worn on, however, I've found myself ripping out more and more of this chrome, to the point where the majority of my site now functions as a "normal" Web application.

One of the first pieces of AJAX I added to this project was the ability to edit a "media bar" while still browsing the page it was on. You'd click an "edit" link in a vertically floated DIV, and it would scale upwards in sort of a light box effect, while fading out its contents and fading in controls to edit those contents. Now, don't get me wrong. This control looked cool (take my word for it). I still have the code lying around in an old Subversion check-in, because I'm a bit proud of it, actually.

The problem with my editable media bar was that ultimately, it was too stylish for its own good. The novelty wore off after a few uses, and then I was stuck with a difficult-to-maintain piece of JavaScript that never seemed to fit with the rest of the site. But I had spent like three days on it! It was so cool! Which is why it had to go: I had spent so much time on it that I had tunnel vision, and when our designer actually got around to seeing it, he didn't even make a peep; it wasn't even a blip on his radar.

Something I had to realize was that if I really wanted this element on the site, I had to also be comfortable taking it away, and reevaluating it. It may come back in a similar form in the future, but for a version 1.0 release, it wasn't worth the time I spent creating it. I've reverted to a standard link to send users to the media bar edit page, and its functionality is as good, if not better, than my initial flashy solution.

So I'm going to be starting simple on my next project. JavaScript and AJAX can wait. It has its place on The Web, but for most everyday user interface patterns, it's just overkill, and the benefits do not outweigh the development time.

Bagels: Cafe Encore

January 26th, 2008

View Larger Map

I started my search for the perfect bagel near Union Square at Cafe Encore. This little cafe is situated on Post Street at Mason Street, about a block away from my office. Its sign is small and is sandwiched between two other signs, so if you're coming from the East, you might miss it.

The cafe itself is small with a few places to sit, a juice fridge, ATM, and a menu with a healthy assortment of both breakfast and lunch options. The two women I've seen working there are very friendly and are willing to chat with the customers. There is a sign behind the register that advises, "No soup for you! (We're temporarily sold out, sorry)" and people seem to get a kick out of it.

Regarding the bagels, I should start by saying that I'm looking at four things with these reviews:

  • Appearance
  • Price
  • Bagel bread
  • Cream cheese
All bagels will be toasted, plain, with plain cream cheese. Now, onto the review for Cafe Encore.

  • Appearance: The bagel doesn't really look good at all. It's smooshed, and reminds me of Lender's bagels. Even when toasted, I feel like it's just going to ooze cream cheese and not have that crunch I love so much when bitten into.
  • Price: The bagel costs $2.50. I'm used to a $2.06 bagel, but I've come to accept that most everything costs more in Union Square, so I'm not too concerned. I will say that $2.50 is on the high end of what I expect to pay for my bagel, though, and anything more than that will just disappoint, unless it's God's gift to bagels.
  • Bagel bread: The appearance says it all: the bread is squishy and lacks character. There's nothing like biting into a fresh, toasted piece of bread, but this comes nowhere close to perfection.
  • Cream cheese: The coverage on the bagel is spotty at best. I pulled off one quarter of the bagel only to realize that 95% of the cream cheese came with it, leaving the other piece beneath it with very little. I'm not looking forward to eating a piece of bagel with almost no cream cheese.

    Another problem is that the cream cheese is really warm and gooey. I like a firm, cold cream cheese to contrast the toasted bagel, but this stuff doesn't hold up to the heat and threatens to turn into a drippy mess.

While I enjoy the cafe itself, I'm going to have to avoid Cafe Encore's bagels from now on. There are too many things wrong with them to keep going back.

A new gym

January 23rd, 2008

I'm starting a new gym. It's actually going to be a chain of gyms, but I'm getting ahead of myself. My new gym is fabulous. It's exclusionary. It's better than any gym you've ever been to.

My gym does not accept membership during the months of November, December, January, nor February.

Moreso, my gym's card scanning system looks at your account during those four months and if it's been more than 8 months, you're politely but firmly told to come back in March. I understand how frustrated members may become, but as I said before, my gym is fabulous, it is exclusionary, and it does not accommodate resolutionists.

Rails Cookie Testing Notes

January 16th, 2008

I ran into a few gotchas I wanted to document about testing and cookies in Rails while developing a user persistence system for a site. The implementation isn't really important, but the nuances of cookie accessing is what I really wanted to focus on.

In a functional test (and possibly an integration test, but I don't really touch those), you can set pre-populated cookie variables like so:

def test_cookie
  @request.cookies[:persist] = 'secret'
  get :index
  assert_response :success
end

The above code will set a cookie named "persist" to the value "secret." The subsequent request (get :index in this case) will have that cookie available to it during the action's lifecycle.

One thing to note, however, is that the cookies hash in a functional test is populated after a request has been made, and is only populated with cookies that were set during the request. What does this mean for us? It means this code will not work (assuming the index page does not set any cookies for us):

def test_cookie
  @request.cookies[:persist] = 'secret'
  get :index
  assert_equal 'secret', cookies['persist'] # This will fail as the cookies hash is empty
end

The reason the cookies hash is empty is because no cookies were set during the call to the index action. Think of the cookies hash as you would the assigns hash: It's only filled if you fill it during the request.

Another issue I ran into was testing for the deletion of a cookie. It's a simple fix, but it's something none-the-less:

def test_cookie
  # This action deletes the "persist" cookie via cookies.delete(:persist)
  delete :destroy
  assert_nil cookies['persist'] # This will fail as the value is actually empty, not nil
  assert cookies['persist'].empty? # This will succeed
end

Finally, unlike the assigns hash, you cannot access the cookies hash after a request using a symbol. You must identify the cookie's key by string, even if you set the cookie's key in your action as a symbol. There's a patch for this (Ticket #5924) but nothing is really being done about it.

def test_cookie
  # This action sets the "persist" cookie to '1'
  post :create
  assert_equal '1', cookies[:persist] # This will fail
  assert_equal '1', cookies['persist'] # This will succeed
end

So there you go. A few issues you might run into when testing with cookies.

New Job, New Food

January 16th, 2008

I started a new job on January 2nd of this year. I'm now working for an incubator of Web applications at a PR firm in San Francisco. The job is going really well, but what I'm really concerned about is the lack of good breakfast food in my new location, Union Square. Granted, I could go to any number of restaurants in the area and get a nice sit-down meal for about $8.00, but I'm really just addicted to bagels.

When I was growing up, my dad used to get a salt-encrusted bagel as often as he could. When his cholesterol started getting too high, my mom made him make us a deal that any bagel he bought would count $5 towards the purchase of a new Super Nintendo game. I think he realized it was a win/win situation, having happy sons and an equally happy stomach, so the plan didn't work very well–at least, not for mom–my brothers and I got at least two games out of it.

There was only one good place to get bagels in Dublin: The Bagel Bakery. I'm glad my dad did the legwork to figure out the best place to buy bagels in my hometown. I think ultimately what made him stop getting so many was the change in ownership. My dad was a people person, and outward image counted a lot towards his coming back to a store. When the new owners were described as, "a little cranky," the bagels stopped coming.

While I understand the downsides of bagels, especially a bagel every morning, I just don't want to give them up. After a lot of searching around SOMA at my old job, I found Cafe Centro in South Park. They have, hands down, the best bagels in SOMA. The bagels themselves are crisp when lightly toasted. The real winner, though, is the cream cheese. A nice, firm cheese is key to a good bagel, and they guys understand that.

My goal in the next few weeks is to document my findings regarding bagels in my new area. I can only hope to find something comparable to The Bagel Bakery and Cafe Centro.

Reconnect your BackgrounDRb database sessions

November 11th, 2007

I ran into a problem with my BackgrounDRb scheduler last night: The MySQL server had to be restarted, and the scheduler got confused because its connection had gone away. By default, a BackgrounDRb worker will not reconnect to its database unless you explicitly tell it to do so. Here's how you can do that.

Read the rest of this entry »

BackgrounDRb Notes

August 12th, 2007

UPDATE: Since this post, a new version of BackgrounDRb has been released, and as such, some, if not most or all of this information should be considered outdated. Unfortunately, I haven't had a chance to check out the new version, but I'll update this page if I end up installing it.

BackgrounDRb is a great Ruby-based scheduler, but its documentation isn't quite as mature as I'd like, so I wanted to jot down some notes to help anyone who's been trying (and possibly failing) to get everything working correctly.

Note #1: You may create a file called config/backgroundrb_schedules.yml to set up default schedules when your BackgrounDRb service starts. This is not to be confused with the -c command-line switch, which is meant to point to the location of your backgroundrb.yml file (its default location is config/backgroundrb.yml).

Note #1a: If you choose to create the above-mentioned YAML file, its format might confuse you a bit, if you're accustomed to Rails' database.yml file or its fixture YAML files: You must prepend each line (except schedule names) with a colon, since BackgrounDRb accesses its YAML variables through symbols. For example:

# The following line should NOT be prepended with a colon (schedule name)
my_worker:
  # Each value inside our my_worker key SHOULD be prepended with a colon
  :class: MyWorker
  :job_key: :my_worker_key

Note #2: If you would like a schedule to repeat at a certain interval, include the :repeat_interval key beneath your :trigger_args parameter (you can't use this parameter with the :cron_trigger trigger type because repeat intervals are built into the cron structure). If you're using a YAML configuration file, it will look something like this:

my_worker:
  :class: MyWorker
  :job_key: :my_worker_key
  :worker_method: :do_work
  :trigger_args:
  :repeat_interval: 2.minutes

The :repeat_interval parameter accepts an integer, in seconds. Since BackgroundDRb is a Rails plugin, you can freely use Rails' time methods (seconds(), minutes(), hours(), etc.) to better format your number. The current (as of August 13, 2007) BackgrounDRb documentation describes :repeat_interval at times, and :interval other times. Setting the :interval key will do nothing.

Note #3: Any call to the logger object from within your workers gets put, by default, into logs/backgroundrb.log. All server calls (startup messages and trigger executions) get logged to logs/backgroundrb_server.log. This may seem trivial, but you could be racking your brain wondering why your worker isn't properly logging its responses. It most likely is, but maybe not in the log file at which you are looking.

Note #4: The BackgrounDRb documentation chooses to access worker classes by their underscored symbol variants (e.g., the MyWorker class would be referenced as :my_worker). You can choose to access your workers by their true class names (e.g., MyWorker) for verbosity's sake.I may add more notes as I come across other gotchas. Check back occasionally for updates.

Singleton can't be dumped

August 12th, 2007

I ran into a new error today, and after digging around my code for a bit, I figured out what was going on. I have an after_filter in my ApplicationController called set_last_url. It came from the session handling code in active_record_store, and its contents look like this:

class ApplicationController < ActionController::Base
  after_filter :set_last_url

  def set_last_url
    session[:last_url] = params
  end
end

For normal purposes, this method should be fine. Its purpose is to capture the last place you visited, and redirect you back after logging in or creating a new account. However, the params hash doesn't always contain information you'd like to store in the session object. In my case, it was an uploaded file. Here is the error along with a snippet of the backtrace:

Processing ItemsController#create (for 127.0.0.1 at 2007-08-12 16:09:38) [POST]
Session ID: 7797d006f6d7668e81a16564043e064f
Parameters: {"action"=>"create", "list_id"=>"1", "controller"=>"items", 
             "item"=>{"name"=>"Futurama", "price"=>"1.00", "url"=>"http://www.futurama.com", 
             "description"=>"DVD Set", "image"=>#}}

TypeError (singleton can't be dumped):
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:83:in `dump'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:83:in `marshal'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:135:in `marshal_data!'
    …

I had two options that became readily apparent: I could change my code such that I only store the controller, action, and possibly ID parameters into the session object. Unfortunately, sometimes I add parameters to the end of my query string, and I don't want to omit them from the redirect URL. The other option was to act on the request method, which is what I ended up doing. In practice, I realized I didn't need to set the last URL after calls that were anything except GET, so I edited my filter definition a bit:

class ApplicationController < ActionController::Base
  after_filter :set_last_url

  def set_last_url
    session[:last_url] = params if request.get?
  end
end

No more error!