Skip to main content

Benjamin Oakes

Photo of Ben Oakes

Hi, I'm Ben Oakes and this is my geek blog. Currently, I'm a Ruby/JavaScript Developer at Liaison. Previously, I was a Developer at Continuity and Hedgeye, a Research Assistant in the Early Social Cognition Lab at Yale University and a student at the University of Iowa. I also organize TechCorridor.io, ICRuby, OpenHack Iowa City, and previously organized NewHaven.rb. I have an amazing wife named Danielle Oakes.

Filtering for the Rails category. Clear

chrismccord/render_sync

by Ben

Real-time Rails Partials

Source: chrismccord/render_sync

ActiveRecord partial_updates broken when duping with Single Table Inheritance

by Ben

We ran into a strange bug in ActiveRecord when upgrading our Rails 3.2.x app to Rails 4.0.x. The bug was that we would dup a record, change some values, save it, and only the values we had changed would be used in the INSERT statement. That is, none of the values that came from the duped object would be used. It turned out to be a bug in the implementation of partial_updates in ActiveRecord, but the cause wasn’t as obvious as we first thought.

Because we tracked it down to partial_updates, our first inclination was just to disable partial_updates like so:

ActiveRecord::Base.partial_updates = false 

That definitely worked, but seemed like disabling a default Rails feature shouldn’t have been necessary. We tracked the problem down so that we could keep using partial_updates.

Here’s some code that reproduces our issue:

# class CreateTestUsers < ActiveRecord::Migration
#   def change
#     create_table :test_users do |t| 
#       t.string :name, :null => false
#       t.string :email
#       t.string :type
#   
#       t.timestamps
#     end 
#   end 
# end 

class TestUser < ActiveRecord::Base
  validates :email, :presence => true
  validates :name, :presence => true
end 

class UserA < TestUser
end 

class UserB < TestUser
end

original_user = UserA.create(:email => 'johndoe@gmail.com',
                             :name => 'John Doe')

dup_user = original_user.dup.becomes(UserB)
dup_user[:type] = 'UserB'
dup_user.email = 'dup@gmail.com'
dup_user.save! # exception!
# ActiveRecord::StatementInvalid: Mysql2::Error: Field 'name' doesn't have
# a default value: INSERT INTO `test_users` (`created_at`, `email`, `type`,
# `updated_at`) VALUES ('2014-03-03 16:33:23', 'dup@gmail.com', 'UserB',
# '2014-03-03 16:33:23')

Certainly not the most common of use cases; it was clear that STI was causing the issue as we worked on creating our minimal test case.

After reviewing how becomes works, we came to the conclusion that it was mangling the state of the object in the way that caused the bug. As you might expect, changing the call order made the difference.

This alternative works with partial_updates enabled:

dup_user = original_user.becomes!(UserB).dup
dup_user.email = 'dup@gmail.com'
dup_user.save!

One could argue that there’s still a bug to fix in Rails, but this is such a corner case that we probably have one of very few codebases that had this issue. We might take the time to submit a pull request, but a blog post is a lot more likely to help someone else in the meantime.

Versions:

Phone/Phoner gem: SyntaxError on Rails 3.2.16, 3.2.15, and possibly before

by Ben

From an issue I opened today. We ended up switching to phony instead. Just a reminder to everyone out there that eval is evil!

While upgrading a project to 3.2.16 we ran into the following error:

syntax error, unexpected end-of-input
          unless defined? @@{:instance_writer=>false}

We tracked it down to monkey patching of cattr_accessor. It looks like some code was copied out of ActiveSupport, but ActiveSupport changed to allow a second argument (e.g., {:instance_writer=>false}). The use that’s causing the error is in ActiveRecord::Base.

Backtrace:

[...]/gems/phone-1.2.3/lib/support.rb:47: syntax error, unexpected end-of-input
          unless defined? @@{:instance_writer=>false}
                            ^
        from [...]/gems/phone-1.2.3/lib/support.rb:47:in `block in cattr_accessor'
        from [...]/gems/phone-1.2.3/lib/support.rb:46:in `each'
        from [...]/gems/phone-1.2.3/lib/support.rb:46:in `cattr_accessor'
        from [...]/gems/activerecord-3.2.15/lib/active_record/base.rb:339:in `<class:Base>'
        from [...]/gems/activerecord-3.2.15/lib/active_record/base.rb:333:in `<module:ActiveRecord>'
        from [...]/gems/activerecord-3.2.15/lib/active_record/base.rb:33:in `<top (required)>'

I hope this helps in providing a fix (or at least for other people who run into the problem).

6 Ways to Remove Pain From Feature Testing in Ruby on Rails

by Ben

6 Ways to Remove Pain From Feature Testing in Ruby on Rails.

From the article:

Writing feature tests in Ruby on Rails used to be the most painful part of my development work flow. Now I kind of like it.

The Rails Testing Pyramid

by Ben

The Rails Testing Pyramid.

We’ve fallen into doing most of what this blog post recommends, which was a nice surprise. Personally, I’m not the best at “throwaway tests”, but that’s something I’ll be keeping in mind.

