Consulting. Training. Development.

Make love, not Ruby core extensions

It all started from this comment in Devise’s issue tracker. As you can see “logging in” feature didn’t work for the user (well it’s a main feature of the Devise gem and this should work for sure). And I decided to take a deep look at this issue.

So as you can see from the backtrace the error was about order method:

1
ArgumentError (The method .order() must contain arguments):

Yeah, this ArgumentError was added to the Rails 4.0 and it raises when you’re trying to call order method with blank arguments. Honestly, I don’t see any reason for adding such “feature” to Rails (you can find an explanation of this behaviour here). Anyway let’s go further.

Don’t (always) depend on third-party services

TL;DR: Don’t depend on third-party services if you can easily write your on in a couple of hours.

I hate timezones. And I believe you hate them too. But it happens that you have to work with them pretty often. And one day I had to detect timezones based on coordinates. You know, you have latitude and longitude and you need to know a timezone of this place. So what did I do? Right, I’ve googled it and found a couple of services that could do this kind of work for me like Geonames, Askgeo, Google, Yahoo Places and so on.

Geonames API is pretty nice so I’ve gave it a shot. It was working quite good but one day it became very slow. I mean VERY slow: I had to wait about 5-10 seconds to get requested timezone. Actually I don’t know why exactly it happened but it was awful. The application that used timezone feature became unusable.

Delegation on a method by method basis with Forwardable

When we need to reuse code we could use inheriatance – create a new child class and add/override some methods. But it could lead to complex inheritance chains and extra methods which is not needed in a new class. Do we have a better alternative? Sure! It’s object composition.

Favor ‘object composition’ over ‘class inheritance’. (Gang of Four 1995:20)

Object composition allows us to create more reusable pieces of code that could be combined together keeping our code simple and understandable.

Let’s take an example of making a new interface over existing object. For example, we need a SimpleQueue class which is based on Array but have more idiomatic interface – enqueuing using enq method and dequeuing using deq method.

Fast hashing with CityHash

Let’s talk about CityHash.

CityHash is a family of non-cryptographic hash functions, designed for fast hashing of strings. It has 32-, 64-, 128-, and 256-bit variants. © Wikipedia

And when Wikipedia says fast it means really fast. Let’s compare CityHash 128-bit variant to MD5 hash function. The whole idea to compare these functions is about my curiosity. As you know Rails’s Asset Pipeline uses MD5 hash function against the content of assets to generate a name for css, js files. And I was thinking what if we would use CityHash for this task?

And I wrote a benchmark. I used cityhash gem - it’s a Ruby wrapper around C++ lib. And my test file was compiled css file from Github. Here is my benchmark:

1
2
3
4
5
6
7
8
9
10
require 'cityhash'
require 'digest'
require 'benchmark/ips'

test_string = File.read('./test.txt')

Benchmark.ips do |x|
  x.report("CityHash.hash128")      { CityHash.hash128(test_string) }
  x.report("Digest::MD5.hexdigest") { Digest::MD5.hexdigest(test_string) }
end

and there are the results:

1
2
CityHash.hash128        27490.3 (±3.4%) i/s  - 139968 in  5.097615s
Digest::MD5.hexdigest   1705.6 (±0.6%) i/s   - 8619   in  5.053583s

Yeah, CityHash.hash128 is 16x faster that Digest::MD5.hexdigest. Pretty impressive, right? And yes, I know that calculationg asset names is not a Rails’s bottleneck. It was just the first real life scenario of using MD5 hash function on big files that came up in my head.

JFYI, I’ve tested CityHash, MD5, MurmurHash3, and xxHash against files of different size. Here is the results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CityHash.hash128 file_1k         176517.1 (±6.6%) i/s -   885807  in  5.042911s
Digest::MD5.hexdigest file_1k    206991.4 (±5.2%) i/s -   1039926 in  5.038555s
MurmurHash3::V128 file_1k        1057240.9 (±8.2%) i/s -  5246514 in  4.999302s
XXhash.xxh32 file_1k             1582107.2 (±12.6%) i/s - 7788214 in  4.998577s

