Jamie Gaskins

Ruby/Rails developer, coffee addict

Better alternative to Rails' before_action to load objects

Aug 25, 2014 @ 07:30pm

Rails controllers have a set of callbacks called before_action and after_action (née before_filter and after_filter). They're pretty handy when you want to do something like this:

class AuthenticatedController < ApplicationController
  before_action :require_authentication

  def require_authentication
    # ...
  end
end

class UserProfilesController < AuthenticatedController
  def edit
     # ...
  end
end

In the AuthenticatedController above, we are telling Rails to run the require_authentication method before each action in that controller. Since the UserProfilesController inherits from it, it also inherits that callback.

One thing I see in a lot of code, though, is this:

class ProductsController < ApplicationController
  before_action :find_product, only: [:show, :edit, :update, :destroy]

  def find_product
    @product = Product.find(params[:id])
  end
end

A practice I've been following is never to modify the state of the controller in a callback. Whenever you do, the fact that @product (or whatever instance variable) is set can be surprising the first time you see it because your controller action didn't set it. In large controllers, this can be very difficult to find and if it's set in an inherited controller, it can be downright frustrating.

Another silly thing about using a callback to do this is that you are specifying which actions will be using this instance variable up front. The callback knows which actions are using the objects it loads. If you add a new method that needs that product, you have to add it to the callback list. This is a code smell; the controller itself has direct knowledge of what objects its actions use.

Also, it could be wrong, or at least outdated. For example, if you changed a particular action to load the object a different way than the way it does in the callback (say you want to eager-load some associated objects to avoid N+1 queries), but you forget to remove that action from the callback's list, you're querying the database at least twice for the same record:

class ProductsController < ApplicationController
  before_action :find_product, only: [:show, :edit, :update, :destroy]

  def show
    @product = Product.eager_load(promotions: :discounts).find(params[:id])
  end

  def find_product
    @product = Product.find(params[:id])
  end
end

This isn't necessarily a problem, but it's inefficient because you're querying the database and throwing away the result. It can cause problems, though, if you change how you query the database for that object, say by querying by a slug instead of an id. And then you have to look through the stack trace to try to find out what is firing that query that's broken, but it's a callback, so stack traces are meaningless.

But DRY!

It's important not to repeat the exact same line in every controller action that deals with a singular object:

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
  end

  def edit
    @product = Product.find(params[:id])
  end

  def update
    @product = Product.find(params[:id])
    # ...
  end

  def destroy
    @product = Product.find(params[:id])
    # ...
  end
end

Here's what I do instead, and I've found it to work very well:

class ProductsController < ApplicationController
  def show
  end

  def edit
  end

  def update
    if product.update_attributes(params[:product])
      # ...
    end
  end

  def destroy
    product.destroy
  end

  private

  def product
    @product ||= Product.find(params[:id])
  end
  helper_method :product

  def new_product
    @new_product ||= Product.new(params[:product])
  end
  helper_method :new_product
end

Notice there is nothing in the show or edit actions, just as if we were using the callback, but we aren't specifying which methods require that object.

To achieve the best of both worlds, we're using memoization, which is just a fancy word for lazy loading. In order to load the Product into memory, we replace our use the @product instance variable with the @product method (as an aside, using accessor methods in your objects is almost always a good idea over using ivars directly).

Wait, there's already a gem for that

You may have seen this pattern before, but not done quite this way. This is almost exactly what the decent_exposure gem does for Rails controllers, but it gives you a handy DSL to do it:

class ProductsController < ApplicationController
  expose(:product) { Product.find(params[:id]) }
end

Turns out, that behavior is wicked-easy to implement:

class ApplicationController < ActionController::Base
  def self.expose(variable_name)
    define_method(variable_name) do
      @exposed_variables ||= {}
      @exposed_variables[variable_name] ||= yield
    end
    helper_method variable_name
  end
end

That is all it takes. Now you can have a nice DSL for these objects in your controller action or your view without adding yet another gem to your project.

