Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a global Rails method inside a module?

I need to create a method that can be used in any situation (MVC, testing etc). Instead of stuffing it in environments.rb id like to create a module and include it as a gem. What class do I need to extend in my gem in order to get Rails to recognize the methods in this module?

Update:

I'm looking for a solution that allows me to call my method without a module prefixed.

like image 248
user1491929 Avatar asked Sep 03 '15 00:09

user1491929


1 Answers

TLDR

You need to "extend" the Kernel module by including your methods into it with include.

Global methods

Generally a "global" method in Ruby means creating a method in Kernel or Object. The method is then available to all (most) classes because they inherit from Object (which includes Kernel). Your method can be called without a prefix from most places because the implicit self in any given context will be an object that inherits from Object and therefore contains the method you defined.

One way to define such a method is just to place its definition in a file outside of any class or module:

def my_method
  # do fancy stuff
end

Of course, you have to make sure that file gets loaded (i.e. required) somewhere.

Global methods from a module

If you want your method to be organized within a module (which is a good idea), you need to include that module into Kernel like this:

module MySpecialMethods
  def my_method
    # do fancy stuff
  end
end

module Kernel
  private
  include MySpecialMethods # This is where the method gets added to Kernel
end

In Rails, I would put the MySpecialMethods module in lib/, add put the Kernel part inside an initializer file like config/initializers/extend_kernel.rb. You also need to help Rails "find" your module in lib/ by adding config.autoload_paths += %W(#{config.root}/lib) to application.rb

Consider avoiding global methods

It's worth nothing that there are very few situations where it's a good idea to extend Kernel in this way. It's possible (likely?) that for your situation it is better to put your methods in a module and explicitly call them with the module name like MyModule.my_method.

module MyModule
  def self.my_method
    # do fancy stuff here
  end
end

There are many places you could place this code. In Rails, I'd recommend putting it in the lib/ folder as above and then making sure Rails can find it by adding config.autoload_paths += %W(#{config.root}/lib) to application.rb

From a Gem

Creating a gem is beyond the scope of this question, but supposing you created a gem, typically the modules within that gem are available in your Rails app as long as you specify the gem in your Gemfile. This is because Rails apps by default call Bundler.require. As long as the gem follows proper naming conventions, the main gem file is required automatically, and the main gem file requires any other necessary files.

If you want to extend Kernel from a gem, just put the Kernel extension code (from above) inside a file that is required (directly or indirectly) when your gem is required.

Notes

  1. Rails initializers only run once when you boot the Rails server, so changes to them are not autoloaded like many other pieces of Rails in development.

  2. If you're tweaking Rails' autoload_paths, you might also want to tweak eager_load_paths (see this blog post)

  3. There are some objects in Ruby that do not inherit from Object (e.g. BasicObject), so technically these "global" methods will not be available everywhere. That's not likely to cause problems for you.

  4. Using Bundler.require in a Rails app is perhaps controversial (see this blog post for more information on that)

like image 110
Nathan Avatar answered Sep 30 '22 11:09

Nathan