Tuesday, February 12, 2013

Crossroads

The TL;DR Version

I like working for startups even if it's a gamble and I can lose my job fast. I enjoy the freedom it provides: I work from anywhere I want, I use the tools I like and I am working with passionate people.
I am moving to Chicago in the summer.

The Story

I wrote about frequent job changes and I was hoping my current job would improve the track record. It did not.

There were a couple of warning signs triggering my job search right after my CodeMash talk. I knew the market for Ruby jobs is very limited in Northeast Ohio and I remembered what it was like in Chicago. My wife was OK with moving to a bigger city so I started my search in the greater Chicago area early January.

First of all I wrote a resume. I don't believe in resumes, but I was targeting a geographic area where I have very few contacts. I pinged friends, tried to find out what's available through them. I also felt that time is running out and in an inexplicable moment, I uploaded my resume to Workbridge Associates' web site. Five minutes later my phone rang. We chatted for a few minutes and later that day I started receiving company profiles looking for Rails/Ruby developers. It was great, I found some amazing companies that I would have never found on my own.

I was on the phone quite a bit in the next two weeks. I talked to the headhunter firm and with the companies looking for developers. I must say having a blog and a Github repo helped me a lot. The hiring managers, developers were able to read up on how I think, what problems I solve and what kind of person I am. I was also happy to see that no company wanted to "grill me" on algorithms and different binary trees, they gave me homework assignments instead, which I think is a much better way of figuring out what the developer knows, how he/she thinks.

By the beginning of February my job search narrowed down to four different companies:

I took the Megabus to Chicago on the 3rd of February and by midnight I was in my hotel room anxiously waiting for the next morning to talk with these great companies.

Table XI gave me a homework assignment which I completed ahead of their deadline. I did not build a CLI interface to feed files into the program, I proved out it's behavior with Cucumber features. I also missed a scenario where you could order multiple times from the same dish.
Table XI invests a lot in their culture. I met with them three times while I was in Chicago. We went through my homework assignment, they had me code on the board and I also had to make changes to a Rails app I have not seen before. I believe they are very selective of who they hire, but again, their team seemed great. Oh, and they have their own chef who prepares lunch four times a week.

I met with the team of Hireology next. A printed paper was taped to their door saying "Hireology welcomes Attila Domokos!". I was flattered.

The company's CEO took me out for lunch. We chatted about our families, what I thought of their product while we ate our delicious soup and sandwich at a restaurant that did not look much from the outside.
After lunch the CEO, the head of product development and I sat down for an afternoon session where they talked about their business, about their growth potential and why they believe in their product. I showed them how I write software, what it means to run tests with and without Rails. But I think they were most intrigued by the executed Gherkin scenarios through Cucumber.
I got together with the CTO of their sister company Urban Bound to find out how I code with others.
At the end of the day we went down to the first floor and had a couple of beers at the pub. By the way, in Chicago almost every office building has a pub or a wine bar on the ground floor. You don't even have to leave the building!

The next day I visited Aggrego which is a small startup under a big company. They are working on the next generation of online news media for both news creation and consumption. Their team is extended with a couple of Thoughtworks engineers. I paired with Brian Kung in the morning, we added a few specs to cover his helper that loaded JS or static images for banner ads depending on the type of browser that made the request. I spent most of my time with Dave Willkomm in the afternoon, we test drove an EC2 server facade object they used in their deployment scripts.

The last day I took a quick cab ride to Giveforward, which is a startup in the Wicker Park neighborhood of Chicago. Their fundraising software helps with medical and other related expenses. Their application is written in PHP and they are ready to port it to Ruby on Rails. I met with two engineers and a designer/UX person. After their product demo and going through some their code I did a quick demo of how I build software. We did a quick brainstorming session on URL structures and on database entity relationships. I had a good conversation with them and their app would be interesting to work on.

After all this, which company did I chose?

Well, not so fast! I was about to get on the bus and travel back to Cleveland when I started receiving news from my coworkers about layoffs happening at my current employer. I knew it was coming, I just did not know it would happen so fast.

Before I answer the question I have to tell you: I liked all the companies I visited. None of them was perfect, there were things I liked in one and were missing in others.

However, I felt a special connection with the team at Hireology. I was amazed by how much they cared about their product, the amount of work they put into it and how firmly they believed in their success. I was also applying for the lead engineer position allowing me to define their development process, work out the details of the relationship between product development and engineering. Building an engineering team is something I found very attractive as well.

By the third and last day of my trip, after exchanging a couple of emails with Adam Robinson, the CEO of Hireology I knew I wanted to work with them. Two hours after I heard the layoffs that I was - unfortunately - part of and when I was riding the Megabus back to Cleveland, I received a fantastic offer that I could not refuse. I'll join Hireology today as their CTO, where I will not only oversee their software development, but I'll get to work on other areas that affect the success of the company.

