Am I Too Old to Be Senior Developer?

Or, aren’t you too young to be a Senior developer?

I never had the title of Senior Developer until about 3 years ago. By that time, I already have 15 years of experience in creating, deploying and fixing software. This is not counting the years I spent freelancing for various tech companies while I was in college. I have worked in startups and big companies, led teams, launched several products on my own and failed, and numerous times I had to learn stuff not related to coding just to get that freaking software out the door. Suddenly, I feel so old and it feels so jurassic now to remember my Visual Basic program running on Windows 95 - ah the good old days!

I imagine these are more than enough to earn a senior title. On the other hand, I’ve worked with developers with a senior title who produced amateurish work. It made me wonder when should companies give the “senior” title to someone. More importantly, when can a developer, call oneself a senior in this field?

In the early stages of a project, the team usually has a number of technical options. A senior developer can articulate the pros and cons of a technical decision - the why and when, not just the how and what. This could be as simple as avoiding early optimizations, highlighting roadblocks the team will face, or avoiding unnecessary work.

An effective signal to know someone is truly a senior developer (or in that mindset regardless of title) is when the developer looks at context when applying a theory. What problem are we solving? What is important and what is not? When to start and when to stop. How often have you seen over-engineered systems that are flexible in ways that don’t matter and inflexible in ones that do? This is simply because developers want to try the latest cool thing they’ve read.

There is also, looking at the big picture. Not every moment in software development is spent working on change-the-world tasks. It’s quite the contrary. Majority of your time is spent on boring tasks that must be completed to the same quality as any other potentially more interesting tasks. Have you met a developer who planned on resigning if he get to work on a legacy project, regardless if that project generates millions of dollars of revenue? I did. It made me think now, if he was more interested in showing off in his resume rather than adding value to the company.

If my thoughts above would be considered, does this mean a developer with 3 years of coding experience and brings tremendous value to the team, can now be called a senior developer? How about someone with 10 years experience but in reality is just experienced the same year 10 times?

Realization. It’s never about time - it’s the maturity.

On the other hand, how realistically can you gain maturity in a short amount of time? Can you compress 10 years worth of experience into 1?

Success in software development goes beyond technical skills. If you want to learn how to manage a crisis, you have to experience a crisis. If you want to have a high performing team, you have to learn to work together, which takes time. Hey, even when superstar athletes play together they rarely get success in their first two years.

Can we now agree that years of experience is misleading? Worthwhile experiences simply do not happen every day. You can try compressing it similar to what schools do, or read books that’s ideally based on the author’s years of experience. Still, while these are helpful, they are not good enough to deal with messy problems. In actual work, there are lots of compromises and very few assumptions. And you have to rely on your previous success and failures when dealing with new problems.

#til Elixir's Anonymous Function

fn is a keyword that creates an anonymous function.

sum = fn(a,b) ->
  a+b
end

IO.puts sum.(1,2)

Invoke using . (dot) similar to Ruby’s #call. Note the dot is not used for named function calls.

fb = fn
  {0, 0, c} -> "FizzBuzz"
  {0, b, c} -> "Fizz"
  {a, 0, c} -> "Buzz"
  {a, b, c} -> c
end

A single function with multiple implementations depending on the arguments passed. Elixir does not have assignments - yes you heard it right. Instead, it uses pattern matching then it binds the variable to the value. Note each implementation should have the same number of arguments.

IO.puts fb.({0,0,3})
# FizzBuzz
IO.puts fb.({0,2,3})
# Fizz
IO.puts fb.({1,0,3})
# Buzz
IO.puts fb.({1,2,3})
# 3

Since it is just a function, why not define it inside another function.

fizzbuzz = fn(n) ->
  fb = fn
    {0, 0, c} -> "FizzBuzz"
    {0, b, c} -> "Fizz"
    {a, 0, c} -> "Buzz"
    {a, b, c} -> c
  end

  fb.({rem(n,3), rem(n,5), n})
end

IO.puts fizzbuzz.(10)
IO.puts fizzbuzz.(11)
IO.puts fizzbuzz.(12)
IO.puts fizzbuzz.(13)
IO.puts fizzbuzz.(14)
IO.puts fizzbuzz.(15)
IO.puts fizzbuzz.(16)

We now have a baseline FizzBuzz solution with no conditional logic. Note rem(a,b) is an operator that returns the remainder after dividing a by b. The final solution uses the Enum module.

Enum.each(1..100, fn(n) -> IO.puts fizzbuzz.(n) end)

