validates_uniqueness_of on massaged contents of column

I am trying to check the uniqueness of a column updated from a form. In my model I have this:

class Entity < ActiveRecord::Base

  has_one :client, :dependent => :destroy   has_many :sites, :through => :locations

  before_validation :indexed_name

  validates_presence_of :entity_name   validates_presence_of :entity_legal_name   validates_presence_of :entity_legal_form

  validates_uniqueness_of :entity_name

  def entity_name     read_attribute(:entity_name).titlecase   end

  def entity_name=(name)     write_attribute(:entity_name, name.keycase)   end

  def indexed_name     self.entity_name = self.entity_name.keycase   end

(keycase is a local extension to the string class)

This code coerces the values entered into entity_name to lowercase and saves them to the database and displays data in that column as titlecase. All that works as expected. What does not work is the validates_uniqueness_of.

What I desire is that form data entered into entity_name as "A TEST CLIENT" be massaged to "a test client" BEFORE the validates_uniqueness_of is performed. This is not what is happening even though the update SQL is generated with the contents of entity_name properly prepared.

How do I accomplish this?

Try setting up a call back on the model that runs before validation:

before_validation()

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M001303

ESPNDev@gmail.com wrote:

Try setting up a call back on the model that runs before validation:

before_validation()

ActiveRecord::Callbacks

I have done that:

  before_validation :indexed_name

  validates_uniqueness_of :entity_name

  def indexed_name     self.entity_name = self.entity_name.keycase   end

And this does not work. What variable contains the value that is used by validates_uniqueness_of?

Oh, by the way, I found this tonight. Apparently I am not the only one twisting in the wind on this problem.

http://blog.craz8.com/articles/2007/12/10/rails-validates_uniqueness_of-is-completely-broken/

This is the development log:

Processing EntitiesController#new (for 127.0.0.1 at 2008-03-18 21:23:11) [GET]   Session ID: BAh7BzoMY3NyZl9pZCIlMjFkMjZlMWE3ODgyM2MwMzBmZmE0NzJiMzI2OGJl%0ANjYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--2c65e7445b0b3a4500a926ab4ca20ba2d50b06d1   Parameters: {"action"=>"new", "controller"=>"entities"}   Entity Indexes (0.000000) PRAGMA index_list(entities)   SQL (0.000000) PRAGMA index_info('idxU_entities_entity_name') Rendering template within layouts/entities Rendering entities/new Completed in 0.04700 (21 reqs/sec) | Rendering: 0.01600 (34%) | DB: 0.00000 (0%) | 200 OK [http://localhost/entities/new\]

Processing ApplicationController#index (for 127.0.0.1 at 2008-03-18 21:23:11) [GET]   Session ID: BAh7BzoMY3NyZl9pZCIlMjFkMjZlMWE3ODgyM2MwMzBmZmE0NzJiMzI2OGJl%0ANjYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--2c65e7445b0b3a4500a926ab4ca20ba2d50b06d1   Parameters: {}

...

Submit new data.

Processing EntitiesController#create (for 127.0.0.1 at 2008-03-18 21:23:51) [POST]   Session ID: BAh7BzoMY3NyZl9pZCIlMjFkMjZlMWE3ODgyM2MwMzBmZmE0NzJiMzI2OGJl%0ANjYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--2c65e7445b0b3a4500a926ab4ca20ba2d50b06d1   Parameters: {"commit"=>"Create", "entity"=>{"entity_legal_name"=>"The Second should fail Test Client Ltd.", "entity_name"=>"second test client", "entity_legal_form"=>"CORP"}, "authenticity_token"=>"1de43991e546d4823d88635f1971041ba746b432", "action"=>"create", "controller"=>"entities"}   Entity Indexes (0.000000) PRAGMA index_list(entities)   SQL (0.000000) PRAGMA index_info('idxU_entities_entity_name')

THIS is the validation call:

  Entity Load (0.000000) SELECT * FROM entities WHERE (entities.entity_name = 'Second Test Client') LIMIT 1

Why is "entity_name"=>"second test client" in the parameters passed above but entities.entity_name is nonetheless set equal to 'Second Test Client'? What variable is holding this value?

The code for validated_uniqueness_of is

510: def validates_uniqueness_of(*attr_names) 511: configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] } 512: configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) 513: 514: validates_each(attr_names,configuration) do |record, attr_name, value| 515: condition_sql = "#{record.class.table_name}.#{attr_name} #{attribute_condition(value)}" 516: condition_params = [value] 517: if scope = configuration[:scope] 518: Array(scope).map do |scope_item| 519: scope_value = record.send(scope_item) 520: condition_sql << " AND #{record.class.table_name}.#{scope_item} #{attribute_condition(scope_value)}" 521: condition_params << scope_value 522: end 523: end 524: unless record.new_record? 525: condition_sql << " AND #{record.class.table_name}.#{record.class.primary_key} <> ?" 526: condition_params << record.send(:id) 527: end 528: if record.class.find(:first, :conditions => [condition_sql, *condition_params]) 529: record.errors.add(attr_name, configuration[:message]) 530: end 531: end 532: end

What is passing |value| from the model? This seems to be the source of the difficulty.

@James:

The answer is in the code you're written.

  def entity_name     read_attribute(:entity_name).titlecase   end

  def entity_name=(name)     write_attribute(:entity_name, name.keycase)   end

You've provided an accessor for 'entity_name' that reads the attribute and returns it in titlecase form. That would appear to explain why you're getting 'Second Test Client' -- you're going through the accessor which is doing what you've asked it.