Archive for the ‘rails’ Category

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.

A Sense of Belonging

Friday, August 29th, 2008

Numerous times I’ve needed and I’ve seen other people have needed the need to check whether an object belongs to the currently logged in user. I’ve worked out that something like this works:

class User < ActiveRecord::Base
  hasmany :posts
  def owns?(object)
    object.user == self
  end
end
This works when you have a currently logged in user and call it by using currentuser.owns?(@post). Now what if you wanted to do it the other way around? Well it’s really as simple as this:
class Post < ActiveRecord::Base
  belongsto :user
  def belongsto?(otheruser)
    user == otheruser
  end
end
Now you can reference that through @post.belongsto?(otheruser).

If you wanted to use either of these in the controller, it would be like this:

class PostsController < ApplicationController
  def edit
    @post = Post.find(params[:id])
    check_ownership
  end

def update @post = Post.find(params[:id]) if currentuser.owns?(@post) # or @post.belongsto?(currentuser) # carry on... if @post.updateattributes(params[:post]) flash[:success] = "Post updated!" redirectto topicpath(@topic) else flash[:error] = "Post could not be updated." render :action => "edit" end else flash[:error] = "You do not own that post." end end

private def checkownership if !currentuser.owns?(@post) # or @post.belongsto?(currentuser) flash[:error] = "You do not own that post!" redirectbackordefault topicpath(@topic) end end end

Now here we’ve called checkownership in the edit action which will stop the template from being rendered by calling redirectbackordefault. We can’t call (as I found out thanks to BlueSea) checkownership in the same way in the update action because the code will still be executed. So we must call the methods we defined in the model, either currentuser.owns?(@post) or @post.belongsto?(current_user).

Stop Being So Self-Centered!

Tuesday, July 29th, 2008

I’ve seen so many examples of people being self-centered in their code, always calling their methods on self when they don’t need to!

self is a handy reference to the current object (or class) depending on how the method is defined. In a class that inherits from ActiveRecord for example, calling self before defining a method will define that method on that class, rather than the objects of that class. Once inside of the method, you are free to go about as you will; Ruby will know what you mean when you call other methods inside that method.

A prime example would be this self-centered piece of code:

class Forum < ActiveRecord::Base
  acts_as_tree
  
  def to_s
    self.title
  end
  
  def self.find_all_without_parent
    self.find_all_by_parent_id(nil)
  end
  
  def root
    self.parent.nil? ? self : self.parent
  end
  
end

These are a few methods with both good and bad examples of using self.

We'll start with #tos. This method is defined by typing def tos, which tells ruby we want to define a method called "to_s" on all objects of the Forum class. Inside the method however, we have a bad-case of self-centering, by calling self where it's not needed. Ruby already knows that we're operating with an object derived from the forum class, and calling self will only return the same object! So why do we do it here? We can remove the superfluous self call, and the method will still work.

Our next method is self.findallwithout_parent. The self. prefix to the method tells Ruby that we want to define this method on the Forum class itself, rather than a derived object from the class. Inside the method again we have a bad case of self-centering! Ruby already knows we're operating with the Forum class, and calling self will only return the Forum class once more. Again we can remove the superfluous self call and the method will still work.

In both of these examples, you could've called self on self and then the method multiple times: self.self.title or even self.self.self.self.self.title, but that's just going a bit overboard!

In the last method is two incorrect usages of self, and one correct use of self. The method is #root. The method defines a one-lined if statement by calling a method (self.parent.nil?) which will return either true or false. The next character is a space followed by a ?, which indicates to ruby anything after this is what we want executed when self.parent.nil? returns true. Here we just call self, and this is the good use of self. The #root method is attempting to find the root element for the tree heirachy of the forums, if the forum object we're calling root does not have a parent, we want it to return itself as it is the highest level of the forum structure. The next character in the code is a colon (:), which tells Ruby what we want to do if self.parent.nil? returns false. Here we've called self.parent, which again is a superflous call to the self object. We only need to call parent because Ruby already knows which context we are referring to! The final superfluous self call is the self.parent.nil? from where our one-lined if statement originated.

I hope you've enjoyed this little tidbit, and please stop being so self-centered.

UPDATE: RSL has posted an easier way than parent.nil? ? self : parent as the first comment. The code is now simply:

def root
  parent || self
end

UPDATE #2: The root method was still broken as I realised this morning as I tampered with the tests. The method is supposed to get the highest forum in a string of forums. This works perfectly for 2-level-deep forums, but not for 3-level-deep. The third-level element would've returned the second-level element rather than the first. The revised code is now:

  def root
    parent.nil? ? self : (parent.root == parent ? parent : parent.root)
  end