CityHash.hash128 file_100k       51033.2 (±5.9%) i/s -   257895 in    5.072438s
Digest::MD5.hexdigest file_100k  3720.0 (±1.1%) i/s -    18600 in     5.000583s
MurmurHash3::V128 file_100k      39508.5 (±5.2%) i/s -   196962 in    5.001046s
XXhash.xxh32 file_100k           50274.8 (±1.4%) i/s -   251962 in    5.012696s

CityHash.hash128 file_1mb        6980.9 (±6.1%) i/s -    35152 in     5.057140s
Digest::MD5.hexdigest file_1mb   366.8 (±2.7%) i/s -     1850 in      5.047233s
MurmurHash3::V128 file_1mb       3947.5 (±6.3%) i/s -    19845 in     5.047911s
XXhash.xxh32 file_1mb            4547.8 (±11.9%) i/s -   22795 in     5.091122s

CityHash.hash128 file_10mb       572.1 (±12.8%) i/s -    2856 in      5.085573s
Digest::MD5.hexdigest file_10mb  36.1 (±2.8%) i/s -      183 in       5.069818s
MurmurHash3::V128 file_10mb      369.0 (±7.3%) i/s -     1836 in      5.003449s
XXhash.xxh32 file_10mb           436.7 (±11.0%) i/s -    2160 in      5.013092s

CityHash.hash128 file_100mb      59.0 (±13.6%) i/s -     294 in       5.086478s
Digest::MD5.hexdigest file_100mb 3.6 (±0.0%) i/s -       19 in        5.242694s
MurmurHash3::V128 file_100mb     37.9 (±7.9%) i/s -      189 in       5.037752s
XXhash.xxh32 file_100mb          48.0 (±4.2%) i/s -      240 in       5.005541s

So if you need a fast non-cryptographic hash function you definitely should try CityHash.

Keyword arguments in Ruby 2.0

In computer programming, named parameters or keyword arguments refer to a computer language’s support for function calls that clearly state the name of each parameter within the function call itself. © Wikipedia

Right now (I mean in Ruby <= 1.9.3) you can ‘emulate’ it with passing a hash as an argument to a method:

1
2
3
4
5
def foo(options = {})
  puts "#{options[:bar]} #{options[:buz]}"
end

foo(bar: 'bar', buz: 'buz') # => 'bar buz'

Lightning JSON in Rails

Rendering JSON is pretty easy in Rails:

1
render json: @statuses

This works well if there are small number of records to be returned. But what happens when we need to return 10,000 records at once? Things slow down dramatically and the most time-consuming parts are JSON serialization and database operations.

Include only required attributes

The first obvious thing is generating JSON with only attributes that we need, e.g.:

1
render json: @statuses, methods: [:latitude, :longitude, :timestamp, :virtual_odometer]

Tidying JSON gives over 20% performance:

1
2
default    5.940000   0.080000   6.020000 (  6.094221)
attrs      4.820000   0.010000   4.830000 (  4.932337)

Define everything you want in Ruby

Have you ever thought about which symbols Ruby method name can contain?

1
2
3
4
5
class Wat
  define_method(', .;') { puts 'WAT' }
end

Wat.new.public_send(', .;') # => 'WAT'

Right. It works in Ruby and is even used in ActiveModel codebase as column names in databases can have spaces for instance. So if column name is “total price” you can call this method which was defined by ActiveModel for you as:

1
user.public_send('total price') # WORKS!

And you definetely can’t call it as

1
user.total price

because, you know, it’s impossible for parser to understand this code as a method call.

Actually I’ve discovered this feature when geocoder gem broke our application. Look at the following diff:

1
2
3
4
5
6
 def self.response_attributes
-      %w[place_id, osm_type, osm_id, boundingbox, license,
-         polygonpoints, display_name, class, type, stadium, suburb]
+      %w[place_id osm_type osm_id boundingbox license
+         polygonpoints display_name class type stadium suburb]
 end

