Inheriting Rails i18n validation error messages in the subclass
Asked Answered
S

2

5

What I understand

Suppose I have a class with a handy validation like:

User < ActiveRecord::Base
    validates :username, :format => {/regex/}, :message => :name_format
end

In this case, I can use i18n to make the error message translatable, by including the following in my /config/locals/en.yml:

en:
    activerecord:
        errors:
            models:
                user:
                    attributes:
                        username:
                            name_format: 'has the way-wrong format, bro!'

This is fine and generally really handy.

What I want to know:

My question is: What happens when I have subclasses that inherit from User:

UserSubclassOne < User
    # extra stuff
end
UserSubclassTwo < User
    # extra stuff
end
...
UserSubclassEnn < User
    # extra stuff
end

Now the problem is that Rails can't find the translation user_subclass_one.attributes.username.name_format.

It complains:

translation missing:
en.activerecord.errors.models.user_subclass_one.attributes.username.name_format

I'd hope that Rails would look up the hierarchy of UserSubclassOne to User when searching for a string in en.yml and then notice when it gets a 'hit', but (unless I've done something horribly wrong) apparently that doesn't happen.

An obvious solution is to duplicate the data in en.yml.en.errors.models for user, user_subclass_one, user_subclass_two, etc, but my Rails-sense tells me that this is deeply wrong.

Any ideas, folks?

Potential Complication:

User is defined in a gem MyGem that is included in a Rails engine MyEngine that is included in the full-on Rails app MyApp that defines UserSubclassOne, ..., UserSubclassEnn. I don't think this should matter though, since the validations are running in MyGem::User, which is where the en.yml file lives -- just wanted to let people know in case it does.

Ultimate problem/solution:

So it turns out that the problem was namespacing. Recall that MyApp (which defines UserSubclassOne) uses MyGem (which defines User). It turns out User is actually in the namespace MyGem (this is not necessarily always the case), so the full declaration line at the beginning of User is not:

User < ActiveRecord::Base

but rather

MyGem::User < ActiveRecord::Base

.

When the i18n gem looks up the class hierarchy, it notices this namespace and searches for my_gem/user, rather than simply user, my_gem.user, my_gem: user, etc.

Thus I had to change my en.yml file to: /config/locals/en.yml:

en:
    activerecord:
        errors:
            models:
                my_gem/user:
                    attributes:
                        username:
                            name_format: 'has the way-wrong format, bro!'

and bingo!

Salmonella answered 15/2, 2013 at 0:37 Comment(0)
S
6

So it turns out that the problem was namespacing. Recall that MyApp (which defines UserSubclassOne) uses MyGem (which defines User). It turns out User is actually in the namespace MyGem (this is not necessarily always the case), so the full declaration line at the beginning of User is not:

User < ActiveRecord::Base

but rather

MyGem::User < ActiveRecord::Base

.

When the i18n gem looks up the class hierarchy, it notices this namespace and searches for my_gem/user, rather than simply user, my_gem.user, my_gem: user, etc.

Thus I had to change my en.yml file to: /config/locals/en.yml:

en:
    activerecord:
        errors:
            models:
                my_gem/user:
                    attributes:
                        username:
                            name_format: 'has the way-wrong format, bro!'

and bingo!

Salmonella answered 21/2, 2013 at 19:17 Comment(1)
A belated thank you. I have been pulling my hair out. I knew it was namespacing-related but couldn't get the errors to load without this help. The Rails Engine spec is almost useless. They completely left out this rather important detail.Sensory
W
2

According to the Rails Guides for i18n regarding Error Message Scopes (5.1.1) for Active Record validation error messages, what you're attempting to do should work:

Consider a User model with a validation for the name attribute like this:

class User < ActiveRecord::Base
  validates :name, :presence => true
end

<...snip...>

When your models are additionally using inheritance then the messages are looked up in the inheritance chain.

For example, you might have an Admin model inheriting from User:

class Admin < User
  validates :name, :presence => true
end

Then Active Record will look for messages in this order:

activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank

This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models, or default scopes.

So, in your case, assuming your classes look something like this:

app/models/user.rb

User < ActiveRecord::Base
  validates :username, :format => {/regex/}, :message => :name_format
end

app/models/user_subclass.rb

UserSubclass < User
  validates :username, :format => {/regex/}, :message => :name_format
end

and your config/locales/en.yml looks something like:

en:
  activerecord:
    errors:
      models:
        user:
          attributes:
            username:
              name_format: 'has the way-wrong format, bro!'

then the message searching for a validation on UserSubClass should go:

activerecord.errors.models.user_sublcass.attributes.username.name_format # fail
activerecord.errors.models.user_sublcass.name_format                     # fail
activerecord.errors.models.user.attributes.username.name_format          # success
activerecord.errors.models.user.name_format
# ...

Assuming that your model files and yaml files look similar to what's above, then the potential complication you mentioned may be the issue, but obviously I can't be certain.

Wynd answered 15/2, 2013 at 4:5 Comment(4)
+1 for solid reference! I'll keep trying to see whether I've messed up or Rails isn't behaving as promised. When it's resolved, I'll mark this answer as accepted if appropriate.Salmonella
So it turns out the main issue was indeed the "Potential Complication". The correct key for UserClass should have been my_gem/user_class (rather than simply user_class or even my_gem.user_class).Salmonella
That's a good point! Would you be able to please add this detail as an edit to your question for the reference of anyone else with a similar issue? Also, could you either put what your solution ended up being in your question (and accept this answer), or put it in an answer of your own and accept that.Wynd
Will do, Paul! Thanks for the gentle prod ;)Salmonella

© 2022 - 2024 — McMap. All rights reserved.