I'll start my work remote, but I'll make frequent, one week long trips to Chicago so I can work with others in person at Hireology. I'll be back in Europe for seven weeks in May and June and we are moving to the North suburbs of Chicago in July.
I love Chicago, I fell in love with that city the first time I visited in 1998. Seeing the sparkling startup life there just proves it to me that my wife and I made a great decision.

So my dear friends in Northeast Ohio, let's spend time together while I am around. I'll try to attend user group meetings, coding events, work from coffee shops or from your office. I may be leaving soon, but I am sure I'll be back. Maybe for CodeMash next year?!

Thursday, February 7, 2013

User Devised - Disconnecting Your User Entity From Devise

We have several Rails apps with duplicated user information. If you're John Smith in one of the apps, we will create a new user with the same information in the others. That's crazy, single sign-on should be the obvious solution.

In order to get there we had to disconnect the authentication mechanism from our User entity. Once we have the same separation in our apps, pulling the authentication out into a service or API should be very easy.
Also, I like to keep the User model application specific. I would much rather have fields that are relevant to our app needs in our User model and not mix devise's fields into that entity.
Here is what I had to do to disconnect devise from our User model.

First of all, I had to generate a member table with the following migration:

class AddMembers < ActiveRecord::Migration
  def change
    create_table(:members) do |t|
      ## Database authenticatable
      t.integer :user_id
      t.string :email,              :null => false, :default => ""
      t.string :encrypted_password, :null => false, :default => ""
      t.string :first_name,         :default => ""
      t.string :last_name,          :default => ""
      t.string :middle_name,        :default => ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, :default => 0
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      # Only if using reconfirmable
      t.string   :unconfirmed_email 

      ## Lockable
      # Only if lock strategy is :failed_attempts
      t.integer  :failed_attempts, :default => 0 
      t.string   :unlock_token
      # Only if unlock strategy is :email or :both
      t.datetime :locked_at

      ## Token authenticatable
      t.string :authentication_token

      # Uncomment below if timestamps were not included
      # in your original model.
      t.timestamps
    end

    add_index :members, :email,                :unique => true
    add_index :members, :reset_password_token, :unique => true
    add_index :members, :confirmation_token,   :unique => true
    add_index :members, :unlock_token,         :unique => true
    add_index :members, :authentication_token, :unique => true
  end

  def self.down
    # By default, we don't want to make any assumption
    # about how to roll back a migration when your
    # model already existed. Please edit below which fields
    # you would like to remove in this migration.
    raise ActiveRecord::IrreversibleMigration
  end
end

Instead of putting all the Member related code into the User model, I created a concern that can be mixed into the User model. This module can be reused in all of the Rails apps to connect the Member entity to the User.

require 'active_support/concern'
require 'active_support/core_ext/module'

module ModelExtensions
  module UserDevised
    extend ActiveSupport::Concern

    included do
      has_one :member
      validates_associated :member

      after_save :save_member, :if => lambda {|u| u.member }
      delegate :last_sign_in_at, :password, :password=,
               :password_confirmation, :password_confirmation=,
               :to => :member

      before_validation do
        self.member.first_name = self.first_name
        self.member.middle_name = self.middle_name
        self.member.last_name = self.last_name
        self.member.email = self.email_address
      end
    end

    def initialize(*params)
      super(*params)
      self.build_member(*params) if member.nil?
    end

    def save_member
      self.member.save!
    end
  end
end

I'd like to mention a couple of things about the module you see above. I am using one-to-one relationship between User and Member. Whenever I save a User, I save the Member as well by using the after_save callback. Admins can overwrite the users' password, I delegate those fields from User to Member, so the Member entity remains hidden behind the User. When a new User is initialized, I create a Member entity as well as you see it in the initialize method.

Devise strategies and overrides are defined in the Member model.

class Member < ActiveRecord::Base
  belongs_to :user

  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable

  devise :database_authenticatable, :lockable, :timeoutable,
         :recoverable, :trackable, :validatable, :omniauthable
         # non-registerable :registerable, :rememberable

  validates :first_name, :last_name, :presence => true

  def active_for_authentication?
    # Comment out the below debug statement to view
    # the properties of the returned self model values.

    super && user.active?
  end

  def confirmed_account?
    (self.last_sign_in_at.nil? == false &&
        self.reset_password_token.nil?)
  end

end

I remember how hard it was to find information on the separation of the application specific User and the devise Member entity when I was working on this. I hope someone will find this code helpful, it would have helped me a lot.