I’ve read the |> pipe operator before, so why not use it. The pipe operator simply takes the result of one expression and passes it as the 1st parameter to the next expression.

(1..100) |> Enum.each(fn(n) -> IO.puts fizzbuzz.(n) end)

Joy Ride in Ruby's Spaceship

Ruby’s <=> operator is commonly referred to as the spaceship in the community. You may not have played with it directly but I bet you have relied on it a lot of times. Because, every time you use sort an array, you are tapping in to the spaceship operator.

Now, why would you care? Because sometimes we need to sort things which does not have a natural ordering computers are accustomed to. Take for example sorting clothes that use S, M, and L to refer to their sizes. And to add more fun, how about putting XS, XL, and XXL into the mix.

Let’s start with a bare class and see what happens.

require 'minitest/autorun'

class Size
  attr_reader :size

  def initialize(size)
    @size = size.to_s.upcase
  end

  def to_s
    @size
  end
end

describe Size do
  let(:sizes) { %w[L S M].map { |s| Size.new(s) } }
  it { sizes.sort.map(&:to_s).must_equal %w[S M L] }

  let(:a) { Size.new('S') }
  let(:b) { Size.new('M') }

  it { (a > b).must_equal false }
  it { (a < b).must_equal true }
end

$> ruby size.rb                                                                                                     [2.1.2]
Run options: --seed 37770

# Running:

EEE

Finished in 0.001207s, 2486.1356 runs/s, 0.0000 assertions/s.

  1) Error:
Size#test_0001_anonymous:
ArgumentError: comparison of Size with Size failed
    size.rb:24:in `sort'
    size.rb:24:in `block (2 levels) in <main>'


  2) Error:
Size#test_0002_anonymous:
NoMethodError: undefined method `>' for #<Size:0x007f8a7bae6c50 @size="S">
    size.rb:29:in `block (2 levels) in <main>'


  3) Error:
Size#test_0003_anonymous:
NoMethodError: undefined method `<' for #<Size:0x007f8a7bae6390 @size="S">
    size.rb:30:in `block (2 levels) in <main>'

3 runs, 0 assertions, 0 failures, 3 errors, 0 skips

Our test failed and that’s good news, isn’t it? The first failure is because the default implementation of <=> doesn’t do all the comparison required to make sort work. To make this work, we need to implement a <=> that returns the following:

  • nil if the comparison does not makes sense
  • -1 if left side is less than right side
  • 1 if left side is greater than right side
  • 0 if left and right are the same

Let’s update our code to use the spaceship.

class Size
  attr_reader :size

  SIZES = %w[S M L].freeze

  def initialize(size)
    @size = size.to_s.upcase
  end

  def to_s
    @size
  end

  def <=>(other)
    position <=> other.position
  end

  protected

  def position
    SIZES.index(size)
  end
end

Some things to ponder in our implementation.

  • In Ruby, operator calls are just method calls where the left side is the receiver and right side is the argument. In other words, this is a <=> b is the same as a.<=>(b)
  • It is common practice to call the argument as other as you know the other object :)
  • We leverage an existing implementation of <=>. The method #index returns the position of an element in the array, which is a Fixnum. The Fixnum class already knows how to compare numbers.
  • We use protected to hide an implementation detail but at the same time allow us to use it within instance methods of objects of the same class.

Now, how about the other test failures? Do we need to implement the < and > operators as well? Fortunately, Ruby got our back. We just need to include the module Comparable and we’re good. But wait, there’s more! By including the Comparable module, we also get <=, >=, and == for free.

Here’s the full implementation with additional test scenarios, including a reverse sort.

require 'minitest/autorun'

class Size
  include Comparable

  SIZES = %w[S M L].freeze

  attr_reader :size

  def initialize(size)
    @size = size.to_s.upcase
  end

  def to_s
    @size
  end

  def <=>(other)
    position <=> other.position
  end

  protected

  def position
    SIZES.index(size)
  end
end

describe Size do
  let(:sizes) { %w[L S M].map { |s| Size.new(s) } }

  it { sizes.sort.map(&:to_s).must_equal %w[S M L] }
  it { sizes.sort { |a,b| b <=> a }.map(&:to_s).must_equal %w[L M S] }

  let(:a) { Size.new('S') }
  let(:b) { Size.new('M') }

  it { (a > b).must_equal false }
  it { (a < b).must_equal true }
  it { (a >= b).must_equal false }
  it { (a <= b).must_equal true }
  it { (a == b).must_equal false }
end

