17

I'm working on a small DataMapper-like ODM project, and I'm trying to make use of the ActiveModel::Validations component. However, I ran into a problem while writing tests - I'm using anonymous classes to construct my test schemas, however when it comes to running the validators, the ActiveModel::Name class is throwing an error: Class name cannot be blank. You need to supply a name argument when anonymous class given

Here's a simple code example to reproduce:

require 'active_model'

book_class = Class.new do
  include ActiveModel::Validations

  validates_presence_of :title

  def title; ""; end # This will fail validation
end

book_class.new.valid? # => throws error

The exception is only raised when there's a failed validator - I'm guessing the problem happens when it tries to construct the validation error message. So my question is:

  • I did a lot of searching, but couldn't find anyone trying to do something similar. Is this simply not possible with ActiveModel, or is there a workaround I'm not aware of?
motns
  • 305
  • 2
  • 6

2 Answers2

24

ActiveModel tries to get the model name (as you see here) when setting up the error messages. The quickest way to get around it (short of giving your anonymous class a name), is to give your class a class method model_name that returns an instance of ActiveModel::Name.

for example

require 'active_model'

book_class = Class.new do
  include ActiveModel::Validations
  def self.model_name
    ActiveModel::Name.new(self, nil, "temp")
  end
  validates_presence_of :title

  def title; ""; end # This will fail validation
end

book_class.new.valid? # => no error
Jim Deville
  • 10,632
  • 1
  • 37
  • 47
  • Thanks, exactly what I wanted! I noticed that `ActiveModel::Name` can take a class name as an argument, but didn't spot that I can return my own instance via `self.model_name`. Another reason not to program late at night... :P – motns Jan 21 '13 at 11:40
  • Why return an instance of `ActiveModel::Name` rather than a plain string from `.model_name`? – Gabe Kopley Mar 09 '16 at 00:15
  • This was 2 years ago, but IIRC, the validation process expects (expected?) an instance of ActiveModel::Name from that method. – Jim Deville Mar 11 '16 at 16:39
13

The error is being thrown in the initialize function of ActiveModel::Name here.

module ActiveModel
  class Name
    def initialize(klass, namespace = nil, name = nil)
      @name = name || klass.name

      raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?       
      # ...
    end
  end
end

So rather than defining a class method model_name that returns an ActiveModel::Name, you can define a class method name that returns a String.

require 'active_model'

book_class = Class.new do
  include ActiveModel::Validations
  validates_presence_of :title

  def self.name
    "Book"
  end

  def title; ""; end # This will fail validation
end

book_class.new.valid? # => false
Kris
  • 19,188
  • 9
  • 91
  • 111
hjing
  • 4,922
  • 1
  • 26
  • 29
  • 1
    This is essentially because `Class.new.name #=> nil`, so either passing `name` to ActiveModel::Name or defining a name method on the class works. – Kris Oct 26 '18 at 11:44