Why Are Global Methods Allowed to Be Defined Outside of a Class in Ruby

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.

Why can some classes and/or methods be called without instances of their parent class?

First of all, your intuition is correct.

Every methods must be an instance method of some receiver.

Global methods are defined as private instance methods on Object class and hence seem to be globally available. Why? From any context Object is always in the class hierarchy of self and hence private methods on Object are always callable without receiver.

def fuuuuuuuuuuun
end

Object.private_methods.include?(:fuuuuuuuuuuun)
# => true

Class methods are defined as instance methods on the "singleton class" of their class instance. Every object in Ruby has two classes, a "singleton class" with instance methods just for that one single object and a "normal class" with method for all objects of that class. Classes are no different, they are objects of the Class class and may have singleton methods.

class A
class << self # the singleton class
def example
end
end
end

A.singleton_class.instance_methods.include?(:example)
# => true

Alternative ways of defining class methods are

class A
def self.example
end
end

# or

def A.example
end

Fun fact, you can define singleton methods on any object (not just on class objects) using the same syntax def (receiver).(method name) as follows

str = "hello"

def str.square_size
size * size
end

str.square_size
# => 25

"any other string".square_size
# => raises NoMethodError

Some programming language history — Singleton classes are taken from the Smalltalk language where they are called "metaclasses". Basically all object-oriented features in Ruby (as well as the functional-style enumerators on Enumerable) are taken from the Smalltalk language. Smalltalk was an early class-based object-oriented language created in the 70ies. It was also the language that invented graphical user interfaces like overlapping windows and menus et cetera. If you love Ruby maybe also take a look at Smalltalk, you might fall in love yet again.

Using define_method to define global methods outside of a module

I don't know your whole situation, but you probably don't really want to be defining global methods. If you don't want to type the HTML:: then add an include HTML statement at the beginning of your code.

Is a Ruby global variable equivalent to a class variable on Class Object?

1) JS: My understanding is that variables set outside the "outer" function's scope are global in context

This depends on what exactly you mean by "variables". Variables declared with const or let are lexically scoped.

Variables declared with var in the top-level context aren't actually variables at all, they become properties of the top-level object (e.g. window, document, global, … depending on your environment).

and store a location to the assigned value.

That is true of all variables in both Ruby and ECMAScript, as well as properties in ECMAScript.

3) "Everything" in Ruby is an object (except blocks and individual index items within an array), including classes, primitives, etc.

This really depends on what exactly you mean by "everything". There are lots of "objects" (in the sense of "things we can talk about") in Ruby that aren't "objects" (in the sense of "things we can assign to variables, pass around, manipulate in Ruby code"). For example, variables aren't objects in Ruby.

A) Is the Rails.application object actually an instance of the Application Class?

I have no idea what this has to do with global variables.

Is there any difference between a Ruby global variable:

$var = "A Ruby global variable"

and

Class Object
@@var = "A class variable set on the Class Object"
end

Yes, there is the pretty obvious difference that objects and classes which don't have Object in their ancestors chain won't have access to Object's class variables:

class BasicObject
@@var
end
# NameError: uninitialized class variable @@var in BasicObject

Is Class Object, as the Class from which all other Classes/Objects ultimately inherit, therefore, Ruby's "global" context (or not, and I missed something huge)?

The Object class acts as a sort-of global context for some stuff, but that is not because it is global, but rather because it is the parent of most classes (basically anything that doesn't extend directly from BasicObject).

Anything that uses inheritance (class variables, methods, and constants) and is defined in Object will also be available in everything that descends from Object. But that has nothing to do with "global". That's just how inheritance works. Note that Object itself inherits from Kernel and BasicObject, so this is true for anything defined in those two as well.

C) (added as an edit) Because a global variable needs to be initiated and is available in a global scope, it is a class variable and not an instance variable. Is that reasoning sound?

No, it is not. A global variable is neither an instance variable nor a class variable. It is a global variable.

If a global variable is an object, it has to inherit from Class Object, right? This would mean the "global" context is still wrapped within Class Object.

Variables aren't objects in Ruby.

If I define a method in Ruby, does it belong to any class?

If you define a method outside of any class, it will become a private method of the Object class.

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)

Can't access array defined outside my ruby function

Ruby is an object oriented language. Even more, Ruby appeared on the scene with a motto “everything is an object.” Even numbers and (sic!) nil in Ruby are objects:

▶ 42.class
#⇒ Fixnum < Integer
▶ nil.__id__
#⇒ 8

So, you are supposed to use objects for anything that is slightly more complicated than a one-liner. Objects have a lot of goodness out of the box: instance variables, lifecycle, etc.

class Game
def initialize
@coins = []
end

def add_coin(value)
@coins << value
end

def coins
@coins
end

def amount
@coins.size
end
end

Now you might create in instance of this class and while it’s alive, it will hold the value of @coins:

game = Game.new
game.add_coin("1")
puts game.coins
#⇒ ["1"]
puts game.amount
#⇒ 1
game.add_coin("1")
game.add_coin("2")
puts game.coins
#⇒ ["1", "1", "2"]
puts game.amount
#⇒ 3

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.

Module vs methods outside module

Modules allow you group your methods and you can include them in other modules and classes. You can include module methods into the module/class itself or its eigenclass. So, with modules you have more flexibility. Not to mention that you are not cluttering up the "global" namespace.

Also, when you define method in a file, you are defining it in the current context. Since, everything in Ruby is an object, you are actually defining it within a main object (an instance of Object class).

2.3.0 :001 > self
=> main
2.3.0 :002 > self.class
=> Object
2.3.0 :003 > self.object_id
=> 70123533022000
2.3.0 :004 > def test; end;
2.3.0 :005 > self.public_methods.include? :test
=> true


Related Topics



Leave a reply



Submit