1

So the goal is to turn for instance "ProductCustomer", which comes from the class, into "product customer".

I used to have this:

notification.notifiable.model_name.human.downcase

It didn't work out of course, since if the notifiable is nil it breaks. I don't want to use try or something similar since it can be solved with using notifiable_type.

So now I changed to this:

notification.notifiable_type.split(/(?=[A-Z])/).join(' ').downcase

But this is way too complex to use every time in the view. So either I would like to define this as a view helper or using some ruby formatting method if there is a simple one.

Can somebody tell me what the Rails convention is in this case? If it's a helper, how does the method looks like and where should I put it?

8
  • "ProductCustomer".underscore.gsub("_", " ") Commented May 25, 2016 at 15:36
  • 'ProductCustomer'.to_s.underscore.tr('_', ' ') Commented May 25, 2016 at 15:37
  • @АлексейКузнецов why need the string 'ProductCustomer' convert to string again with to_s? Commented May 25, 2016 at 15:39
  • @Зелёный because ProductCustomer is probably class object: "ProductCustomer", which comes from the class Commented May 25, 2016 at 15:48
  • In your comment is not a class object 'ProductCustomer'.to_s.underscore.tr('_', ' ') is a string. This is a string 'ProductCustomer' this is an object/class/constant ProductCustomer. Commented May 25, 2016 at 15:53

2 Answers 2

2

Options:

Initializer

/your_app/config/initializers/my_initializer.rb

module MyModule
  def human_model_name
    self.class.to_s.tableize.singularize.humanize.downcase
  end
end

ActiveRecord::Base.send(:include, MyModule)

Including MyModule in ActiveRecord::Base will add human_model_name in all ActiveRecord instances. So, you will be able to do...

user.human_model_name #=> user
notification.human_model_name #=> notification
notification.notifiable.human_model_name #=> product customer
any_active_record_instance.human_model_name #=> etc.

To avoid exceptions when notifiable is nil, you can use try method.

notification.try(:notifiable).try(:human_model_name)

A cleaner way can be use delegate

class Notification < ActiveRecord::Base
  delegate :human_model_name, to: :notifiable, prefix: true, allow_nil: true
end

Then, you can do:

notification.notifiable_human_model_name # if notifiable is nil will return nil as result instead of an exception

A simple method in your Notification model

class Notification < ActiveRecord::Base
  def human_notifable_name
    return unless self.notifiable # to avoid exception with nil notifiable
    self.notifiable.class.to_s.tableize.singularize.humanize.downcase
  end
end

Then...

notification.human_notifable_name

View Helper (If you think this is a view related method only)

module ApplicationHelper # or NotificationHelper
  def human_model_name(instance)
     return unless instance # to avoid exception with nil instance
    instance.class.to_s.tableize.singularize.humanize.downcase
  end
end

Then, in your view...

<%= human_model_name(notification.notifiable) %>

Either option is fine. I would use one or the other depending on the case. In this case, I would use the first option. I think you are adding behaviour that can be useful in any model. I mean your method is not directly related with something about notifications. In a more generic way you want a method to return the class name of an ActiveRecord's instance. Today you want the model name of the notifiable ActiveRecord's instance. But, tomorrow you may want the model name of any ActiveRecord model.

To answer the question "Where should I put a method?" I suggest to break (without fear) a little bit the MVC pattern and read about:

(a little bit old, but you can get the idea)

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the detailed answer Leantraxxx!. I think (not sure), you are using notifiable instead of notification_type, in which case the null object problem still exists. Could you pls update it? Yeah my problem is that either option is fine :). It's hard to decide since I'm not that experienced yet. As I see this is only a view problem for me since I'd use the method to make things nicer for the user. Could you elaborate why you think "adding behaviour that can be useful in any model"?
Pls also see my prev comment. so not notification_type but notifiable_type
Could you tell me why you are sticking with notification.notifiable over notification.notifiable_type? Is it better to use that or something else?
notifiable_type (string) and notifiable_id (integer) are columns in database to build the polymorphic notifiable ActiveRecord instance. Im extending ActiveRecord using ActiveRecord::Base.send(:include, MyModule). If I use the string notifiable_type I would need to extend String instead of ActiveRecord class and execute notification.notifiable_type.human_name. If you do that your method will be more generic. It will be available for any string: "LeandroSegovia".human_name #=> leandro segovia
1

"ProductCustomer".tableize.singularize.humanize.downcase

1 Comment

Teddy, I don't feel this one simpler. Correct me pls, if I'm wrong!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.