I have a Ruby module in a file called my_module.rb:
module My_module
def my_module_method
puts 'inside my method'
end
end
In a file my_class.rb in the same folder, I have a class contained within the module.
module My_module
class My_class
def my_object_method
My_module.my_module_method
end
end
end
My_module::My_class.new.my_object_method => 'undefined method 'my_module_method''
I was not expecting this error. I assumed that Ruby would run into the line 'My_module.my_module_method' and search for a module called 'My_module' and a method within it called 'my_module_method.' This is what Java does, for example. However, Ruby does not do this. In order to get my_object_method to work, I have to write in my_class.rb:
require 'my_module.rb'
Why doesn't Ruby search for My_module when I call my_object_method? It seems obvious what it should search for and therefore redundant to require the programmer to explicitly write 'yes, Ruby, please allow me to make calls to module-wide methods.' What am I missing?
Ruby doesn't automatically load files. If you need a code from some file, you have to load it (by calling require) explicitly.
Thus, when you run "ruby my_class.rb" it loads only this file and you have to define dependencies between files by yourself.
You seem to have a misunderstanding of how to define a class method. In order to make your method call work, you could define it as def self.my_method_name.
In both classes and modules, methods work the same when you define them as class methods using self. or alternatively the class << self syntax. However instance methods (methods without the self.) work differently in these 2 cases. In classes, as you seem to understand, they're accessible once you instantiate the class using .new. In modules, they're only accessible if you include or extend.
See also:
difference between class method , instance method , instance variable , class variable?
http://www.rortuts.com/ruby/ruby-include-vs-extend/
Oh any by the way. Ruby doesn't enforce any convention where you have 1 file per class (named identically). You need to manually require files wherever you need them. Although there are some frameworks such as Rails which auto-require files, and enforce naming conventions.
Related
I would like to namespace my Ruby classes by putting them in a module. Indeed, this is a good idea if I decide to publish my Ruby gem so that the class names do not clash with existing classes in another gem. I know I can do the following for a class A::B:
module A
class B
end
end
However, the above is quite cumbersome in that I need to put all my class definitions into a single Ruby source file in order to scope them under the module. I would rather keep my class definitions in separate source files, much like a Java project, so how can I add classes to a module when they are all defined in separate files?
The accepted practice in this case is to wrap every file in module block
# a/b.rb
module A
class B
end
end
# a/c.rb
module A
class C
end
end
Btw. because of the way constant are resolved, it is advisable to use the long form I quoted above instead class A::B
(http://blog.honeybadger.io/avoid-these-traps-when-nesting-ruby-modules/).
Sinatra defines a number of methods which appear to live in the current scope, i.e. not within a class declaration. These are defined in the Sinatra gem.
I'd like to be able to write a gem which will create a function which I can call from the global scope, e.g
add_blog(:my_blog)
This would then call the function my_blog in the global scope.
Obviously I could monkeypatch the Object class in the gem with the add_blog function, but this seems like overkill as it would extend every object.
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
I have a class and a module which have the same names:
module Pushover
def configure
..
end
end
module MyModule
class Pushover
def blah
Pushover.configure
end
end
end
This doesn't work because the Pushover.configure call directs to the containing class. Now, an obvious fix would be to rename the class. However, the Module is from a gem and the class conforms to a naming convention required in a DSL. So ideally they should both stay the same. I could also create a second helper class and call via that, but that all seems a little hacky. My preferred solution would be to directly reference the module method.
All the existing questions around this area seem to be disambiguating in the opposite direction - i.e. they want to get the class reference not the module.
Is there any way for me to inform Ruby that I mean the module rather than the class when I specify Pushover?
If you don't want to look up the constant relative to the current scope, just use an absolute path:
::Pushover.configure
I am in a position at the moment where I have a plugins folder where there there could be 1 or 100 plugins to be loaded.
Now the problem is, each plugin requires an instance of a class defined within the startup ruby file.
A really simplified example would be:
#startup.rb
def load_plugins
#... get each plugin file
require each_plugin
end
class MuchUsedClass
def do_something
#...
end
end
muchUsedInstance = MuchUsedClass.new
load_plugins
#some_plugin.rb
class SomePluginClass
def initialize(muchUsedInstance)
#muchUsedInstance = muchUsedInstance
end
def do_something_with_instance
#muchUsedInstance.do_something
end
end
somePluginInstance = SomePluginClass.new(muchUsedInstance)
somePluginInstance.do_something_with_instance
The main problem is that when you call require, it doesn't have any clue about what has happened before it is being required. So I find it nasty making a global variable within the startup file just to satisfy all other required files, but it seems like one of the only ways to be able to pass some data down to an included file, I could also make a singleton class to expose some of this, but that also seems a bit nasty.
As I am still new to ruby and am still looking through the statically typed glasses, I will probably be missing a decent pattern to solve this, in C# I would opt for dependency injection and just hook everything up that way...
Your example code does not have a global variable. Global variables have names that start with $. The code as you wrote it won't work, because muchUsedInstance is just a local variable and will not be shared between different Ruby files.
If you are not going to change the instance ever, you could easily store it as a constant:
MuchUsedInstance = MuchUsedClass.new
You could store it as a nested constant inside the class:
MuchUsedClass::Instance = MuchUsedClass.new
You could store it as an instance variable inside the class object, with a getter method that automatically creates it if it isn't there already:
def MuchUsedClass.instance
#instance ||= MuchUsedClass.new
end
I'm writing a small gem, and I want to define a DSL-like method, pretty much the same as the desc and task methods in Rake.
Rake defines them as private methods in the Rake::DSL module and then
self.extend Rake::DSL
to mix the module into the main object? (I'm a newbie and go ahead laugh if I'm wrong)
what are the benefits by doing so? is it because making these methods private can prevent any other objects to use them (that is, to prevent something like some_obj.desc) ?
what if I define the methods in Kernel
module Kernel
private
include Rake::DSL
end
Is there any difference?
If you define private method in Kernel module it will be available in the whole project. You will also rewrite desc method that project use to define rake task. But if you write your methods in your submodule and then extend it in superclass or some module - you can easily write any kind of DSL lang like you might saw in Rake or RSpec.
P.S. Making methods private prevents other moludes or classes (but not subclasses) to use them (but not owerwrite) - I mean module nesting hierarchy.
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).