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&gt; 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