Before this was fixed as you can see there was a typo in defining response_attributes array (which is used to define methods with corresponding names). Yeah, extra commas (note: you don’t need to separate items in array if you use %w[] syntax). But it worked OK because we can define methods with commas and other stuff. And thanks to that typo class method wasn’t redefined in our code. Fortunately it’s already fixed.

What’s new in upcoming SimpleForm 2.1.0 version

UPDATE: Good news! Actualy SimpleForm 2.0.3 was released with a bunch of bugfixes and small features that are described below. Full CHANGELOG is here.

Hey, readers! Today I want to share with you some news about upcoming SimpleForm 2.1.0 release. A lot of bugs were fixed and some awesome features were added. So let’s take a look on these goodies:

1. :checked option

Now you can pass an array to :checked option and these values will be checked:

1
f.input :roles, collection: %w[admin user manager editor], as: :check_boxes, checked: %w[user manager]

Just wow, right?

2. nil :collection

Imagine you have such code:

1
2
3
<%= simple_form_for [@project, @story] do |f| %>
  <%= f.association :documents, as: :check_boxes, collection: @documents %>
  ...

So if @documents is a nil object then all documents will be loaded. Looks like a security hole, right? Not anymore. In SimpleForm 2.1.0 f.association creates blank select for blank association. That’s right, mind = blown.

3. config.generate_additional_classes_for

Guys, guys, guys! With new SimpleForm form you can add all these additional CSS classes (like string for string inputs, text for text inputs, etc.) only for wrapper! I can’t believe in this but it’s a pure truth! All you need is to set up config.generate_additional_classes_for with [:wrapper] option:

config/initializers/simple_form.rb:

1
config.generate_additional_classes_for = [:wrapper]

4. checked_value and unchecked_value

Oh. My. God. With new SimpleForm you can specify checked and unchecked values for boolean inputs:

1
f.input :action, as: :boolean, :checked_value => 'on', :unchecked_value => 'off'

And now instead of default 1 for checked value and 0 for unchecked you’ll get on and off. Unbelievable. I was dreaming about it soooooo long.

5. config.wrapper_mappings

And last but not least. Make sure you’re sitting right now. Hey, you, I’m not kidding, take a sit because this last awesome feature will turn your world upside down.

You can specify different wrappers for different input types in your SimpleForm’s config! Take a look at this example below:

config/initializers/simple_form.rb:

1
config.wrapper_mappings = { :string => :prepend }

BOOM! All your string inputs uses :prepend wrapper. Crazy, right?! Just think about it. You can specify different wrappers for different input types in your SimpleForm’s config. WOOOOW!

Yeah, this 2.1.0 version is not released yet but you always can try it from git:

1
gem 'simple_form', github: 'plataformatec/simple_form'

You can read full CHANGELOG here

Phew, that’s all. I hope you like it. Cheers!

Testing custom SimpleForm inputs

We’re using SimpleFrom because of its powers in customization. We consider creating custom inputs every time we’re going to write something like this:

1
2
3
<%= f.input :birth_date, order:      [:day, :month, :year],
                         end_year:   Date.today.year - 18,
                         start_year: Date.today.year - 100 %>

It’s worth to have simple input definition in views without need to specify all those options every time. The following looks much better:

1
<%= f.input :birth_date, as: :birth_date %>

Or even:

1
<%= f.input :birth_date %>

But every time implementing custom inputs we should bother about writing tests for them. Let me show the way we write tests for custom SimpleFrom inputs. We’ll use RSpec and put input specs into spec/inputs directory.

ActiveRecord inheritance and contexts

The User model appears almost in every Rails application and there are different types of users in our apps (admins and members at least). We often have different roles that are allowed to edit only specified fields or select only specified values.

In our application we have app admins and company (account) admins along with regular members. Company admins should be restricted to create only company admins and company members of their company. Regular members cannot change theirs role or company. App admins are allowed to do anything with users.

As both app admins and company admins have similar functionality it’d be good to have a one controller to manage users. We may have something like this in the admin/users controller:

1
2
3
4
5
6
7
class Admin::UsersController < ApplicationController
  def create
    @user = User.new(params[:user])
    @user.save
    respond_with :admin, @user
  end
end