4

I am developing a Rails web application and am confused about how to utilize the lookup table values in my models. Here is an example model from my app:

table name: donations

id
amount
note
user_id
appeal_id
donation_status_id
donation_type_id
is_anonymous
created_at
updated_at

The fields *donation_status_id* and *donation_type_id* refer to lookup tables. So in my code I have several random places where I make calls like this:

my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus.find_by_name("completed").id
#do something
end

To my inexperienced eyes, a one-off query to the DonationStatus table seems incredibly wasteful here, but I don't see any other good way to do it. The first idea I thought of was to read all my lookup tables into a hash at application startup and then just query against that when I need to. But is there a better way to do what I am trying to do? Should I not worry about queries like this? Thanks!

JimDaniel
  • 12,513
  • 8
  • 61
  • 67

3 Answers3

2

How about putting it in a constant? For example, something like this:

class DonationStatus < ActiveRecord::Base
  COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id
  PENDING_DONATION_ID   = DonationStatus.find_by_name("pending").id
  # ...
end

class DonationsController < ApplicationController
  def some_action
    my_donation = Donation.find(params[:id])
    if my_donation.donation_status_id == DonationStatus::COMPLETED_DONATION_ID
    #do something
  end
end

This way, DonationStatus.find_by_name("pending").id gets executed exactly one. I'm assuming, of course, that this table won't change often.

BTW, I learned this trick in Dan Chak's book, Enterprise Rails.

EDIT: I forgot to mention: in practice, I declare constants like this:

COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id rescue "Can't find 'completed' in donation_statuses table"
Mark Westling
  • 5,904
  • 4
  • 26
  • 30
  • 1
    I would name the constant COMPLETED instead of COMPLETED_DONATION_ID, but other than that, this is a valid approach and I'm not sure why it was down-voted. – Whit Kemmey Jun 15 '13 at 00:52
  • ...especially since the OP wanted to avoid the second query, which this does, but the selected answer doesn't. – Whit Kemmey Jun 15 '13 at 00:53
2

Since you have two models, you should use ActiveRecord Model Associations when building the models.

class Donation
  has_one :donation_status
end

class DonationStatus
 belongs_to :donation
end

Then when you do

my_donation = Donation.find(params[:id])
if my_donation.donation_status.status_name == 'complete'
  #do something
end

For more information, you may want to read up how rails is doing the model associations http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Don't worry about performance, rails has taken care of that for you if you follow how the way it should be done

Shanison
  • 2,295
  • 19
  • 26
  • I think this may be the solution I am after, but to get it to work I had to make Donation (belongs_to :donation_status) and DonationStatus (has_many :donations) -- then I am able to call d = Donation.find(params[:id], :include => [:donation_status]) and everything I need is preloaded into the object, so I can call d.donation_status.name == 'completed' – JimDaniel Jul 30 '11 at 02:14
  • Yes, use include is the way to go. – Shanison Jul 30 '11 at 10:10
1

What you could do is add this method to Donation:

# Donation.rb
def completed?
    self.donation_status.name == 'completed' ? true : false
end

And then just do my_donation.completed?. If this is called a second time, Rails will look to cache instead of going to the DB.

You could add memcached if you want, or use Rails' caching further, and do:

def completed?
    return Rails.cache.fetch("status_#{self.donation_status_id}_complete") do
        self.donation_status.name == 'completed' ? true : false
    end
end

What that will do is make a hash key called (for example) "status_1_complete" and if it's not defined the first time, will evaluate the block and set the value. Otherwise, it will just return the value. That way, if you had 1,000,000,000 donations and each of them had donation_status 1, it would go directly to the cache. memcached is quite fast and popular.

MrDanA
  • 11,489
  • 2
  • 36
  • 47