4

CanCanCan displays localised flash messages if a resource is not authorised.

https://github.com/CanCanCommunity/cancancan/wiki/Translations-(i18n)

# en.yml
en:
  unauthorized:
    manage:
      all: "You do not have access to %{action} %{subject}!"

I have dug through the GitHub repo, but I can't figure out how the subject variable is defined.

From the output, I would guess that subject is defined as something like object.class.name.underscore.

I want to alter this to use object.model_name.human.

This would make it much more consistent with Rails conventions, and easier to localize.

en:
  activerecord:
    models:
      mymodel: MyLocalizedSubjectName

Can someone point me to code that defines subject, or suggest how I can patch CanCanCan to use localized model names?

Andy Harvey
  • 12,333
  • 17
  • 93
  • 185

3 Answers3

2

It looks to me like the message is coming from the unauthorized_message method in ability.rb. In particular these lines:

  variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
  message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + ['']))

So you could redefine that method to do what you want (although it is already using humanize).

EDIT: Including a note about i18n keys when using human and namespaced models. Given a locale file like this:

en:
  activerecord:
    models:
      user: xxxx
      base: aaaa
      activerecord/base: bbbb
      active_record/base: cccc

I get these results:

2.4.1 :001 > User.model_name.human
 => "xxxx" 
2.4.1 :002 > ActiveRecord::Base.model_name.human
 => "cccc" 
2.4.1 :003 > 

You can also say ActiveRecord::Base.model_name.i18n_key to avoid some trial-and-error.

Paul A Jungwirth
  • 23,504
  • 14
  • 74
  • 93
  • Thanks @paul. My issue is that the current implementation using `underscore` does not support namespaced classes. For example if subject is a `Namespace::Model`, then this method returns `namespace/model` which is not compatible with `humanize` or Rails locale files. I'm thinking to patch this method, and replace `subject.class.to_s.underscore.humanize` with `subject.model_name.param_key.humanize`. Does this sound reasonable? Or am I missing something obvious with the CanCanCan implementation? – Andy Harvey Mar 02 '18 at 05:40
  • 1
    Sounds reasonable to me! I'm sure the inline conditional is there for a reason, so I'd say: `variables[:subject] = (subject.class == Class ? subject : subject.class).model_name.human`. For example `ActiveRecord::Base.model_name.human` gives just `"Base"`. – Paul A Jungwirth Mar 02 '18 at 15:24
  • 1
    Added a note about what to put in your translation files if you use `model_name.human`. – Paul A Jungwirth Mar 02 '18 at 15:41
  • 1
    I bet the CanCanCan people would even accept a pull request to change `to_s.underscore.humanize.downcase` to `model_name.human`, since the former [doesn't do any localization](https://github.com/rails/rails/blob/acbcef6094d61eb8c4820295620d170743a4bd71/activesupport/lib/active_support/inflector/methods.rb#L103-L149). It seems like an obvious improvement. – Paul A Jungwirth Mar 02 '18 at 15:46
  • Thanks @paul, this has got me on the right track. I've also opened an issue over at CanCanCan. Feel free to add if you wish https://github.com/CanCanCommunity/cancancan/issues/486 – Andy Harvey Mar 03 '18 at 13:19
0

You can override your message by specifying it in the yml file. In your case:

en:
  unauthorized:
    edit:
      widget: "You do not have access to edit item!"

if you want to change the name of widget in all the cases you can try with:

en:
  unauthorized:
    manage:
      widget: "You do not have access to %{action} item!"

the last option you have is to simply override your Widget class to_s method:

class Widget
  def self.to_s
    'Item'
  end
end
coorasse
  • 5,278
  • 1
  • 34
  • 45
  • Thanks @coorasse, however rather than defining every model 1-by-1, I'm really looking for a generic solution. My app's model names are already localized as per Rails convention, and I'm trying to figure out how/if `%{subject}` can be coerced to used these values. – Andy Harvey Feb 26 '18 at 08:30
0

localize are in the folder path config/locale, if you simply want to change default message, you should find here what you are looking for. Otherwise, if you want to change language or have multiple languages, you could set it on you config/application.rb by setting, for examples:

I18n.available_locales = [:en, :it]
config.i18n.default_locale = :it

The first line make languages available, default one is en.yml but you could have more depending on the gem you have installed. I have devise for example and have four file: en.yml, it.yml, devise.en.yml and devise.it.yml. To make available a language, make sure to have duplication for every file. If you want to switch from one to another in your application by controller you could by:

I18n.locale = :it
I18n.locale = :en

or if you want to set the default one by controller you could by:

I18n.locale = I18n.default_locale

Hope I could be helpful, bye

Pietro Allievi
  • 386
  • 6
  • 14
  • Thanks @Pietro. Unfortunately, the flash generated by CanCanCan does not appear to be compatible with Rails `config/locale` files if the underlying model is namespaced. Perhaps I'm overlooking something obvious? – Andy Harvey Mar 02 '18 at 12:30