Ways to Define a Global Method in Ruby

How can I add a method to the global scope in Ruby?

TL;DR extend-ing a module at the top level adds its methods to the top level without adding them to every object.

There are three ways to do this:

Option 1: Write a top-level method in your gem

#my_gem.rb

def add_blog(blog_name)
puts "Adding blog #{blog_name}..."
end
#some_app.rb

require 'my_gem' #Assume your gem is in $LOAD_PATH
add_blog 'Super simple blog!'

This will work, but it's not the neatest way of doing it: it's impossible to require your gem without adding the method to the top level. Some users may want to use your gem without this.

Ideally we'd have some way of making it available in a scoped way OR at the top level, according to the user's preference. To do that, we'll define your method(s) within a module:

#my_gem.rb
module MyGem

#We will add methods in this module to the top-level scope
module TopLevel
def self.add_blog(blog_name)
puts "Adding blog #{blog_name}..."
end
end

#We could also add other, non-top-level methods or classes here
end

Now our code is nicely scoped. The question is how do we make it accessible from the top level, so we don't always need to call MyGem::TopLevel.add_blog ?

Let's look at what the top level in Ruby actually means. Ruby is a highly object oriented language. That means, amongst other things, that all methods are bound to an object. When you call an apparently global method like puts or require, you're actually calling a method on a "default" object called main.

Therefore if we want to add a method to the top-level scope we need to add it to main. There are a couple of ways we could do this.

Option 2: Adding methods to Object

main is an instance of the class Object. If we add the methods from our module to Object (the monkey patch the OP referred to), we'll be able to use them from main and therefore the top level. We can do that by using our module as a mixin:

#my_gem.rb

module MyGem
module TopLevel
def self.add_blog
#...
end
end
end

class Object
include MyGem::TopLevel
end

We can now call add_blog from the top level. However, this also isn't a perfect solution (as the OP points out), because we haven't just added our new methods to main, we've added them to every instance of Object! Unfortunately almost everything in Ruby is a descendant of Object, so we've just made it possible to call add_blog on anything! For example, 1.add_blog("what does it mean to add a blog to a number?!") .

This is clearly undesirable, but conceptually pretty close to what we want. Let's refine it so we can add methods to main only.

Option 3: Adding methods to main only

So if include adds the methods from a module into a class, could we call it directly on main? Remember if a method is called without an explicit receiver ("owning object"), it will be called on main.

#app.rb

require 'my_gem'
include MyGem::TopLevel

add_blog "it works!"

Looks promising, but still not perfect - it turns out that include adds methods to the receiver's class, not just the receiver, so we'd still be able to do strange things like 1.add_blog("still not a good thing!") .

So, to fix that, we need a method that will add methods to the receiving object only, not its class. extend is that method.

This version will add our gem's methods to the top-level scope without messing with other objects:

#app.rb

require 'my_gem'
extend MyGem::TopLevel

add_blog "it works!"
1.add_blog "this will throw an exception!"

Excellent! The last stage is to set up our Gem so that a user can have our top-level methods added to main without having to call extend themselves. We should also provide a way for users to use our methods in a fully scoped way.

#my_gem/core.rb

module MyGem
module TopLevel
def self.add_blog...

end
end

#my_gem.rb

require './my_gem/core.rb'
extend MyGem::TopLevel

That way users can have your methods automatically added to the top level:

require 'my_gem'
add_blog "Super simple!"

Or can choose to access the methods in a scoped way:

require 'my_gem/core'
MyGem::TopLevel.add_blog "More typing, but more structure"

Ruby acheives this through a bit of magic called the eigenclass. Each Ruby object, as well as being an instance of a class, has a special class of its own - its eigenclass. We're actually using extend to add the MyGem::TopLevel methods to main's eigenclass.


This is the solution I would use. It's also the pattern that Sinatra uses. Sinatra applies it in a slightly more complex way, but it's essentially the same:

When you call

require 'sinatra'

sinatra.rb is effectively prepended to your script. That file calls

require sinatra/main

sinatra/main.rb calls

extend Sinatra::Delegator

Sinatra::Delegator is the equivalent of the MyGem::TopLevel module I described above - it causes main to know about Sinatra-specific methods.

Here's where Delgator differs slightly - instead of adding its methods to main directly, Delegator causes main to pass specific methods on to a designated target class - in this case, Sinatra::Application.

You can see this for yourself in the Sinatra code. A search through sinatra/base.rb shows that, when the Delegator module is extended or included, the calling scope (the "main" object in this case) will delegate the following methods to Sinatra::Application:

:get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
:template, :layout, :before, :after, :error, :not_found, :configure,
:set, :mime_type, :enable, :disable, :use, :development?, :test?,
:production?, :helpers, :settings, :register