It's up to you and/or your team whether you choose the DSL route or to use explicit methods like the first solution I showed. Either way is a much better solution than loading using a callback. In fact, just don't use callbacks. They're almost never what you want.

The Taboo of Salary Discussion

Jun 19, 2014 @ 11:49pm

The phrase "salary discussion" bring about weird feelings in me. My first thought is "salary negotiation", which always scares the hell out of me because:

  • Salary is considered personal and, therefore, something people don't really discuss with each other.
  • Because of that, I don't know how much a developer should expect to get paid.
  • As a candidate for a job, I don't want to ask for too much and the interviewer thinks "this guy has way too high an opinion of himself".
  • I don't want to ask for too little and the interviewer think "this guy cannot possibly be a professional developer".

The other thought that comes to mind is that, relating to the second bullet point above, in order to find out what other developers are getting paid, I have to ask them, which feels invasive. I don't know how anyone will take that question, so I'm always afraid to ask. I've always been told it's like asking how much someone weighs — it's personal.

The problem with that line of thinking is that, if you don't know what others in your field make at a similar level of experience, you don't know how much you should ask for.

I make $80k/year. I grew up pretty poor (single mother making barely over minimum wage), I struggled through college, even served in the military, so in a way that figure seems astronomical to me and it feels kind of weird to even think I deserve more.

Sometimes it doesn't matter …

I live with my roommate in a pretty cheap townhouse in the suburbs of Baltimore, MD (I didn't think it was all that cheap at first, but I'd lived a pretty minimalist lifestyle up until I moved here).

Since my expenses have been low, I haven't been really concerned with whether I've been getting paid as much as I should because I've made more than I need to get by. Worrying about that would've added stress that I just couldn't handle.

… but sometimes it does

I'm about to move into a new apartment in the city with my fiancée where the rent will be about $700/month more. Add to that the fact that I'll be going from splitting the expenses in a cheaper house with a roommate to being the only person with a paycheck in a more expensive place and my expenses/salary ratio just got a lot higher.

To clarify why I'm the only one with a paycheck there, my fiancée won't be able to get a job until she gets a US visa giving her residency, which probably won't be until after we're married.

Since suddenly money will be significantly tighter, I actually do need to know whether I should be getting paid more.

But I stiiiiiiill … haven't found … what I'm looking for

Before I got my current job, my entire career in programming had been freelance, so I have no idea how much money a full-time senior developer should make. So when thinking about this, I realized I didn't know how to approach researching it. I didn't think it'd be a great idea to bluntly ask my employer "Hey, am I being screwed here?" Though I imagine they'd probably be straight with me, even if I asked it like that, the idea of rocking the boat is really scary.

UPDATE: I want to clarify that I didn't mean "am I being screwed" seriously. I use a fair amount of hyperbole in my everyday speech, but I try to make it obvious and my usage of it here was not obvious. I know that my employers aren't intentionally short-changing me.

I asked my friend Alicia about it (she's an absolutely fantastic person if you ever get a chance to meet her). She told me she wasn't sure what a senior developer makes but that junior devs at her last job started at $70k/year.

That kinda stung a bit. A junior dev starts at $70k? My first reaction was: My more-than-a-decade of experience and my depth and breadth of knowledge of Ruby, Unix, and various front-end web technologies is only worth an extra 14%? But my first reactions are almost always pretty extreme and visceral.

So after I calmed down, I realized that maybe $80k was all they could offer at the time. I asked for $100k, but I was the first developer whose work they could bill for. Before they hired me, the only other employees were apprentices and they weren't billing clients for their work (quite commendable, since they acknowledged that it's unfair to charge full contract rates for inexperienced work). So the only money the company made was from billable time that the two of them (the business owners, not the apprentices) put in while also doing the business-development side of things. When I took that into consideration, it seemed a bit more reasonable. (UPDATE: They clarified for me that this was indeed the case.)

I had received offers from two other companies (one here in Baltimore, the other a remote position) and both were in the same ballpark, so I wondered if maybe the only jobs that were paying in the $100k range were in Silicon Valley making feline timesharing apps and you had to work 29 hours a day, 8 days a week. However, Alicia told me I should check payscale.com. When I answered their questions (location, experience, etc), this was what I saw:

Payscale.com results for a software developer in Baltimore

Well, shit. Now I'm really confused. After checking out the various graphs beneath that one, I confirmed that these numbers are for Ruby developers in Baltimore with experience similar to mine. So maybe I am getting paid 20% less than I should be?

Grab that cash with both hands and make a stash

So now I'm actually asking everyone out in the open. I'd like to know how much Ruby/Rails developers with a decade of experience are making in a full-time position. If you work in the industry, if you know someone who does, if you hooked up once with someone who does, I just want to know (well, not about the hookup, just the salary thing).

I don't want to know for the purpose of matching a person with a dollar amount. I'm more interested in finding out on a statistical level.

I'm also not asking because I'm looking for something better. I like my job. The people I work with are great. I'm curious because I'll likely be asking for a raise at my next review, and I'd like to have some numbers ready for that. However, if you're offering a significantly higher amount, you might be able to convince me. ;-)

Just as a closing thought: companies capitalize on the fact that people are afraid to talk about how much they make. It means that they can pay different people different salaries to work in the same position because they'll never know. It contributes to wage gaps based on gender and race. Help yourself and others in your position. Talk about it. By all means, tweet about it (I'll be monitoring the #DevSalary hashtag on Twitter or you can tweet directly at me).

My experience at RubyNation

Jun 07, 2014 @ 11:22pm

Yesterday and today, I attended the RubyNation conference in Silver Spring, Maryland. One of my goals this year was to attend more conferences because I've been trying to meet more people in the community. I attended BohConf last year in Baltimore and it was great, even though it didn't have a Ruby focus.

I suppose I'll get this bit of personal information out of the way because it's very relevant to almost the entire post: I suffer from generalized anxiety disorder. I go to great efforts to mask it because I don't want others to have to deal with it, but the idea of being around a lot of people, quite frankly, terrifies me. Thankfully, RubyNation is a small conference (only 250 attendees according to their website), so I figured it'd be a good place to start and asked if my company would buy a ticket for me. They ended up sponsoring the conference, so they shut down the office on Friday and we all went.

Day 1

I live about halfway between Baltimore and Silver Spring, so I drove down to the conference that morning. I mostly kept to myself or tried to stick with my work posse because being around people I know helps ease the anxiety.

The conference was really great. The presentations were fantastic. Sarah Allen gave a fantastic opening keynote, followed by Eileen Uchitelle talking about ActiveRecord's crazy association logic. A little bit after that, Davy Stevenson gave an awesome talk about the science, art, and craft of programming, which really hit home with me. It was pretty awesome to see three of the first four presentations being given by women.

Everyone was very friendly, but I still felt very nervous about talking to anyone, so I kinda sat and waited for people to approach me or tagged along with my workmates.

We broke for lunch around 1pm, which I'd been dying for because in the scramble to get ready to go I forgot to eat breakfast. My company took about 20 people to Nando's (by the way, excellent food, but overpriced for the quantity and level of service). We couldn't get a bunch of tables together so we broke into groups of 3-4 per table.

I got to sit with Florian Motlik and Russell Osborne. They're really awesome people and I really enjoyed talking with them. Russell actually reminds me a lot of Hampton Catlin, but with a beard. We took a #RubyFriends photo and everything. It was fantastic.

Spoiler alert: anxiety wins

After lunch, we went to a presentation about machine learning with Ruby. During the talk, the presenter mentioned the sexmachine gem, which claims to be able to tell whether a name is male or female. I'd heard of this gem before, so my first thought was "Oh, right, that gem that thinks I'm a woman".

When from the time I started preschool all the way up until my adult life, I heard "Jamie is a girl's name" a lot. A lot of bullies found it fun to torment me because I had the audacity to be given a "girl's name" at birth.

I tell you this solely because this was the presenter's very next slide:

http://i.imgur.com/yxg3Pzf.jpg

"jamie, female"

Notice that the name "Kim" is labeled as "mostly female", but it shows my name as unequivocally "female". Given that it took a lot of effort to manage my anxiety at the conference already, that's all it took to trigger an attack.

