Posts Tagged ‘rails’

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

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.

Finding SQL Type of Column

Sunday, December 14th, 2008

exlibris asked tonight in #rubyonrails how to find the type of a column in the table for a model. I didn’t know this and maybe someone else out there may not know how either. The solution lies within the columnshash method from ActiveRecord:Base which returns a hash containing all kinds of useful information about all the columns in your table such as:

  • Precision (integer, notes the precision of the column if it’s decimal-based)
  • Primary (boolean, identifies if column is primary key)
  • default
  • limit (integer, denotes how long the field can be)
  • type (symbol, the class type in lowercase)
  • name (string, fairly obvious)
  • null (boolean, identifies if column can be set to null)
  • scale (integer, does something)
  • sqltype (returns the sql type of the column)
That final column is the magic attribute we’re looking for. To get to it we access it like Model.columnshash["attribute"].sqltype

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.

Faking It

Monday, July 14th, 2008

Today I was doing the one thing I truly, truly love doing and that’s complaining about writing RSpec tests. I came across a doozy of a problem involving RSpec testing and faking subdomains. Here’s a stripped down version of what I did:

def login_as(user)
  session["user"] = users(user).id
  request.host = User.find(session["user"]).company.domain + ".example.com"
end

Just pop that into your spec/spechelper.rb and then you can use loginas(:user) which will find the fixture with the name of “user” and then go from there to setting your faked host as being from, for example, blah.example.com.

Pretty simple, shame Google didn’t turn up any relevant results without me having to dig deeper than usual.

A bit of refactoring love

Friday, June 13th, 2008

Find, Find, Find, Find, I don’t think so…

As explained in previous posts, Rails controllers have 7 default actions (index, new, create, show, edit, update, destroy). Four of these seven actions make the same find call, Model.find(params[:id]) and this tutorial is to tidy that up so you’re not repeating yourself over four different actions. To clean this up we’ll just call a before filter:

class ForumsController < ApplicationController
  before_filter :find_forum

  # Actions go here
  
  private
    def find_forum 
      @forum = Forum.find(params[:id])
    end
end

Now you may be thinking, "Why are we doing that? That's 5 lines!". Think about if you wanted to change the find statement, and now you'll begin to picture why. Changing one line is much easier than changing four. For example, if I wanted to find forums by their slugs instead of an ID I would simply change @forum = Forum.find(params[:id]) to @forum = Forum.findbyslug(params[:id]). Of course, for this to work with the restful routes helpers the way we expect it to (e.g. forumpath(@forum) -> /forums/the-first-forum), we'll need to re-define #toparam in our model:

class Forum
  def to_param
    slug
  end
end

Common Lookups

Sometimes you'll have data initialised for your forms and you'll want to initialise this data multiple times. Instead of repeating yourself like this:

class ForumsController

  def new
    @forum = Forum.new
    @something_special = SomethingSpecial.find(:all, :order => "id DESC")
  end
 
  def create
    @forum = Forum.new(params[:forum])
    if @forum.save
      flash[:success] = "A forum has been created."
      redirect_to @forum
    else
      flash[:failure] = "A forum could not be created."
      @something_special = SomethingSpecial.find(:all, :order => "id DESC")
      render :action => "new"
    end
  end

end

You could instead have:

class ForumsController

  def new
    @forum = Forum.new
    common_lookups
  end
 
  def create
    @forum = Forum.new(params[:forum])
    if @forum.save
      flash[:success] = "A forum has been created."
      redirect_to @forum
    else
      flash[:failure] = "A forum could not be created."
      common_lookups
      render :action => "new"
    end
  end

  private
    def common_lookups
      @something_special = SomethingSpecial.find(:all, :order => "id DESC")
    end
end

Shorter Routing

One last thing that I'd like to show you is shorter routing. Ever since the restful routing helpers were added, routing to specific controllers and their actions has become easier and easier. Rails 2.0 makes it extremely easy, but first we'll see how far we've come:

  1. <%= link_to @forum, { :controller => "forums", :action => "show", :id => @forum.id } %>
  2. <%= link_to @forum, forum_path(@forum) %>
  3. <%= link_to @forum, forum_path %>
  4. <%= link_to @forum, @forum %>
  5. <%= link_to @forum %>

As long as there's a #tos method in the Forum model it will insert that as the phrase shown to the user for the link. All of the above should produce the same URL, with the exception of the first which will produce /forums/show/1, and going down the list they're just shorter ways of writing the same thing. If you had nested routes such as forumtopicpath(@forum, @topic) you could do <%= link_to @topic, [@forum, @topic] %> as the extremely short version of it. The reason why we can't do just <%= link_to [@forum, @topic] %> is because this will show the tos version of @forum, followed immediately by the to_s version of @topic.

Updating a select box based on another

Tuesday, May 20th, 2008

In two projects now I’ve had to use code where I update a select box (machines) in regards to what’s selected in the first (customers). mark[oz] from #rubyonrails asked how to do this last week and I told him I would write a blog post the following night. 5 days later, here it is.

I use this when I’m creating a new call and the user needs to select which customer and then which machine. I have the following in my CallsController:

def new
  @call = Call.new
  @customers = Customer.find(:all, :order => "name ASC")
end
Relatively simple. We instantiate a new call object so the form knows where to go, and we define @customers so the first select box has some information.

Of course in our Customer model we have:

class Customer < ActiveRecord::Base
  hasmany :machines
end
And the machine model:
class Machine < ActiveRecord::Base
  belongsto :customer
end
And the call model:
class Call < ActiveRecord::Base
  belongsto :machine
  belongsto :customer

These define the relationships used within the system.

Next is the new.html.erb page itself.

New Call
<% form_for @call do |f| %>
  <%= render :partial => "form" %>
  <%= submit_tag "Create" %>
<% end %>
And a stripped-down version of the form partial:
<%= f.label "customer_id" %>
<%= f.select "customer_id", @customers.map { |c| [c.name, c.id] }%>
<%= observe_field "call_customer_id", :url => by_customer_machines_path, :with => "customer_id" %>
<%= f.label "machine_id" %>
<%= f.select "machine_id", "Please select a customer" %>
Right now that we have the form all set up, lets set up our machines controller
class MachinesController < ApplicationController

CRUD operations go here

def bycustomer @machines = Machine.findallbycustomerid(params[:customerid]) end end

And our bycustomer.rjs:
page.replacehtml 'callmachineid', optionsfromcollectionforselect(@machines, "id", "name")
And finally the config/routes.rb:
map.resources :customers do |customer|
  customer.resources :machines
end

map.resources :machines, :collection => { :by_customer => :post }

Now when we select a customer from the top drop down, the machines drop down should be populated too.