Gist: test behavior and edge cases at the unit level (the most tests, base of pyramid), test core integration and “non-unit” functionality at the service level (fewer tests, middle of pyramid), and test only critical UI flows at the acceptance level (fewest tests, top of pyramid).

Using dom_id with a presenter (or other objects)

by Ben

We have been using presenters much more often in our Rails code, with the goal of having views only use “Mustache-level” logic (conditionals, loops, and interpolation). We’ve been happy with the results so far; it really cleans up the views, and moves logic to easily testable objects. In fact, most tests for presenters can be fast specs and still test well.

I’ve gotten the impression that not everyone does presenters the same way, so before getting to the code I’ll clarify. Our presenters are decorator-like, with a restriction that they only transform values, not make any lasting changes to them. Methods are typically “opted-in” for use in the presenter using def_delegators from Forwardable. For a simple example, PersonPresenter might have a method called full_name that combines first_name and last_name; the Person class only manages the persistance of first_name and last_name. That is, presenters wrap an object with methods for presentation to a human.

Not all of Rails makes this pattern easy to use. We ran into an issue recently when calling dom_id() in a view. This was our code, roughly:

# File: app/presenters/foo_presenter.rb
class FooPresenter
  def initialize(foo)
    @foo = foo
  end

  def bar
    # ...
  end

  # ...
end

# File: app/controllers/foos_controller.rb
class FoosController < ApplicationController
  def show
    foo = Foo.find(params[:id])
    # It's nice to only have to expose a single object to the view.
    @foo_presenter = FooPresenter.new(foo)
  end

  # ...
end
# File: app/views/foos/show.html.erb
<span id="<%= dom_id(@foo_presenter) %>"><%= @foo_presenter.bar %></span>

And this is the error we got:

ActionView::Template::Error (undefined method `to_key' for #<FooPresenter:0x007fc782994a98>):
1: <span id="<%= dom_id(foo_presenter) %>"><%= foo_presenter.bar %></span>
[...]

The obvious fix would be to add a to_key method that just delegates to the model, but that quickly became a rabbit hole. Many more methods were needed, and I quickly gave up.

It turns out that dom_id() follows a protocol in that it will call to_model on whatever’s passed in. We can use that to solve our problem. (ActiveRecord objects just return self, as you might imagine.) Here’s the fix:

# File: app/presenters/foo_presenter.rb
def FooPresenter
  # ... (previous content)

  def to_model
    @foo
  end
end

Now the presenter can be used with dom_id(), delegating to the presented model. It’s a surprisingly simple solution. :)

(Versions: Ruby 2.0.0, Rails 3.2.12)

Fallback font for non-Mac users

by Ben

Recently merged into MailView:

Closes #27

Pretty simple: just add a fallback font family. Without this change, the headers (to, from, etc) are in Times New Roman on Linux, which looks really out of place.

Thanks for maintaining MailView!

Fast Specs

by Ben

I gave a talk on “Fast Specs” at our first round of lightning talks for ICRuby. I’m pretty happy with how it turned out.

Summary: techniques for speeding up the test suite in your Rails app (subsecond specs in some cases)

Cover image of "Fast Specs"

Calculating the next leap year in Ruby

by Ben

I needed to calculate the next leap year today, and was happy to find out about Date.leap? via this StackOverflow question.

However, there wasn’t a built-in way that I could find to get the next leap year. It turned out to be pretty simple, but still worth sharing.

# next_leap_year.rb
# License: MIT

require 'date'

def next_leap_year(year)
  year += 1 until Date.leap?(year)
  year
end

require 'minitest/spec'
require 'minitest/autorun'

describe 'next_leap_year' do
  describe 'given a leap year' do
    it 'returns the same year' do
      assert_equal(next_leap_year(2012), 2012)
    end
  end

  describe 'given a non-leap year' do
    it 'returns the next leap year' do
      assert_equal(next_leap_year(2013), 2016)
    end
  end
end

That’s not doing quite what you think…

by Ben

I recently helped an intern at Hedgeye work through a problem with a database query. Because I’m working in a separate timezone, I ended up making suggestions through a GitHub pull request. We discussed and decided that what I wrote was self-contained enough that I should re-post so it can help others.

:conditions => ["event_type != ?", 'LOGIN'||'LOGOUT'],

I don’t think this is doing quite what you think…

'LOGIN' || 'LOGOUT' # => 'LOGIN'

So this turns into:

where event_type != 'LOGIN'

I’m guessing you meant to do:

where event_type != 'LOGIN' or event_type != 'LOGOUT'

But, believe it or not, != is a MySQL proprietary extension to SQL. It would probably be best to use something that’s a part of ANSI SQL:

where event_type <> 'LOGIN' or event_type <> 'LOGOUT'
-- alternative:
where event_type not in ('LOGIN', 'LOGOUT')

Because these are literals (not user-provided values), there’s no point in sanitization using ?.

Conclusion:

:conditions => "event_type not in ('LOGIN', 'LOGOUT')",