I gathered my things as calmly as I could and headed straight to my car. The day was over for me before 2:30pm.

Day 2

I laid in bed this morning wondering if I should even go back today. What if a similar thing happens? What if I'm just too afraid to talk to anyone? Then I'll have wasted all that time just so I could feel terrible about myself.

I think I felt guilty because my company paid for me to be there. Whatever the reason, I got out of bed, showered and went back. I actually managed a decent mood, too.

I'd talked to my fiancée and my roommate last night about my experience yesterday and they told me to remember to take breaks. I realized I hadn't taken any breaks away from the crowd yesterday, so today I did and it really, really helped. They both texted me to make sure I was okay, too. That's a nice feeling.

For lunch, I went to lunch with one of the guys from work and several other people I'd never met (including the aforementioned opening-keynote speaker, Sarah Allen) and we chatted about Ruby, JavaScript, Apple's new Swift programming language, Rails, and the US government — not necessarily in that order. Two out of two lunches at this conference went perfectly.

Nothing else super eventful happened for the rest of the conference. It was just a pleasant time all the way up until I left. Great talks, great people, hell they even served cheesecake! Okay, so one super eventful thing happened. :-)

Jamie at the Bat

The conference experience was totally worth it. I managed to meet some new friends despite my aversion to actually talking to people. I got beaned on my first at-bat, but I stepped back up to the plate and, though I didn't hit it out of the park, I did at least make it on base. I don't know why I went with a baseball metaphor, but I'm keepin' it.

The Seven Methods of Highly Introverted People

For other programmers who are introverted and/or suffer from social anxiety, I absolutely recommend going to a conference. A small one worked well for me. Maybe something like RailsConf or RubyConf would've been significantly more stressful, I'm not sure, but it's tough to imagine a small conference being worse for social anxiety than a large one.

I was lucky enough to have one so close to my house, but Katrina Owen told me in a Ruby Rogues Parley thread that one thing that helps her at conferences is to get a hotel close to the venue. This way, if you start to get overwhelmed, you have a place you can retreat to. I plan to do exactly this if I go to any conferences that are outside of my backyard.

There are almost certain to be moments you'll struggle with. Take breaks. Take breaks proactively and away from people. It's like dehydration: by the time you realize you're thirsty, you're already dehydrated. The later you realize you need a break, the longer break you'll need to calm down.

You'll likely need to be alone for a while afterward and that's okay. Ever since I got home, I've been in the basement avoiding everyone. It's how I unwind after being around people all day.

And now I'm exhausted, so I'm going to bed.

Rails idioms considered harmful

May 17, 2014 @ 08:17pm

I'm not sure if it's Rails itself or the community by which it is surrounded, but something encourages us to treat our Rails apps as if they are simply a web interface to the database. Consider the idiomatic Rails create controller action:

class ThingsController < ApplicationController
  def create
    @thing = Thing.new(params[:thing])

    if @thing.save
      redirect_to @thing, notice: 'Thing was saved.'
    else
      render :new
    end
  end
end

The good news is that, in a lot of cases, this is about all you need. If you're adding a product to your e-store's catalog, publishing an article, or any other time you're just giving the app some data to be presented later, it's perfectly reasonable to treat it as nothing more than an SQL INSERT statement wrapped in a web request.

The problem I see is when people essentially copy and paste this code (whether they actually copy/paste or rewrite it with little to no modification, the end result is the same) for absolutely every create action in their app. If, instead of a ThingsController, this were a UsersController, would you still treat it as a simple CRUD controller?

The reason I'm writing this is that, at work about a week ago, someone suggested I do just that, and it's been bothering me. At the domain level, "creating a user" tells me absolutely nothing. From the user's perspective, they're not creating a user; they are the user. They're creating an account or, put another way, registering.

License, registration, and proof of email address

I've tried renaming the User class to UserAccount or simply Account in the past and it feels awkward. What if other users can be added to the same Account for payment-consolidation purposes? How do you reference the user themselves if calling the registration process "creating a user account", which simply INSERTs a record into the user_accounts table?

