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.
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.
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.
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:
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:
123456789101112131415161718192021222324
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.
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.:
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.totalprice
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:
123456
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.
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]
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:
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:
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.
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: