Archive for the ‘rails’ Category

I want you to give

Monday, April 19th, 2010

Greetings. I want to “answer” to you as I feel you deserve an answer for my previous post.

I won’t start this post with a terrible analogy. I will use swear words throughout though.

I will, however, start it with an apology. The apology is for my most successful post yet. I am honestly sorry for anyone that was offended by the language of the post. Let me explain my thoughts, and I hope we can resolve our differences.

It was positively successful. It drove a lot of traffic to my blog which is what all bloggers truly want: to be noticed. To have people leaving comments. To have an effect on the world around them. If you want to really, really hurt a blogger: stop reading what they write. Your “un-attention” is blogger Kryptonite. To those who have commented positively on not only this post, but any other post on this blog, thank you. You give me the motivation to continue writing.

Then there was the negative reaction that I did see coming, but did nothing to prevent it. I welcomed it. Simply put: negative reactions are more noticed than positive reactions of the same strength.

I will dip into my history as a supermarket checkout attendant again here temporarily. I would receive thanks from the customers who I served regularly. I thought I was good at my job. The people whom I worked with thought I was good at my job. But when a customer complained that I packed something incorrectly, that’s when the shit hit the fan. I would be brought into the manager’s office and we would have a discussion about ensuring that all customers are happy. This is impractical. We are all humans, and therefore we make mistakes, or take offense to something when there was never any given. It only takes a small negative thing to set off a large enough reaction that you’ll tell you friends about it, where as it would take a huge positive thing for a similar-sized reaction to occur. Never, ever, was I called into the manager’s office for him to compliment me on any of the exceptional things I did. He was an asshole.

As a programmer, I attempt to see the logic in everything. I can understand that, for example, when I press the “a” key on my keyboard, that an “a” is going to appear on the screen, always. It’s just logical. This is why I sometimes get frustrated with human behaviour, as it is illogical. My intention for the post was to get people interested in working on the core of Ruby on Rails rather than the ecosystem around it. You cannot have a stable ecosystem without a stable core. Yes, you are correct. I was arrogant in my post. But I implore you to think of it from my side of the fence. I am trying to convey a point. To get some motion going. Do you honestly think my post would have generated that much traffic had I been saying things such as “Hey guys, there’s 900 open tickets in Ruby on Rails. Would you mind, you know, taking some time out of your very, very busy schedules and fixing it”? I think not. Before you go reaching for that comment form, read that again.

I think not.

The operative words, of course, being “I think”. You know this. I don’t need to explain this to you. I am not stating it as fact that the post would not have been successful had I used positive terminology rather than the words I used. It is simply my interpretation. You have yours, and I have mine. I willingly used terminology that was offensive to evoke a large enough response in order to get some more eyes on Rails tickets.

I want to thank those of you who reacted negatively to this post, too. Why? You guys are the ones who generated the media coverage. You got it very highly ranked on the Ruby subreddit, Hacker News and many other technology media sites. You are the people who I want to talk to the most. You are the ones who have great ideas of how I could better go about conveying my point, and I have talked with some of you, such as Eric Florenzano. Eric was the writer of the aforementioned “Axe body spray” tweet. I politely asked him if I could email him, and he said yes and so I’ve posted the transcript if you would like to have a read. This is the kind of back-and-forth I am really looking forward to having with you all.

Then finally, my favourite response of all from this post was from a guy called Matthew Joiner (yes, I did my research too ;)) who was the only person out of all 10,000-and-something unique visitors to my blog to track down my email address and write me an email. Matthew starts off his email like this:

No one special sending you a random email, I know how people hate it, but I also am not really into commenting on blog posts, so I hope this doesn’t annoy you too much.

When I read this, I was annoyed. Annoyed because he thought I would be annoyed! Why would sending me an email, annoy me? I want you to notice me. The one way you can inform me that you’ve noticed something that I’ve done is to email me. You can also buy me cider in real life (FYI, favourites so far are Magners and Aspley and Scrumpy Jack). I digress. You can come up to me at any of the Ruby/Rails meetups I go to and shake my hand and say “I really liked that.” It’s my fuel. I want to be noticed. Maybe I’ll post about the “why”s of it later, but I’m sure you can guess those.

Matthew was the only person to send me an email which instantly disqualifies him from the “no one special” category forever. He has gone out of his way to track down my email and tell me what he thought. Nobody else did. So I wrote him a reply effectively thanking him, and discussing a couple of things. A 1500-word reply. You can find the transcript from this email here. He mentioned my email was nowhere on the site, but now it is in the sidebar. This is the most direct-line of contact you can have with me just short of my mobile number.

This is also the kind of back-and-forth I want to have with you all. I want you to ask me questions about Rails 3, and in return I get something from you automatically, which is the feeling of satisfaction of being noticed. Then if I help you, further satisfaction that I’ve improved your life somehow.

Finally, when a lot of you start asking questions, then I have more material to write down for a book I’m thinking of writing. Yup, you heard it here: I’m going to take on writing a book. This is why I want you all working on Rails and the community, because I simply will not have the time to sit in the IRC channel or on Stack Overflow to help you out anymore. Somebody else needs to step up and “be me” temporarily if I am to complete this book by the end of the year. I want your feedback on this! So contact me on twitter or e-mail me with ideas of what you think should go in a book about Rails.

So let me finish on a positive note. Since that post last weekend, over one hundred Rails tickets have been marked off the list, be it them being marked as invalid and then all the way up to patches being submitted and a lot of people getting things done. That’s an amazing amount of effort and it is these types of people the community needs more of. If you want to nominate somebody for the Ruby Hero award, do not nominate me (ok, you can if you want). Nominate the collective of people making the community a better place. I am not the only person doing this. I fear for that situation. I do not want to be the go-to guy for all of your issues as that is the community’s job. But, in order to build a knowledgeable community, those with the knowledge need to be out there spreading it, rather than sitting in their “ivory towers”.

Side-note: I’d like to think I know for a fact that this post won’t gain the level of recognition that the original did. That’s fine with me. If you could go about proving me wrong, please do: I enjoy being wrong.

All I ask is that I want you to help one person a day with Ruby or Rails. Be it a co-worker, a friend, or a complete stranger. Just go out there and do something. C’mon, you can do it. It’s an awesome feeling.

has_and_belongs_to_many double insert

Wednesday, April 14th, 2010

This is a story about my work with GetUp, in particular the past week. It’s about a problem that I’ve been putting off help one of the guys (James) solve, it didn’t seem all that important to me. So last night I kind of promised that I’d sit down with him this morning and help him work out what it was. Hopefully it was something silly either of us did and it would only take us an hour.

You know how this story is going to end up already.

It didn’t take us an hour. It’s now 5pm and I’ve only just figured out what it was.

Symptoms

We have two models who’s names aren’t important so excuse me if I use the name Person and Address to represent them. They are nothing of the sort. In their purest form to replicate this issue, they are defined like this:

class Address < ActiveRecord::Base
  has_and_belongs_to_many :people
end

class Person < ActiveRecord::Base
  has_and_belongs_to_many :addresses
  accepts_nested_attributes_for :addresses

end

When we go to create a new Person record:

Person.create(:addresses_attributes => { "0" => { :suburb => "Camperdown" } }) 

It inserts 1 Person record, 1 Address record but 2 join table records.

So, wtf?

We originally thought it was a bug in our application. How, in all realities, could Rails have a bug, right?

Wrong!

I should know how many bugs Rails could have. I should have been more wary. I was not. And it bit me in the arse. So out of curiosity I googled the issue and saw that others came across it and then I tried checking out to v2.3.4, which worked!. So there was a regression between v2.3.5 and v2.3.4. A simple git bisect bad v2.3.5 with git bisect good v2.3.4 put me on the way to finding out what this was. A couple of bisects later, I found the offending commit was 6b2291f3, by Eloy Duran.

A “solution(?)”

So I generated an application to simply demonstrate that this was a 2.3.5 regression. As I say in the README, I suggest using 2-3-stable if this bothers you. Alternatively there’s always Rails 3, or simply specifying the :uniq => true option on your has_and_belongs_to_many.

That was a fun 7 hours.

As I found out this (the next) morning and Tim Riley points out in the comments the ticket for this bug is #3575 and the related commit is 146a7505 by Eloy Duran also. Freezing rails to v2.3.5 and git cherry-picking this commit into this frozen version fixes it.

Where did I put that puts?

Wednesday, November 4th, 2009

This is the question I ask after I’ve just finished a massive debugging session and I run the tests and halfway through there’s something vague like “S3″ printed out. So I do a Cmd+Shift+F looking for that string and of course it doesn’t exist. What’s a guy to do?

Well, at Mocra we put this in our config/environment.rb file (although a better location would be in a required file located somewhere in lib, probably named debug.rb):

# Print the location of puts/p calls so you can find them later
def puts str
  super caller.first if caller.first.index("shoulda.rb") == -1
  super str
end

def p obj puts caller.first super obj end

And when we don’t want it we comment it out. This will give us the exact location of the puts so we can track it down and remove it.

Size != Count

Friday, October 30th, 2009

When using a has_many :through in Rails with a counter_cache an interesting thing can occur. When you call size on the association, it can return a seemingly incorrect number. This is caused by the following code in activerecord/lib/has_many_through_association.rb:

def size
  return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
  return @target.size if loaded?
  return count
end

It’ll reach the counter_cache column which could be incorrect, giving you all the objects returned when you look up the association, but an invalid number. This was only in my tests where data was being created through Machinist. Just watch yourself for this, this is the second time it has caught me, and ideally the last.

Connecting to Multiple Databases Using ActiveRecord

Saturday, October 17th, 2009

You can call establish_connection with the key that points to another database config in your config/database.yml file

class Person < ActiveRecord::Base
  establish_connection(:hr)
end
class Ticket < ActiveRecord::Base
  establish_connection(:bug_tracker)
end

If you have a whole bunch of models that need to connect to another database:

class HR < ActiveRecord::Base
  establish_connection(:hr)
end

class People < HR
  # ...
end

class Resource < HR
  # ...
end

distance_of_time_in_words

Friday, August 7th, 2009

I was infatuated with this method when I first saw Rails but I’ve seen a couple of people recently express that Rails’ built in distanceoftimeinwords is not accurate enough, showing something like “about 2 years” rather than “2 years, 21 days, 5 hours, and 6 minutes”. With some help from chendo at Mocra (where I work) I’ve made a new distanceoftimeinwords which should be a drop-in replacement for the old crappy one. To install it, use: script/plugin install git://github.com/radar/dotiw.git. This also comes with another method if you’re still picky about the output: distanceoftimeinwords_hash which gives you a Hash containing keys in your native tongue. The README should give you a good guide of what other options it supports too.

sort_by

Sunday, July 12th, 2009

Fresh on the heels of yesterdays cantouchthis I’ve released another plugin today called sort_by. It provides you a way of sorting a table by user selected fields and it even paginates *ooh* *ahh* *pause for ego boost*. Inspired by hearing people talk about having to make stuff from scratch to do this I worked on it for about the last 3 hours. Tell me what you think!

Pretty-ifying URLs

Saturday, April 18th, 2009

In today’s modern world we are not limited (cough, FORTRAN) to storing information in just a couple of bits. Some say that having pretty URLs is good SEO. I say it’s just common sense. I want the URL to tell a story, not give me some number that is only important to the system it’s coming from.

This is where to_param comes in.

By default this is defined like this:

def to_param
  id
end

Pretty high-tech stuff there. I won’t explain it to you. Give it a moment to sink it. et-cetera. This method is called on an object when it is passed to a url helper such as link_to or redirect_to. It is important to note that you should NEVER call @object.id as that will give the id of the object, not the to_param version of that name.

You can override this in your model in order to give you pretty urls! The way you do this is:

def to_param
  "#{id}-#{name.parameterize}"
end

Where id is the id of the model and name is the text you want into the URL. We call parameterize on this text in order to make it URL friendly.

The reason why we put in the id in the URL is two-fold. Firstly, (/forums/1-best-forum-ever) is so that when it gets passed to our controller as params[:id] or similar, we’re able to pass it to a finder:

Forum.find(params[:id])
# will be passed in as...
Forum.find("1-best-forum-ever")

Now you may go “Hey… wait a second! I don’t have an ID in my database that says ’1-best-forum-ever’!” and you’d be right, you don’t. But you do have an ID of 1 for a record somewhere. Rails will call to_i on this value and convert it simply down to 1 and you’ll be able to treat the pretty URL as if it were a real ID.

Secondly, if two objects in your database have the same name they won’t clash for the parameterized versions, since the id will always be unique.

But I don’t want my ID in my URL

Hey, that’s cool too! Just a little tougher… Before you save your records you’ll want to write out the permalink as a new field, so define a field called permalink in your table if you want to take this route. Then you do a before_save and define to_param like this:

before_create :set_permalink

def set_permalink
  self.permalink = name.parameterize
end

def to_param
  permalink
end

Now when you call the finder you’re going to need to find_by_permalink! instead of just find:

Forum.find_by_permalink!(params[:id])

We have to use the bang version of find_by_permalink because this will raise an ActiveRecord::RecordNotFound exception if the record is not found, just like find. (Thanks to Yarsolav Markin for mentioning this)

So there you have it. to_param overriding for pretty URLs.

I18n: An Overview

Wednesday, October 29th, 2008

Welcome to yet another overview, this time it’s on the new I18n features in Rails 2.2 which you can install by typing gem install rails -s http://gems.rubyonrails.org -v 2.2.0.

Any mention of the t method in this guide are also spots where you can use translate too, they are just aliased methods (t is aliased to translate), it’s just much easier (for me) to type t than it is to type translate. Please excuse my laziness.

I’ve begun adding in the translation calls for rboard in my personal branch on GitHub and today I would like to show you how I’ve done it.

Firstly I have added these two lines to my config/environment.rb:

config/environment.rb

I18n.loadpath = Dir.glob("#{RAILSROOT}/locales/*.rb")
I18n.default_locale = "en-AU"

This tells rails to load the translation files (aka locale files) from the locales directory in the root of my rails app, and they are in a ruby format. Alternatively you could load yaml files. this also tells it to set the default locale as “en-AU”, which will load locales/en-AU.rb by default.

My (incomplete) translation file looks like this: locales/en-AU.rb

{
  :'en-AU' => {
   :forumheading => "Forum",
   :topicsheading => "Topics",
   :postsheading => "Posts",
   :lastpostheading => "Last Post",
   :noforums => "There are no forums.", 
   :administratorshouldcreateforum => "Maybe an administrator should create one.",
   :youshouldcreateforum => "Maybe you should create a forum.",
   :forumstatistics => "Forum Statistics",
   :postspertopic => "Posts per topic",
   :recentusers => "Users on in the last 15 minutes",
   :registeredusers => "Registered Users",
   :home => "Home",
   :editprofile => "Edit Profile",
   :memberlist => "Member List",
   :search => "Search",
   :newmessage => "new message",
   :logout => "Logout",
   :timenow => "The time is now",
   :viewingforum => "Viewing forum",
   :newtopic => "New Topic",
   :moderationheading => "Moderation",
   :topicheading => "Topic",
   :repliesheading => "Replies",
   :viewsheading => "Views",
   :authorheading => "Author",
   :ago => "ago",
   :by => "by"
  }
}

When I have a string I want translated in my app I will simply call stuff like t(:author_heading) and Rails will look up the correct translation for it, which in this case is just “Author”.

Now if I had another translation file, say locales/es.rb and I had Spanish users on rboard they could select a locale from their profile page and that would store it as a string on their user record. To translate this, we can use a before_filter on the application controller:

app/controllers/application.rb

class ApplicationController < ActionController::Base
  beforefilter :setlocale
  def setlocale
    I18n.locale = currentuser.locale if logged_in?
  end
end

This will set the locale to whatever the user has set, providing that they are logged in.

Interpolation

If you wish to insert a value into a translation you can use interpolation. To do this you can specify the t method call like this:

in a i18n-friendly file somewhere

<%= t(:welcome, :user => current_user.login) %>

And then in your locales file specify this:

locales/en-AU.rb

:welcome => "welcome {{user}}!"

