Using 'validates_inclusion_of' to validate foreign key


#1

I seem to be missing something trying to use ‘validates_inclusion_of’ to
validate a foreign key and was hoping some one could piont out my
mistake?

The problem seems to be that Order.find_all.collect is not returning an
array that contains the order_id, if I replace it with a hardcoded array
everything works as expected.

The model:

class OrderItem < ActiveRecord::Base
validates_inclusion_of :order_id, :in => Order.find_all.collect {
|order| order.id }
belongs_to :order
end

The test:

class OrderItemTest < Test::Unit::TestCase
fixtures :orders, :order_items
def test_validates_inclusion_of_order_id_in_orders
orderitem = OrderItem.new(:order_id => 1)
assert( orderitem.save, orderitem.errors.full_messages.join(’\n’))
end
end

The fixture:
first:
id: 1
sid: ‘ON001’

All of the code can be found at http://metaspot.net/dev/browser/?rev=46


#2

Michael G. wrote:

The problem seems to be that Order.find_all.collect is not returning an
array that contains the order_id, if I replace it with a hardcoded array
everything works as expected.

Guess I’ll respond to my own post…

This was most likley a bad idea anyway. I really don’t want to suck an
entire table index into an array every time I want to create or update a
new row. I should of just created a validation that did something like
this…

def validate
begin
Order.find(self.order_id)
rescue ActiveRecord::RecordNotFound
errors.add(:order_id, “invalid foreign key reference”)
end
end


#3

You might have difficulty using :in => to achieve that. A custom
validation is probably in order. For performance reasons, it’s
probably worth adding a method to ActiveRecord::Base to select just
the model ids. That will save you from having to instantiate all the
model objects each time the validation occurs.

class ActiveRecord::Base
def self.ids
connection.select_values(“select #{primary_key} from
#{table_name}”).collect(&:to_i)
end
end

class OrderItem
belongs_to :order

def validate
errors.add_to_base “Invalid order” unless
Order.ids.include?(order_id)
end
end

-Jonathan


#4

That is probably a better approach. Using Order.exists? will make the
code a bit shorter:

errors.add_to_base ‘Invalid order’ unless Order.exists?(order_id)

-Jonathan.


#5

Jonathan V. wrote:

That is probably a better approach. Using Order.exists? will make the
code a bit shorter:

errors.add_to_base ‘Invalid order’ unless Order.exists?(order_id)

-Jonathan.

Much better, it’s starting to look a bit more like ruby… now that it’s
been expressed in a single line :wink:

Thanks