$> ruby size.rb                                                                                                     [2.1.2]
Run options: --seed 44807

# Running:

.......

Finished in 0.001490s, 4698.8003 runs/s, 4698.8003 assertions/s.

7 runs, 7 assertions, 0 failures, 0 errors, 0 skips

Exercise: Software versions often follow the convention major.minor.patch. Create a Version class that takes a string version, e.g. “10.10.3” and implement the <=> operator.

I’m currently reading Effective Ruby by Peter Jones and this post is based on Item 13: Implement Comparison via “<=>” and the Comparable Module.

You Can't Handle the Truth

Quick question. What’s the output of this code in Ruby?

amount = 0
if amount
  puts 'hey'
else
  puts ‘nah'
end

If you answered nah, you’re wrong. But it’s fine because this is one of the biggest gotchas for developers who are new to Ruby. Heck, even seasoned developers like myself sometimes forget this. I blame my college CS professors for putting too much C syntax in my brain.

Ruby has a simple rule for dealing with boolean values: everything is true except false and nil. This also means that every expression and object in Ruby can be evaluated against true or false. For example, you can have a method find that returns an object when it finds one or nil otherwise.

if  o = Customer.find_by(email: ‘stevej@rip.com’)
  puts o.name
else
  puts ‘not found it'
end

But it’s a different story when returning a numeric value because 0 evaluates to true.

false and nil can also be a common source of confusion because you have 2 values that can be false. Consider the default behaviour of Hash, which returns nil if the key does not exist. If you only factor in the nil scenario, you will have a problem when a key returns a false value - a common scenario with code that handles configuration or settings. In the case below, this will output missing key

h = {'a' => 1, 'b' => false}
key = ‘b'
if h[key]
  puts 'found a value'
else
  puts 'missing key'
end

If that’s enough confusion for you, consider this: true, false, and nil are just instances of a class.

irb> true.class
=> TrueClass
irb> false.class
=> FalseClass
irb> nil.class
=> NilClass

They are global variables but you can’t set any value to it which is fine. Otherwise, there will be chaos!

irb> true = 1
SyntaxError: (irb):18: Can't assign to true
true = 1

But, this is Ruby and we can always introduce chaos. Matz, the creator of Ruby, has given us this much power because he trusts that we know what we are doing.

irb> class Bad
irb>   def ==(other)
irb>     true
irb>   end
irb> end

irb> false == Bad.new
=> false
irb> Bad.new == false
=> true

What the heck just happened? Well, == is just another method call - the first is for the FalseClass instance while the second is for the Bad instance.

If you have been using Ruby for a while and wants to become better at it, I suggest you get a copy of Effective Ruby by Peter Jones.

Rails #permitted

I recently upgraded a personal app I use for learning new things in Rails. But when I upgraded from 4.1.4 to 4.1.12 I encountered this familiar error.

Customer.where(auth.slice(:provider, :uid)).first_or_initialize
ActiveModel::ForbiddenAttributesError: ActiveModel::ForbiddenAttributesError
from /Users/greg/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/activemodel-4.1.12/lib/active_model/forbidden_attributes_protection.rb:21:in `sanitize_for_mass_assignment`

Now I remember. It’s one of those mass assignments where you have to specify the ‘permitted’ values before you can continue. In Rails 4, it’s good practice to whitelist the attributes you receive in the controller and it goes something like this:

def user_params
  params.require(:user).permit(:username, :email, :password)
end

# somewhere in the controller
Customer.create(user_params)

Now, let’s use this idiom. It should be easy, right?

> auth.permit(:provider, :uid)
=> nil

Wait, that didn’t go as expected. How about just simply composing the hash?

> Customer.where(provider: auth[:provider], uid: auth[:uid]).first_or_initialize
=> #<Customer:0x007fd9168ffa88>

Interesting. #permit returns nil, using plain hash works, and #slice doesn’t.

> auth.slice(:provider, :uid).class
=> OmniAuth::AuthHash < Hashie::Mash

It shouldn’t matter what auth is as long as it behaves like what the Customer model expects. But what does the Customer model expects? Actually, the error message is telling us what it expects. In Rails, there is this module for mass assignment protection:

# https://github.com/rails/rails/blob/master/activemodel/lib/active_model/forbidden_attributes_protection.rb
module ActiveModel
  # Raised when forbidden attributes are used for mass assignment.
  #
  #   class Person < ActiveRecord::Base
  #   end
  #
  #   params = ActionController::Parameters.new(name: 'Bob')
  #   Person.new(params)
  #   # => ActiveModel::ForbiddenAttributesError
  #
  #   params.permit!
  #   Person.new(params)
  #   # => #<Person id: nil, name: "Bob">
  class ForbiddenAttributesError < StandardError
  end

  module ForbiddenAttributesProtection # :nodoc:
    protected
      def sanitize_for_mass_assignment(attributes)
        if attributes.respond_to?(:permitted?) && !attributes.permitted?
          raise ActiveModel::ForbiddenAttributesError
        else
          attributes
        end
      end
      alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment
  end
end

Nothing fancy here. Rails does a simple check whether to allow mass assignment or not.

> auth.slice(:provider, :uid).permitted?
=> false

> { provider: auth[:provider], uid: auth[:uid] }.permitted?
NoMethodError: undefined method `permitted?' for {:provider=>"facebook", :uid=>"123"}:Hash

OmniAuth::AuthHash does not even allow it. Plain Hash works because it doesn’t even respond to #permitted.

Rebuild Rails Part 4

Now, it is time to build real pages in our super duper Tracks framework. We will support ERB and to do that we need the erubis gem.

# app/controllers/posts_controller.rb
class PostsController < Tracks::Controller
  def index
    locals = { title: "/posts/index" }
    render template: "posts/index", locals: locals
  end
end

# app/views/posts/index.html.erb
hello from tracks <%= title %>

# lib/tracks/controller.rb
def render(options)
  template_name = options.fetch(:template)
  locals = options.fetch(:locals)

  filename = File.join "app/views", "#{template_name}.html.erb"
  template = File.read(filename)
  erb = Erubis::Eruby.new(template)
  erb.result locals
end

$ ruby spec/application_spec.rb
Run options: --seed 12593

# Running:

..

Finished in 0.020498s, 97.5705 runs/s, 195.1410 assertions/s.

2 runs, 4 assertions, 0 failures, 0 errors, 0 skips

Our tests show we are good but it’s not even close to Rails' “magic”. First, let’s use the controller/action convention, i.e. if no template is passed to the render method, we should use the app/views/posts/index.html.erb template. We also modify our Controller to save the env data passed by Rack because we need to check the path.

# lib/tracks/controller.rb
module Tracks
  class Controller
    attr_reader :env, :controller_name, :action_name

    def initialize(env)
      @env = env
      extract_env_info
    end

    def extract_env_info
      _, @controller_name, @action_name, after = path_info.split("/")
    end

    def path_info
      env["PATH_INFO"]
    end

    def extract_template_name
      "#{controller_name}/#{action_name}"
    end
  end
end

We then update our render method to check for the template if it is not passed.

# lib/tracks/controller.rb
     def render(options)
-      template_name = options.fetch(:template)
+      template_name = options.fetch(:template) { extract_template_name }
       locals = options.fetch(:locals)

       filename = File.join "app/views", "#{template_name}.html.erb"
@@ -29,6 +43,5 @@ module Tracks
       erb.result locals
     end

Our next modification involves using the @instance_variables to pass values from the controller to the view files. To do that, we just need to pass the current binding to the eruby instance and it should pickup the instance variables we have in the controller.

In Rails, it is a bit more involved. There is a concept of view context. Rails collects the instance variables from the controller, then duplicates the values into the view context. The Ruby methods #instance_variables, #instance_variable_get, #instance_variable_set allow Rails to accomplish that.

def render(options={})
  template_name = options.fetch(:template) { extract_template_name }
  filename = File.join "app/views", "#{template_name}.html.erb"
  template = File.read(filename)
  erb = Erubis::Eruby.new(template)
  erb.result(binding)
end

We also update our render method and controller because we do not need the locals parameter.

# app/controllers/posts_controller.rb
class PostsController < Tracks::Controller
  def index
    @title = "/posts/index"
    render
  end
end

# app/views/posts/index.html.erb
hello from tracks <%= @title %>

We still have the extra render call in our controller. To remove it, we keep track of the call to render and if there’s no rendered result yet, we call render.

diff --git i/app/controllers/posts_controller.rb w/app/controllers/posts_controller.rb
index f7883e3..d7aa012 100644
--- i/app/controllers/posts_controller.rb
+++ w/app/controllers/posts_controller.rb
@@ -1,6 +1,5 @@
 class PostsController < Tracks::Controller
   def index
     @title = "/posts/index"
-    render
   end
 end
diff --git i/lib/tracks/controller.rb w/lib/tracks/controller.rb
index 0119c1b..3d5d75a 100644
--- i/lib/tracks/controller.rb
+++ w/lib/tracks/controller.rb
@@ -30,7 +30,12 @@ module Tracks

       controller_class_name = controller.capitalize + "Controller"
       controller_class = Object.const_get(controller_class_name)
-      controller_class.new(env).send(action)
+      controller_context = controller_class.new(env)
+      controller_context.send(action)
+
+      if controller_context.rendered_string.nil?
+        controller_context.render
+      end
+
+      controller_context.rendered_string
     end

     def render(options={})
@@ -38,7 +43,7 @@ module Tracks
       filename = File.join "app/views", "#{template_name}.html.erb"
       template = File.read(filename)
       erb = Erubis::Eruby.new(template)
-      erb.result(binding)
+      @rendered_string = erb.result(binding)
     end

We also update our controller and tests to cover the case of using render explicitly.

# app/controllers/posts_controller.rb
class PostsController < Tracks::Controller
  def index
    @title = "/posts/index"
  end

  def show
    @title = "/posts/index"
    render template: "posts/index"
  end
end

# spec/application_spec.rb
require_relative "spec_helper"

describe CrazyApp::Application do
  include Rack::Test::Methods

  def app
    CrazyApp::Application
  end

  it "should respond with /" do
    get "/"
    last_response.ok?.must_equal true
    last_response.body.strip.must_equal "hello from index.html"
  end

  it "should respond with different path" do
    get "/posts/index"
    last_response.ok?.must_equal true
    last_response.body.strip.must_equal "hello from tracks /posts/index"
  end

  it "should respond with different template" do
    get "/posts/show"
    last_response.ok?.must_equal true
    last_response.body.strip.must_equal "hello from tracks /posts/index"
  end
end

Rebuild Rails Part 3

Let’s continue our rebuild rails series by supporting controllers. We simplify things by assuming a controller/action path format and only support GET request.

We adjust our .call implementation to handle the new path format. We also introduce another method #render_controller_action that inspects the path_info and instantiates the right controller using Rails NameController convention.

# lib/tracks.rb
def self.call(env)
  path_info = env["PATH_INFO"]
  if  path_info == "/"
    text = Tracks::Controller.render_default_root
  else
    text = Tracks::Controller.render_controller_action(env)
  end

  [200, {"Content-Type" => "text/html"}, [text] ]
end

# lib/tracks/controller.rb
def self.render_controller_action(env)
  path_info = env["PATH_INFO"]
  _, controller, action, after = path_info.split("/")

  controller_class_name = controller.capitalize + "Controller"
  controller_class = Object.const_get(controller_class_name)
  controller_class.new.send(action)
end

$ ruby spec/application_spec.rb
Run options: --seed 55835
# Running:

.E

Finished in 0.015837s, 126.2865 runs/s, 126.2865 assertions/s.

  1) Error:
CrazyApp::Application#test_0002_should respond with different path:
NameError: uninitialized constant PostsController
    /code/crazy/lib/tracks/controller.rb:13:in `const_get'

