Adding new ARel visitors to resolve TypeError: Cannot visit ClassName

Is there an approved way to add or register a new ARel visitor without adding it to the ARel source? Looking at the some of the source code it appears that new visitors are just hard coded as a new methods or aliases.

The reason I’m asking is because I get “TypeError: Cannot visit ClassName” when I override reader methods with some sort of composed object (similar to Active Record’s composed_of) and use that attribute in a uniqueness validation.

Take the following for example:

class Cat < ActiveRecord::Base

validates :name, :uniqueness => { :scope => [:breed] }

def breed

CatBreed.new(super)

end

end

class CatBreed

def initialize(breed)

@breed = breed

end

def to_s

@breed

end

end

The above code will raise “TypeError: Cannot visit CatBreed” when I try to create a new cat unless I add a visitor method to Arel::Visitors::ToSql called visit_CatBreed:

class Arel::Visitors::ToSql

def visit_CatBreed(value)

quoted(value.to_s)

end

end

I’m hoping there is a better way to deal with this problem, either with a less hacky ARel approach or something different all together.

Is there an approved way to add or register a new ARel visitor without
adding it to the ARel source? Looking at the some of the source code<https://github.com/rails/arel/blob/master/lib/arel/visitors/to_sql.rb#L434-466> it
appears that new visitors are just hard coded as a new methods or aliases.

The reason I'm asking is because I get "TypeError: Cannot visit ClassName"
when I override reader methods with some sort of composed object (similar
to Active Record's composed_of) and use that attribute in a uniqueness
validation.

Take the following for example:

class Cat < ActiveRecord::Base
  validates :name, :uniqueness => { :scope => [:breed] }

  def breed
    CatBreed.new(super)
  end
end

class CatBreed
  def initialize(breed)
    @breed = breed
  end

  def to_s
    @breed
  end
end

The above code will raise "TypeError: Cannot visit CatBreed" when I try to
create a new cat unless I add a visitor method to Arel::Visitors::ToSql
called visit_CatBreed:

class Arel::Visitors::ToSql
  def visit_CatBreed(value)
    quoted(value.to_s)
  end
end

Doing this is fine for now. The way the AST is translated to SQL won't
change, so this will probably not break in the future.

I'm hoping there is a better way to deal with this problem, either with a
less hacky ARel approach or something different all together.

It seems like we should be using the underlying database value (the
attribute value rather than the return value of the method call) for the
unique constraint. I think this might be a bug. Jon, wdyt?

Aaron,

Thanks for the reply. I played around with it a little and it seems like changing how the scope_value is retrieved in ActiveRecordmodule::Validationsclass::UniquenessValidator would fix the issue (though I’m not sure what sort of repercussions this would have elsewhere)

scope_value = record.send(scope_item)

to either of these

record[scope_item]
record.read_attribute(scope_item)

I'm hoping there is a better way to deal with this problem, either with a
less hacky ARel approach or something different all together.

It seems like we should be using the underlying database value (the
attribute value rather than the return value of the method call) for the
unique constraint. I think this might be a bug. Jon, wdyt?

Yes, I agree. Who wants to create a patch? :slight_smile:

Me :slight_smile: https://github.com/rails/rails/pull/7072