ways to define a global method in ruby

Just to extend the answer given by bor1s, about the private methods:

In ruby you have "private" and "protected" methods. What bor1s says, is correct when talking about "protected" methods. Declaring a method "private" additionally prevents other instances of the same class from using the method.

When you call a "private" method, you cannot use a dot in front of it - you cannot even use self., even though using or omitting self has usually the same effect.

class Xyzzy
private
def foo
puts "Foo called"
end

public
def do_it
foo # <= Is OK
self.foo # <= raises NoMethodError
end
end

Xyzzy.new.do_it

If you change 'private' to 'protected' in the code above, no error will be raised.

And about modules:

The final result of defining a method in Kernel and extending Kernel with the method defined in some module is the same: in both cases the method is global.

Using a module is just a little more elegant, as it groups your changes in one place, but I would say it's a matter of personal taste.

Usually you do not include methods in Kernel or Object (as it may be a little dangerous), but you include (or extend) a specific class or object which needs these methods, and in this case you need your methods grouped in a module.

Even Rake in version 0.9.0 stopped including the DSL commands in Object:

== Version 0.9.0

  • Incompatible *change*: Rake DSL commands ('task', 'file', etc.) are
    no longer private methods in Object. If you need to call 'task :xzy' inside
    your class, include Rake::DSL into the class. The DSL is still available at
    the top level scope (via the top level object which extends Rake::DSL).

How to access global method from object?

There maybe a better way if you can explain what exactly you're trying to do. But, if you must to do it anyway then:

def login(user, pass)
puts 'global login'
puts "user: #{user}, pass: #{pass}"
end

class Bob
def login(pass)
self.class.send(:login, 'bob',pass) #ERROR#
end
end

login('hello','world')
bob = Bob.new
bob.login('world')

#=> global login
#=> user: hello, pass: world
#=> global login
#=> user: bob, pass: world

Create a global Rails method inside a module?

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)

Why are global methods allowed to be defined outside of a class in Ruby?

When you define a function in Ruby at the global scope in this way, it technically becomes a private method of the Object class, which is the base class that everything inherits from in Ruby. Everything in Ruby is an object, so it is indeed true that you have defined a method.

def say_goodnight(name)
result = "Goodnight, " + name
return result
end

Object.private_methods.include? :say_goodnight
=> true

Because it is defined as a method with private visibility on Object, it can only be called inside objects of the class on which it's defined or subclasses. So why does it appear to be available globally?

Basically, the Ruby program itself defines an instance of Object called main, which serves as the top-level scope where your method was defined. So if you think of your program as running inside main (which is an Object) its private methods are available for use.

# In irb:
self
=> main
self.class
=> Object
self.private_methods.include? :say_goodnight
=> true

Addendum:
This answer which further explains how main is defined and implemented.

Update for Ruby >= 2.3

Noted in the comment thread, later versions of Ruby would define the method Object#say_goodnight in this example with public visibility rather than private. This behavior appears to have changed between Ruby 2.2.x and 2.3.x, but does not affect method exposure.

Global methods in Ruby

For runtime Ruby has special object the main.

It's some kind of trick that all code runs in the context of this object.

So when you're typing methods like puts, p and so on, all of them are calling in the context of self object and passing to self object.

And here's the second thing - the access control.
As you probably know, Ruby has keywords like private, protected and public - all of them manage the access of calling methods on object. Ruby is checking this access control only when you're have construction like

<name_of_object>.your_method and self.your_method

So when you're typing

self.p "something"

Ruby will decline this call because the p method is private method.

Ruby - create a global variable from within a method

You can create a global variable from everywhere if you prefix it with a "$" sign, like: $var

If you declare it in a method, make sure you run that method before calling your variable.

For example:

def global_test
$name = "Josh"
end

$name # => nil

global_test() # Your method should run at least once to initialize the variable

$name # => "Josh"

So, in your example, if you would like to use s_name from outside of your method, you can do it by adding a '$' sign before it like this: $s_name

Here is a good description about ruby variable types:

http://www.techotopia.com/index.php/Ruby_Variable_Scope

But as you can read in it (and mostly everywhere in "ruby best practices" and styleguides) it is better to avoid global variables.

How to dynamically create local and global variables?

You cannot define a local variable with a full stop (.) character in ruby. That is not valid syntax.

(eval):2: unexpected fraction part after numeric literal
string_1.0 = "1.0"

Additionally, you cannot dynamically define local variables. There are various workarounds to sort-of achieve this, however, fundamentally I think you are asking an XY problem.

For example, have you considered using an OpenStruct, or passing this hash as locales when rendering a template, or instead dynamically setting instance variables?



Related Topics



Leave a reply



Submit