When we run the test, you see it failed on PostsController which is what we expect since we haven’t implemented PostsController yet. Let’s add the controller now.

# app/controllers/posts_controller.rb
class PostsController < Tracks::Controller
  def index
    "hello from tracks /posts/index"
  end
end

# config/application.rb
require "./app/controllers/posts_controller"

$ ruby spec/application_spec.rb
Run options: --seed 2475

# Running:
..

Finished in 0.019152s, 104.4277 runs/s, 208.8555 assertions/s.

2 runs, 4 assertions, 0 failures, 0 errors, 0 skips

Automatic loading

Our tests pass but something’s not right. In Rails, there is no need to require every controller (or pretty much anything) to make it work. To support this feature in our framework, we need 2 things:

  • convert PostsController to posts_controller.rb; and
  • auto-require ‘posts_controller’

To tie these 2 together, we tap into Object.const_missing so our framework would know if a class has been used but not yet loaded.

We also update the $LOAD_PATH to include app/controllers folder so Ruby knows where to look.

# lib/tracks.rb
require File.expand_path("../tracks/helper", __FILE__)
require File.expand_path("../tracks/object", __FILE__)

# config/application.rb
require './lib/tracks'
$LOAD_PATH << File.expand_path("../../app/controllers", __FILE__)

module CrazyApp
  class Application < Tracks::Application
  end
end

# lib/tracks/helper.rb
module Tracks
  module Helper
    def self.to_underscore(string)
      string.scan(/[A-Z][a-z]+/).
      join('_').
      downcase
    end
  end
