Monday, September 19, 2011

QOTD "Faster! Faster!"

I recently advised our team to used single quotes instead of double quotes, unless they obviously need interpolation in the strings.

Of course, I immediately got a nice reply about about a benchmark to compare the two use cases. I did not even look it up before making the recommendation. I try not to let facts cloud my opinions.

So there is the benchmark, as extolled on StackOverflow:


$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9.6.2]
$ cat benchmark_quotes.rb
require 'benchmark'
n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("assing interp") { n.times do; c = "a string #{'b string'}"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
end

$ ruby -w benchmark_quotes.rb 
      user     system      total        real
assign single  2.600000   1.060000   3.660000 (  3.720909)
assign double  2.590000   1.050000   3.640000 (  3.675082)
assing interp  2.620000   1.050000   3.670000 (  3.704218)
concat single  3.760000   1.080000   4.840000 (  4.888394)
concat double  3.700000   1.070000   4.770000 (  4.818794)


So doubles on the whole are just fine to use all the time. In fact, single quotes are a tad slower in basic use. Who knew.


To quote http://blog.ntrippy.net/, "Just because my opinion has changed, does not mean the fact that I'm right has."*

*See what I did there?

Friday, September 16, 2011

On the other other hand

I like using polymorphic tables in Rails, it's often a very nice way of structuring access to a single object from multiple points.

To define a polymorphic association, you need to to this in your class:

class Thing < ActiveRecord::Base
   belongs_to :thingable, :polymorphic => true
   #...
end

and on the other side

class Foo < ActiveRecord::Base
   has_may :things :as => :thingable
end

class Bar < ActiveRecord::Base
   has_may :things :as => :thingable
end

Your migration looks like:

class CreateThings < ActiveRecord::Migration
  def change
    create_table :things do |t|
      # attributes ...
      t.string :thingable_type
      #turns into thingable_id
      t.belongs_to :thingable
      t.timestamps
    end
  end
end

Enter the hand that is short

class CreateThings < ActiveRecord::Migration
  def change
    create_table :things do |t|
      # attributes ...
      t.references :thingable, :polymorphic => true
      t.timestamps
    end
  end
end

Tuesday, September 13, 2011

Preach it sister!

I don't often/ever repost other bloggers, but  this guy has such an elegant solution to serious pain in the ass, I thought I'd give him a mention.

alfajango


The problem he solves is very simply: ajax file uploads with jQuery in rails.

He explains the problem and wrote a neat gem called remotipart that just rocks, and is completely unobtrusive to the application.

Good on ya mate!

Friday, September 2, 2011

The nest of despair

I've recently had the need to add a supplemental model to a user when they sign up, based on the signup info they provide.

For instance, if a user signs up the have to at least provide

email
password
password confirmation

However I also wanted to be able to register corporate users, so they can optionally supply

company name
and
company url
Enter accepts_pain, I mean accepts_nested_attributes_for.

This is in principle a very good idea and should make implementing this sooo easy.

So, here is my model before I add it in:

class User < ActiveRecord::Base
   has_one :company
   devise :registerable #keeping it simple here
   validates :email, :presence => true
   attr_accesible :email, :password, :password_confirmation
end

and here is the model after...


class User < ActiveRecord::Base
   has_one :company
   devise :registerable #keeping it simple here
   validates :email, :presence => true
   attr_accessible :email, :password, :password_confirmation, :company_attributes
   accepts_nested_attributes_for :company
end
At this point I should point out that you have to put accepts_asshattery after the association is defined. So in this case, after has_one :company

Ok now on to the views, and here is where the pain started.

A normal view from devise might look like this:

<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <%= f.label :email %>
  <%= f.email_field :email %>

  <%= f.label :password %>
  <%= f.password_field :password %>

  <%= f.label :password_confirmation %>
  <%= f.password_field :password_confirmation %> 

  <%= f.submit "Sign up" %>
<% end %>

To get nested magic, you have to add the nested fields...
  ...
  <%= devise_error_messages! %>

  <%= f.fields_for :company do |builder| %>
    <%= builder.label :name %>
    <%= builder.text_field :name %>

    <%= builder.label :url %>
    <%= builder.url_field :url %>
  <% end %>
   
  <%= f.label :email %>
  <%= f.email_field :email %>

  ...

This however, had me running around in circles for a good long while, so I'll cut to the chase to you: The nested form requires you to specify "company_attributes" not just ":company"

The error is not, I dare say, very apparent.  In my case the User model was firing off several after_create handlers, and they were all fine. Just company never created. no failure, just not call at all.

The API docs were not much help, and pointed my in the intuitive but wrong direction:

This model can now be used with a nested fields_for, like so:
<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :address do |address_fields| %>
    Street  : <%= address_fields.text_field :street %>
    Zip code: <%= address_fields.text_field :zip_code %>
  <% end %>
  ...
<% end %>
So there.