And the output of the translation will now be “welcome Ryan!” or whatever the user login was.

Counting

If you have a translation such as :xnewmessages in your translation file and you want the output of this translation to be correctly pluralized you can pass the count option to this:

In any t method supporting files

<%= t(:x_new_messages, :count => current_user.messages.size) %>

The x_ prefix to our translation is not important, it’s just there to show us that this translation may return different results depending on the count that is passed to it.

Then in your translation file you can do:

locales/en-AU.rb

:xnewmessages => {:zero => 'No new messages', :one => 'One new message', :other => '{{count}} new messages'}

And depending on the value of count it will return one of those three outcomes.

Forcing a Locale

If you want to force a locale on a single translation you can do this by specifying the :locale to the t method call like so:

<%= t(:english, :locale => "en-AU") %>

And this will always show the en-AU translation of the english key in the en-AU.rb locale file.

Alternative Translations

If one of your translations does not match like:

<%= t(:norsk) %>

You can have I18n fall back to any number of other translations:

<%= t(:norsk, :default => [:norwegian, :up_north, :northwards, "norway"]) %>

I18n will attempt to get a default translation from the options specified and will select the first one. If all translations failed then the string version, “norway” will be outputted.

Retrieving Multiple Translations

To get multiple translations back at the same time you can specify an array as the first argument to the t method.

<%= t(:forums, :topics) %>

Assuming you have correct translations for forums and topics you will get the translated versions returned in an array. Assuming you don’t have the correct translations for forums OR topics you will get back a string version of whatever translation is missing, possibly wrapped in a <span class=’translation_missing’></span>.

Further translation files can be found at Sven Fuch’s Github Repository

Single Table Inheritance

Sunday, September 28th, 2008

This tutorial was written using Ruby 1.8.6, Rails 2.1.

A lot of the time I see people asking how they can do something like access levels for their Rails applications and this usually boils down to some STI (single table inheritance) love. You’ll need to have restful_authentication installed.

What is Single Table Inhertiance?

Single Table Inheritance is where you have multiple models that inherit from a single table, hence the name. It’s great for situations like this where we want multiple user access levels, but we don’t want to create multiple tables for the different kinds of users of our application.

Okay.. so how do I use it?

Firstly you start off with one model that inherits from ActiveRecord::Base, and I would advise running script/generate authenticated person session to get this:

app/models/person.rb

class Person < ActiveRecord::Base

end

And then you subclass some other classes from Person app/models/student.rb
class Student < Person

end

app/models/teacher.rb
class Teacher < Person

end

app/models/admin.rb
class Admin < Person

end

The great thing about splitting these into individual files is because they’re more easily managed and closely follows the convention of one model per file.

Now you have a person, student and teacher model and you should have a brand new migration for your people table in db/migrate so go on and open that up and you’ll see something like this:

class CreatePeople < ActiveRecord::Migration
  def self.up
    createtable "people", :force => true do |t|
      t.column :login,                     :string, :limit => 40
      t.column :name,                      :string, :limit => 100, :default => '', :null => true
      t.column :email,                     :string, :limit => 100
      t.column :cryptedpassword,          :string, :limit => 40
      t.column :salt,                      :string, :limit => 40
      t.column :createdat,                :datetime
      t.column :updatedat,                :datetime
      t.column :remembertoken,            :string, :limit => 40
      t.column :remembertokenexpiresat, :datetime

end
add_index :people, :login, :unique => true

end

def self.down droptable "people" end end

To enable STI for this table, just add:
t.column :type, :string
into the create
table block, just after t.column :remembertokenexpires_at, :datetime. This type column will be set to whatever class the record is, so if we create a new Student object, this type column will be set to “Student”.

Controllers

To create new objects for these subclasses I would recommend building a people controller and using the form partial from that to base your form off from the other controllers, such as StudentsController and TeachersController. These controllers should NOT subclass from PeopleController, they should be their own independent controllers because Teachers, People and Students are all individual resource types.

Associated Models

If you have associated models (e.g. Person hasmany :enrolments), any subclass of Person will inherit this relationship. For the relationship, this will be referenced via personid still in the enrolments table.