end

# lib/tracks/object.rb
class Object
  def self.const_missing(c)
    require Tracks::Helper.to_underscore(c.to_s)
    const_get(c)
  end
end

Our implementation of .to_underscore is limited compared to what’s supported in Rails.

irb(main):019:0> s = "PostsController"
=> "PostsController"
irb(main):020:0> s.scan(/[A-Z][a-z]+/).join('_').downcase
=> "posts_controller"

Also, since we call const_get inside const_missing, you will run into serious trouble if your file does not contain the expected class. Try changing the name of the class inside app/controllers/posts_controller.rb into something else and you will get this error.

$ ruby spec/application_spec.rb
Run options: --seed 60433

# Running:

.E
Finished in 0.043152s, 46.3478 runs/s, 46.3478 assertions/s.

  1) Error:
CrazyApp::Application#test_0002_should respond with different path:
SystemStackError: stack level too deep
    /Users/greg/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/forwardable.rb:174

2 runs, 2 assertions, 0 failures, 1 errors, 0 skips

Rebuild Rails Part 2

Before we continue with out rebuilding series, we should write some tests first :)

# spec/spec_helper.rb
ENV["RAILS_ENV"] ||= "test"

require "rack/test"
require "minitest/autorun"

require File.expand_path("../../config/application", __FILE__)


# spec/application_spec.rb
require_relative "spec_helper"

describe CrazyApp::Application do
  include Rack::Test::Methods

  def app
    CrazyApp::Application
  end

  it "should respond with /" do
    get "/"
    last_response.ok?.must_equal true
    last_response.body.strip.must_equal "hello from index.html"
  end

  it "should respond with different path" do
    get "/posts"
    last_response.ok?.must_equal true
    last_response.body.strip.must_equal "hello from tracks /posts"
  end

end

Just like your typical Rails test setup, we have a common spec_helper file. We use minitest/autorun which gives us rspec-style DSL out of the box. For our test, we need Rack::Test::Methods to use get and other http methods. We also need an app method that returns our Rack application to make the tests work.

$ ruby spec/application_spec.rb
Run options: --seed 57256

# Running:
..

Finished in 0.015864s, 126.0716 runs/s, 252.1432 assertions/s.

2 runs, 4 assertions, 0 failures, 0 errors, 0 skips

We’re all good. Awesome!

#ruby Array of Hashes Quiz

Found this interesting ruby quiz from AlphaSights. Given an array of hashes, collapse into an array of hashes containing one entry per day. And you can only reference the :time key and not the rest.

log = [
  {time: 201201, x: 2},
  {time: 201201, y: 7},
  {time: 201201, z: 2},
  {time: 201202, a: 3},
  {time: 201202, b: 4},
  {time: 201202, c: 0}
]

# result should be
[
  {time: 201201, x: 2, y: 7, z: 2},
  {time: 201202, a: 3, b: 4, c: 0},
]

The first thing came to mind is to use Enumerable#group_by

grouped = log.group_by { |i| i[:time] }
collapsed = grouped.collect do |t, a|
  no_time_h = a.inject({}) do |others, h|
    others.merge h.reject { |k, v| k.to_sym == :time }
  end

  {time: t}.merge(no_time_h)
end

puts collapsed.inspect

However, after reading this a couple of times, I still find the solution hard to follow. For starter, group_by returns a hash where the values are an array of hashes which brings me back to the original problem even though it is already grouped by time. That I feel made the rest of the code more complicated.

# result of group_by
{201201=>[{:time=>201201, :x=>2}, {:time=>201201, :y=>7}, {:time=>201201, :z=>2}], 201202=>[{:time=>201202, :a=>3}, {:time=>201202, :b=>4}, {:time=>201202, :c=>0}]}

For my second version, I simply loop into the array and compose the hash using :time as the key. Afterwards, use the key-value pair to compose the resulting array. The code may be longer but it is more readable. Remember, Correct, Beautiful, Fast (in That Order).

hash_by_time = {}
log.each do |h|
  time = h[:time]
  others = h.reject { |k,v| k.to_sym == :time }

  if hash_by_time[time]
    hash_by_time[time].merge! others
  else
    hash_by_time[time] = others
  end
end

collapsed = hash_by_time.collect do |k, v|
  {time: k}.merge(v)
end

To the Crazy Ones

Though I’ve seen this video a gazillion times, I still find it fresh and inspiring.

To The Crazy Ones