First, I've stopped thinking of a lot of scenarios like this as simple CRUD. When a user registers with your app, you're not simply inserting a record into the database. After the user record is saved, you'll usually send a welcome email or an email asking the user to click a link to confirm their email address. Maybe you'll add them to a mailing list or create some default data for them.

Where would you put all of that logic? If you put it into the controller, then you have to duplicate it all any time you add a user outside the normal registration process. This could happen through the admin interface, in a Rails console, a user-invitation process, etc.

A lot of apps I've worked on put that logic in after_create callbacks on the model instead. When setting up associated models, this is definitely an advantage over putting it in the controller because whether you add the user through the normal registration process or through the admin interface, the default data will be setup with no duplication. Sounds great, right? It does until you realize that every time you test a persisted user model you're inserting, at a minimum, twice as many objects into the database as necessary. We maintain an app at work that performs over 10 inserts per user because it follows this pattern (it's okay, we didn't write it like that; it came to us that way).

I consider both of these approaches to be harmful and yet both are used in countless Rails apps that people pay actual money for.

Okay, rocket surgeon, where do I put this crap?

The way I've been dealing with user registrations — and several other processes that aren't basic CRUD — by instantiating what basically amounts to a fake ActiveRecord model. You talk to it somewhat like an ActiveRecord model, but it stores the data elsewhere.

class UserRegistration
  include ActiveModel::Validations

  attr_reader :user

  validates_presence_of :email
  validates_presence_of :password
  validate :passwords_match

  delegate :email, :password, :password_confirmation, to: :user

  def initialize(attributes={})
    @user = User.new(attributes)
  end

  def self.create(attributes={})
    new(attributes).save
  end

  def save
    ActiveRecord::Base.transaction do
      if valid? && user.save
        create_defaults
        UserRegistrationMailer.welcome(user).deliver
        return user # We don't want to return the result of the transaction call
      end
    end
  end

  def passwords_match
    unless user.password == user.password_confirmation
      errors[:base] << 'Passwords do not match'
    end
  end

  def create_defaults
    user.foos.create
    user.bars.create
  end
end

This way, if you use a UserRegistrationsController that uses the idiomatic Rails create action as shown at the top of the article, it will save the user just as you would normally, insert associated models, and send out the welcome email. This makes sure that inserting a user model is as lightweight as possible (the validations are in the registration, so are not run during testing) while still allowing you to insert user records (including fully registered users) easily from elsewhere in the app, such as a Rails console.

As a bonus, if you were including other information than just the user data, such as payment information, you could pass it to the UserRegistration and sort it out there.

class UserRegistration
  # ...

  attr_accessor :user, :credit_card

  def initialize(attributes={})
    @credit_card = CreditCard.new(attributes.slice(:card_number, :expiration_date, :cvv))
    @user = User.new(attributes.slice(:email, :password, :password_confirmation))
  end

  def save
    if valid? && user.save
      payment_gateway.add user
      payment_gateway.charge user, AMOUNT
    end
  end

  def payment_gateway
    @payment_gateway ||= PaymentGateway.new
  end
end

See how in the initialize method, we split out the attributes for the separate models. Without this object to wrap the two models, you'd have to use accepts_nested_attributes_for, which is poor form. Sure, there's a little extra code to maintain, but this isn't a bad thing. Since you're doing validations here, you don't need to do them on the User model. This lets you forget entirely about bypassing validations when adding the user from the admin interface and will increase the speed of any tests that hit the database.

Ask customers for information you actually want

Apr 19, 2014 @ 06:15pm

I came across this tweet from Kurtis Rainbolt Greene:

Tweet about names

If you've ever interacted with a form on any website, you've seen registration forms that ask for things like first and last name as well as your gender. The form asks for first and last name separately so the app can address you by your first name and gender so it can use the "correct" pronouns.

"Correct"

I put "correct" in quotation marks because as much as companies and programmers would like to believe we're doing the right thing by addressing people by their first names or using masculine pronouns just because someone said they were male, what we're actually doing is pigeonholing users into our narrow view of the world.

