1

I have the following validation in a model:

validates_length_of :description,
  :minimum => 2, :on => :save,
  :message => "must be at least 2 words",
  :tokenizer => lambda {|text| text.scan(/\w+/)}

And this works fine. When I add a second field to the model that needs to be validated by number of words, I declare

tokenize_by_words = lambda {|text| text.scan(/\w+/)}

at the top of the model, and use

:tokenizer => tokenize_by_words

This also works fine, and keeps everything DRY. However, it all falls apart when I need to use the same tokenizer across multiple models. If I create config/initializers/tokenizers.rb thus:

class ActiveRecord::Base
  tokenize_by_words = lambda {|text| text.scan(/\w+/)}
end

and remove the definitions in the models, I get /Library/Ruby/Gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1959:in 'method_missing': undefined local variable or method 'tokenize_by_words' for #<Class:0x10357e988> (NameError)

Using an instance variable or replacing the whole thing with a method doesn't work either.

I'm sure there's something blindingly obvious I'm missing, but the only documentation I can find on :tokenizer doesn't really consider DRY-ness :(

John Y
  • 1,161
  • 12
  • 23
  • My Ruby is a bit rusty, but based on the error message: what if you put `class << self` / `end` around the `tokenize_by_words = ...` line? – Ken Feb 28 '10 at 14:01

2 Answers2

2

You should define the tokenizer as a method, i.e.

class ActiveRecord::Base
  def foo(text)
    text.scan(/\w+/)
  end
end

Now use this method symbol as the value for :tokenizer attribute.

validates_length_of :description,
  :minimum => 2, :on => :save,
  :message => "must be at least 2 words",
  :tokenizer => :foo
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198
  • Thanks — unfortunately I now get this when I run the specfile for the model: NoMethodError in 'GbgSymbols should have a description of at least 2 words' undefined method `call' for :tokenize_by_words:Symbol which is the same as if I haven't defined the tokenizer at all! The config/initializers directory is the right place for the definition, isn't it? – John Y Feb 28 '10 at 17:00
  • It should work. I have verified this. Can you try to put the method in one of the models(instead of ActiveRecord::Base) and see if it works. If it does then there might be a issue in the way in which you are adding this method to ActiveRecord::Base class. Read this thread to get information about extending ActiveRecord: http://stackoverflow.com/questions/2328984/rails-extending-activerecordbase – Harish Shetty Feb 28 '10 at 18:01
  • It's taken a while to get back to this! I've marked your answer as accepted, but I still get the same error, even when putting the method directly in the model. The thread you linked to didn't help, as it just shows I was already doing the right thing. (Since Rails is all about convention over configuration, you'd think there'd be a tokenizers.rb somewhere, wouldn't you? :) – John Y Dec 19 '10 at 15:20
  • I'm also getting the same error. It seems @HarishShetty has his own version of active record :) – Abe Petrillo Jul 01 '13 at 17:22
1

You'll be able to use :symbol for tokenizer.
Please stay tuned ;)

class Article
  include ActiveModel::Model

  validates_length_of :content,
    :minimum => 10,
    :message => "must be at least 10 words",
    :tokenizer => :tokenize_by_words

  def tokenize_by_words(text)
    text.scan(/\w+/)
  end
end

Allow symbol as values for tokenizer of LengthValidator by kakipo · Pull Request #16381 · rails/rails

Kensuke Naito
  • 776
  • 6
  • 4