Hello, #{first_name}!

Patrick McKenzie wrote a blog post a while back entitled The Falsehoods Programmers Believe About Names that I'm not going to attempt to duplicate here. It's pretty much the best article ever about how everything you think you know about names is wrong.

The TL;DR is that names in various parts of the world don't fit our Firstname Lastname convention. In fact, there may be more names in the world don't fit our convention than do.

Additionally, in many cultures, a business addressing a customer by their first name is considered too informal and can insult them or at least make them uncomfortable. Either way, you've lost that person as a customer.

EDIT: Kurtis also recommended that I link to the W3C article titled Personal names around the world. It's a fantastic article that provides background for a lot of the falsehoods listed in Patrick McKenzie's article linked above. It's fairly long, but so worth it.

Download gender binaries or compile from source

When you use the words "male" and "female", you're not referring to gender at all. If you are, you're using the wrong words. Rather, male/female refers to a person at the biological level, which is a weird fucking thing to ask about in your registration form for your new social app. Katrina Owen and I discussed this in a Ruby Rogues Parley thread once and it's really stuck with me since then.

The gist of it is: We're not animals. We're civilized (more or less) people that have layers of abstractions on top of our biology. We're not "male" or "female", we're "men" and "women".

And this brings me to my next point: even if you went with man/woman rather than male/female, you're still not covering all the bases. There are a lot of people in this world who don't fit the man/woman archetypes (regardless of whether that matches their biology). My long-time friend and current roommate is one shining example of this. This friend (who will remain nameless until I get their permission to use their name) struggles pretty constantly with their gender identity. They are neither masculine nor feminine (until a cat walks in the room, then they become Agnes from Despicable Me), but have to pick one very often. And a lot of times, people in the same situation feel excluded because the world wasn't designed to work for them the way it has been for everyone else.

Basically, we're making people feel like shit just so we can figure out whether to use "him" or "her" when referring to them. That is literally the only reason. Even if you don't care how your customer feels, this hurts your bottom line by encouraging them to find a competitor that does cater to them.

Okay, so my shit's broke. Please to tell me how to fix?

We ask people for first and last name so we can infer how to address them. We ask for their gender so we can infer which pronouns to use for them.

Both of these are wrong. The thing they have in common (well, besides being wrong) is that both are used to infer something about them. Rather than trying to guess details based on metadata, ask for the information you want directly.

That is, don't chop up their name to guess at how to address them. Ask them how you should address them.

Don't use third-person pronouns based on a multiple-choice gender response. Ask them what pronouns to use when referring to them. And don't limit pronouns to him/her. Don't even limit it to him/her/them. If you want to be truly inclusive and respectful, get a freeform response for every single pronoun they want used:

  • Subject pronoun (he/she/they)
  • Object pronoun (him/her/them)
  • Possessive pronoun (his/her/their)
  • Possessive gerunds (his/hers/theirs)

The examples in parentheses are just that: examples. Let your customer enter a freeform response in case the pronouns they want aren't there (trust me, cisgender people, there are pronouns transgender people use that you haven't heard of).

I've mentioned this to a few people before and almost universally, their knee-jerk reaction was "But using male/female is so much easier!" This is simply not true. It's slightly (seriously, only slightly) easier for building the form, sure, but then you have to use a function or helper all over your app to select the "correct" pronouns every time you need one.

#{user.name} updated #{possessive_pronoun_for(user)} profile.

But wait, here's the one with user-specified pronouns:

#{user.name} updated #{user.possessive_pronoun} profile.

Since user.possessive_pronoun is a simple reader method and doesn't check anything else, there's no logic that needs to be tested (you are doing TDD, right?). You're just getting data from the model.

Besides, if you put in that little bit of extra effort to offer proper pronoun support, you're very likely to have a lot of very happy customers who will feel included rather than looking at your registration form and saying "Oh, another fucking 'male/female' select box." They'll tell their friends that your service is inclusive and it will forever be known as "the service that doesn't hate marginalized people".

TwitterGithubRss