Calling custom method from within a Ruby core class - ruby

TL;DR;
How can I call a method (written in the middle of nowhere) from within a Ruby core class?
I'm writing a script that manages text files. Here is my code:
File.open("file.txt", "w").each do |f|
f.puts "Text to be inserted"
f.puts text_generated
f.puts "Some other text" if some_condition?
f.puts ""
end
I want to clean up the code by introducing a method:
File.open("file.txt", "w").each do |f|
f.puts_content(text_generated, some_condition?
# -> Generates an error: private method called for <File> (NoMethodError)
end
def puts_content(text, cond)
puts "Text to be inserted"
puts text
puts "Some other text" if cond
puts ""
end
But in fact this method is not callable from within the File class because of private method access.
Can anyone explain that error, and how I can do that?
My workaround is to write those methods inside a custom MyFile class that inherits from File:
MyFile.open("file.txt", "w").each do |f|
f.puts_content # Seems to work
end
class MyFile < File
def puts_content(cond)
puts "Text to be inserted"
puts text_generated_elsewhere
puts "Some other text" if cond
puts ""
end
end
I could put that stuff directly in File, but I'm timid when it comes to touching a language core library.
I want to know if this is a good way to do it.
It is possible to call Ruby core methods from other core modules/classes. Does it mean that all the core modules/classes include or require one another? How does it work under the hood?

When you define a method on the top level, it is added an an instance method on Object and therefore accessible to descendent classes (most of the other core classes)
def foo
1
end
method(:foo)
# => #<Method: Object#foo>
However the access level of this method seems to be different in IRB/pry than when running a script.
In IRB:
puts [].foo
# => 1
In a script:
puts [].foo
# => NoMethodError (private method called...)
Of course, you can always just call the private method using send:
[].send(:foo)
# or, in your case, f.send(:puts_content, text_generated, some_condition?)
Also, in neither case will it overwrite the method on the descendant class if it was already defined:
def length
1
end
puts [].length
# => 0
Your second approach (patching the core class directly) will work and will overwrite the puts_content if it was already defined on File (which it wasn't). However if you want to avoid patching core classes, there are two approaches I recommend:
Use a static (class) method and pass the file object as an argument
class FileUtils
def self.puts_content(file, text, cond)
file.puts "Text to be inserted"
file.puts text
file.puts "Some other text" if cond
file.puts ""
end
end
File.open("file.txt", "w").each do |f|
FileUtils.puts_content(f, text_generated, some_condition?)
end
use a refinement:
module FileUtils
refine File do
def puts_content(text, cond)
puts "Text to be inserted"
puts text
puts "Some other text" if cond
puts ""
end
end
end
# elsewhere ...
using FileUtils
File.open("file.txt", "w").each do |f|
f.puts_content(f, text_generated, some_condition?)
end
You can read about refinements here, but essentially, they're a way to patch a core class in a certain file or class only. This gives you the benefit of that nice, terse monkey-patched syntax with less risk of changing the behavior defined elsewhere.

About your first question. You were getting the error because the method wasn't defined in the File class. So you were not able to call it like this f.puts_content.
You could define a method that receives the File as a parameter puts_content(file, ...).
About the second part of your question, I this is a good solution (thinking object-oriented).

Related

Why can't a class method have the same name as a non-class method?

I'm learning ruby, and noticed that I cannot create a class method called puts:
class Printer
def initialize(text="")
#text = text
end
def puts
puts #text
end
end
The error is:
`puts': wrong number of arguments (given 1, expected 0)
My expectation was that I could use the code like this:
p = Printer.new("hello")
p.puts
It's not just because puts is a built-in method, though. For instance, this code also gives a syntax error:
def my_puts(text)
puts text
end
class Printer
def initialize(text="")
#text = text
end
def my_puts
my_puts #name
end
end
tldr; within the scope of the instance, the puts resolves to self.puts (which then resolves to the locally defined method, and not Kernel#puts). This method overriding is a form of shadowing.
Ruby has an 'implicit self' which is the basis for this behavior and is also how the bare puts is resolved - it comes from Kernel, which is mixed into every object.
The Kernel module is included by class Object, so its methods [like Kernel#puts] are available in every Ruby object. These methods are called without a receiver and thus can be called in functional form [such as puts, except when they are overridden].
To call the original same-named method here, the super keyword can be used. However, this doesn't work in the case where X#another_method calls X#puts with arguments when it expects to be calling Kernel#puts. To address that case, see Calling method in parent class from subclass methods in Ruby (either use an alias or instance_method on the appropriate type).
class X
def puts
super "hello!"
end
end
X.new.puts
P.S. The second example should trivially fail, as my_puts clearly does not take any parameters, without any confusion of there being another "puts". Also, it's not a syntax error as it occurs at run-time after any language parsing.
To add to the previous answer (https://stackoverflow.com/a/62268877/13708583), one way to solve this is to create an alias of the original puts which you use in your new puts method.
class Printer
alias_method :original_puts, :puts
attr_reader :text
def initialize(text="")
#text = text
end
def puts
original_puts text
end
end
Printer.new("Hello World").puts
You might be confused from other (static) programming languages in which you can overwrite a method by creating different signatures.
For instance, this will only create one puts method in Ruby (in Java you would have two puts methods (disclaimer: not a Java expert).
def puts(value)
end
def puts
end
If you want to have another method with the same name but accepting different parameters, you need to use optional method parameters like this:
def value(value = "default value")
end

Ruby modules for functions

It is possible to add methods to a class using modules. E.g.,
class Test
include Singleton
end
Is it possible to do the same with methods? E.g.,
class Test
include Singleton
def test
include Instructions_to_add_to_the_method
puts 'done'
end
end
where:
module Instructions_to_add_to_the_method
puts 'hi !'
end
When calling Test.instance.test, I want:
hi !
done
I do not wish to call another method, as it would give me issues with the scope of my variables.
It is possible to add methods to a class using modules. E.g.,
class Test
include Singleton
end
No. This does not "add methods to a class". include simply makes Singleton the superclass of Test. Nothing more. Nothing is being "added to a class". It's just inheritance.
Is it possible to do the same with methods? E.g.,
class Test
include Singleton
def test
include Instructions_to_add_to_the_method
puts 'done'
end
end
There is no method Test#include, so this will simply raise a NoMethodError.
When calling Test.instance.test, I want:
hi !
done
That's what inheritance is for:
class Test
include Singleton
def test
super
puts 'done'
end
end
module Instructions_to_add_to_the_method
def test
puts 'hi'
end
end
class Test
include Instructions_to_add_to_the_method
end
Test.instance.test
# hi
# done
Note that this way of using inheritance in Ruby is a little bit backward. If you really need something like this, you should use a language like Beta, where this is how inheritance works naturally.
A better solution would be something like the Template Method Software Design Pattern which in Ruby can be something as simple as yielding to a block:
class Test
include Singleton
def test
yield
puts 'done'
end
end
Test.instance.test { puts 'hi' }
# hi
# done
or taking a Proc as an argument:
class Test
include Singleton
def test(prc)
prc.()
puts 'done'
end
end
Test.instance.test(-> { puts 'hi' })
# hi
# done
or by calling a hook method:
class Test
include Singleton
def test
extension_hook
puts 'done'
end
def extension_hook; end
end
class HookedTest < Test
def extension_hook
puts 'hi'
end
end
HookedTest.instance.test
# hi
# done
I do not wish to call another method, as it would give me issues with the scope of my variables.
There are no variables in your code, so there can't possibly be any "issues" with them.
This is probably a terrible idea. Messing with your classes at run-time will make problems hard to debug (which version of that method was I calling there?) and your code hard to follow (oh, right, I can't call that method yet because it's not defined yet, I need to call this method first).
Given what you said at the end of your question about variable scoping, I'm almost certain that this won't solve the problem you actually have, and I'd suggest actually posting your actual problem.
That said, the question you asked can be answered by using the included and extended hooks which, unsurprisingly, fire when a module gets included and extended:
module FooModule
def self.included(base)
puts 'FooModule included'
end
def self.extended(base)
puts 'FooModule extended'
end
def new_method
puts 'new method called'
end
end
class Extender
def test_extend
self.extend FooModule
puts 'done'
end
end
class Includer
def test_include
self.class.include FooModule
puts 'done'
end
end
t1 = Extender.new
t2 = Extender.new
t1.test_extend # Prints out "FooModule extended" followed by "done"
t1.new_method # Prints out "new method called"
t2.new_method rescue puts 'error' # Prints out "error" - extend only modifies the instance that calls it
t1 = Includer.new
t2 = Includer.new
t1.test_include # Prints out "FooModule included" followed by "done"
t1.new_method # Prints out "new method called"
t2.new_method # Prints out "new method called" - all past and future instances of Includer have been modified

Ruby File.each(e) block binds `self` to File not the instance?

Just teaching myself some Ruby, and wrote a little mixin to try out adding behaviors to File. Would like to write the shortest bit of code, specifically myFn:
module MyMixin
def fnSelf(filename)
File.new(filename).each { |line| puts "#{self.lineno}: #{line}" }
end
def fnLexcial(filename)
f = File.new(filename)
f.each { |line| puts "#{f.lineno}: #{line}" }
end
end
class File
extend MyMixin
end
Certainly fnLexical works, given the lexical scoping on f, but I would have expected (coming from the perspective of a Smalltalk programmer) that self in fnSelf would have been bound to the instance of File upon which .each was called, but instead it is bound to File (the class).
Other iterators on other classes work as I would expect, such as:
module MyArrayMixin
def fnSelf()
self.each { |e| puts "#{self} --- #{e}" }
end
end
class Array
include MyArrayMixin
end
[1,2,3,4,5].fnSelf
... which works as I would intuitively expect.
Now, I realize that I'm extending File but includeing into Array, but that implies that self is not bound dynamically at runtime inside of each block, which is again not what I would have expected. I must be missing some bit of intuitive understanding about Ruby.
The key difference between extend and include is that:
include adds behaviour to instances of the class.
extend adds behaviour the the class itself.
Remember that in ruby - like smalltalk - (almost) everything is an object; File is an instance of Class.
Hopefully this explains why self == File in your first method: because the object you are defining the method on literally is the class (and object): File.
Now then, how could one go about writing this method in a one-liner that works?
module MyMixin
def fnSelf(filename)
File.new(filename).each { |line| puts "#{self.lineno}: #{line}" }
end
end
Firstly, it's worth mentioning a couple of (very well conformed to) ruby styleguide points: snake_case method names, and 2-space tabs.
The easiest way to implement this would be to use Enumerable#each_with_index rather than falling back to the lineno method:
module MyMixin
def fn_self(filename)
File.new(filename).each_with_index { |line, idx| puts "#{idx + 1}: #{line}" }
end
end
However, as a purely academic exercise here, if you want to achieve this result by use of a closure then you can "tap into" the object like so:
module MyMixin
def fn_self(filename)
File.new(filename).tap { |file| file.each { |line| puts "#{file.lineno}: #{line}" } }
end
end

Executing code before all method calls [duplicate]

This question already has answers here:
How can I intercept method call in ruby?
(3 answers)
Closed 3 years ago.
I'm still new to Ruby in many ways so am a bit stuck trying to do this (via a Module? or base class?).
I want to do a "puts" for each method call executed on a class. Similar to a very simple form of a cucumber formatter, ie:
class MyClass
def method_a
puts 'doing some stuff'
end
end
So that the output looks like:
MyClass.new.method_a => 'methods_a', 'doing some stuff'
More importantly I want it to apply to any method on any class (dynamically, without littering my code). And I'd like to apply some formatting, ie so 'method_a' => 'Method A'. What's the best way to do this? AOP framework?
class MyClass
def method_a
puts "Doing A..."
end
def method_b
puts "Doing B..."
end
def self.call_before_all_methods
all_instance_methods = instance_methods - Class.instance_methods
all_instance_methods.each do |x|
class_eval <<-END
alias old_#{x} #{x}
def #{x}
print "\'#{x.to_s.split('_').each{|x| x.capitalize!}.join(' ')}\', "
old_#{x}
end
# remove_method old_#{x}.to_sym
END
end
end
private_class_method :call_before_all_methods
call_before_all_methods
end
a = MyClass.new
a.method_a
a.method_b
So the trick here is make an alias for each method first, and then re-define the method by "print formatted method name" + "original method which is the alias". It's also dynamically processed by Here document (<<-END).
But since re-definition of each method will call its original method, eventually it's not possible to remove_method or undef the alias. I think it's not a big deal to leave all those alias (old_method_a, old_method_b) there.
Here you are (apart from before_filter), even though I don't know why you want to do this:
class MyClass
def puts_all(&blk)
# get all instance_methods, also including default ones, so remove them by - Class.instance_methods
all_other_methods = self.class.instance_methods - Class.instance_methods
# remove the method name itself dynamically by saying __callee__
all_other_methods.delete(__callee__)
# formatting as per your need
all_other_methods.each do |x|
print "#{x.to_s.split('_').each{|x| x.capitalize!}.join(' ')}, "
send(x)
end
end
def method_a
puts "Doing A..."
end
def method_b
puts "Doing B..."
end
def another_fancy_method
puts "Doing fancy..."
end
end
MyClass.new.puts_all
#=> Method A, Doing A...
#=> Method B, Doing B...
#=> Another Fancy Method, Doing fancy...
So, to achieve this dynamically, you can simply use instance_methods and _callee_.

Ruby design pattern: How to make an extensible factory class?

Ok, suppose I have Ruby program to read version control log files and do something with the data. (I don't, but the situation is analogous, and I have fun with these analogies). Let's suppose right now I want to support Bazaar and Git. Let's suppose the program will be executed with some kind of argument indicating which version control software is being used.
Given this, I want to make a LogFileReaderFactory which given the name of a version control program will return an appropriate log file reader (subclassed from a generic) to read the log file and spit out a canonical internal representation. So, of course, I can make BazaarLogFileReader and GitLogFileReader and hard-code them into the program, but I want it to be set up in such a way that adding support for a new version control program is as simple as plopping a new class file in the directory with the Bazaar and Git readers.
So, right now you can call "do-something-with-the-log --software git" and "do-something-with-the-log --software bazaar" because there are log readers for those. What I want is for it to be possible to simply add a SVNLogFileReader class and file to the same directory and automatically be able to call "do-something-with-the-log --software svn" without ANY changes to the rest of the program. (The files can of course be named with a specific pattern and globbed in the require call.)
I know this can be done in Ruby... I just don't how I should do it... or if I should do it at all.
You don't need a LogFileReaderFactory; just teach your LogFileReader class how to instantiate its subclasses:
class LogFileReader
def self.create type
case type
when :git
GitLogFileReader.new
when :bzr
BzrLogFileReader.new
else
raise "Bad log file type: #{type}"
end
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
end
As you can see, the superclass can act as its own factory. Now, how about automatic registration? Well, why don't we just keep a hash of our registered subclasses, and register each one when we define them:
class LogFileReader
##subclasses = { }
def self.create type
c = ##subclasses[type]
if c
c.new
else
raise "Bad log file type: #{type}"
end
end
def self.register_reader name
##subclasses[name] = self
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
register_reader :git
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
register_reader :bzr
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class SvnLogFileReader < LogFileReader
def display
puts "Subersion reader, at your service."
end
register_reader :svn
end
LogFileReader.create(:svn).display
And there you have it. Just split that up into a few files, and require them appropriately.
You should read Peter Norvig's Design Patterns in Dynamic Languages if you're interested in this sort of thing. He demonstrates how many design patterns are actually working around restrictions or inadequacies in your programming language; and with a sufficiently powerful and flexible language, you don't really need a design pattern, you just implement what you want to do. He uses Dylan and Common Lisp for examples, but many of his points are relevant to Ruby as well.
You might also want to take a look at Why's Poignant Guide to Ruby, particularly chapters 5 and 6, though only if you can deal with surrealist technical writing.
edit: Riffing of off Jörg's answer now; I do like reducing repetition, and so not repeating the name of the version control system in both the class and the registration. Adding the following to my second example will allow you to write much simpler class definitions while still being pretty simple and easy to understand.
def log_file_reader name, superclass=LogFileReader, &block
Class.new(superclass, &block).register_reader(name)
end
log_file_reader :git do
def display
puts "I'm a git log file reader!"
end
end
log_file_reader :bzr do
def display
puts "A bzr log file reader..."
end
end
Of course, in production code, you may want to actually name those classes, by generating a constant definition based on the name passed in, for better error messages.
def log_file_reader name, superclass=LogFileReader, &block
c = Class.new(superclass, &block)
c.register_reader(name)
Object.const_set("#{name.to_s.capitalize}LogFileReader", c)
end
This is really just riffing off Brian Campbell's solution. If you like this, please upvote his answer, too: he did all the work.
#!/usr/bin/env ruby
class Object; def eigenclass; class << self; self end end end
module LogFileReader
class LogFileReaderNotFoundError < NameError; end
class << self
def create type
(self[type] ||= const_get("#{type.to_s.capitalize}LogFileReader")).new
rescue NameError => e
raise LogFileReaderNotFoundError, "Bad log file type: #{type}" if e.class == NameError && e.message =~ /[^: ]LogFileReader/
raise
end
def []=(type, klass)
#readers ||= {type => klass}
def []=(type, klass)
#readers[type] = klass
end
klass
end
def [](type)
#readers ||= {}
def [](type)
#readers[type]
end
nil
end
def included klass
self[klass.name[/[[:upper:]][[:lower:]]*/].downcase.to_sym] = klass if klass.is_a? Class
end
end
end
def LogFileReader type
Here, we create a global method (more like a procedure, actually) called LogFileReader, which is the same name as our module LogFileReader. This is legal in Ruby. The ambiguity is resolved like this: the module will always be preferred, except when it's obviously a method call, i.e. you either put parentheses at the end (Foo()) or pass an argument (Foo :bar).
This is a trick that is used in a few places in the stdlib, and also in Camping and other frameworks. Because things like include or extend aren't actually keywords, but ordinary methods that take ordinary parameters, you don't have to pass them an actual Module as an argument, you can also pass anything that evaluates to a Module. In fact, this even works for inheritance, it is perfectly legal to write class Foo < some_method_that_returns_a_class(:some, :params).
With this trick, you can make it look like you are inheriting from a generic class, even though Ruby doesn't have generics. It's used for example in the delegation library, where you do something like class MyFoo < SimpleDelegator(Foo), and what happens, is that the SimpleDelegator method dynamically creates and returns an anonymous subclass of the SimpleDelegator class, which delegates all method calls to an instance of the Foo class.
We use a similar trick here: we are going to dynamically create a Module, which, when it is mixed into a class, will automatically register that class with the LogFileReader registry.
LogFileReader.const_set type.to_s.capitalize, Module.new {
There's a lot going on in just this line. Let's start from the right: Module.new creates a new anonymous module. The block passed to it, becomes the body of the module – it's basically the same as using the module keyword.
Now, on to const_set. It's a method for setting a constant. So, it's the same as saying FOO = :bar, except that we can pass in the name of the constant as a parameter, instead of having to know it in advance. Since we are calling the method on the LogFileReader module, the constant will be defined inside that namespace, IOW it will be named LogFileReader::Something.
So, what is the name of the constant? Well, it's the type argument passed into the method, capitalized. So, when I pass in :cvs, the resulting constant will be LogFileParser::Cvs.
And what do we set the constant to? To our newly created anonymous module, which is now no longer anonymous!
All of this is really just a longwinded way of saying module LogFileReader::Cvs, except that we didn't know the "Cvs" part in advance, and thus couldn't have written it that way.
eigenclass.send :define_method, :included do |klass|
This is the body of our module. Here, we use define_method to dynamically define a method called included. And we don't actually define the method on the module itself, but on the module's eigenclass (via a small helper method that we defined above), which means that the method will not become an instance method, but rather a "static" method (in Java/.NET terms).
included is actually a special hook method, that gets called by the Ruby runtime, everytime a module gets included into a class, and the class gets passed in as an argument. So, our newly created module now has a hook method that will inform it whenever it gets included somewhere.
LogFileReader[type] = klass
And this is what our hook method does: it registers the class that gets passed into the hook method into the LogFileReader registry. And the key that it registers it under, is the type argument from the LogFileReader method way above, which, thanks to the magic of closures, is actually accessible inside the included method.
end
include LogFileReader
And last but not least, we include the LogFileReader module in the anonymous module. [Note: I forgot this line in the original example.]
}
end
class GitLogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrFrobnicator
include LogFileReader
def display
puts "A bzr log file reader..."
end
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class NameThatDoesntFitThePattern
include LogFileReader(:darcs)
def display
puts "Darcs reader, lazily evaluating your pure functions."
end
end
LogFileReader.create(:darcs).display
puts 'Here you can see, how the LogFileReader::Darcs module ended up in the inheritance chain:'
p LogFileReader.create(:darcs).class.ancestors
puts 'Here you can see, how all the lookups ended up getting cached in the registry:'
p LogFileReader.send :instance_variable_get, :#readers
puts 'And this is what happens, when you try instantiating a non-existent reader:'
LogFileReader.create(:gobbledigook)
This new expanded version allows three different ways of defining LogFileReaders:
All classes whose name matches the pattern <Name>LogFileReader will automatically be found and registered as a LogFileReader for :name (see: GitLogFileReader),
All classes that mix in the LogFileReader module and whose name matches the pattern <Name>Whatever will be registered for the :name handler (see: BzrFrobnicator) and
All classes that mix in the LogFileReader(:name) module, will be registered for the :name handler, regardless of their name (see: NameThatDoesntFitThePattern).
Please note that this is just a very contrived demonstration. It is, for example, definitely not thread-safe. It might also leak memory. Use with caution!
One more minor suggestion for Brian Cambell's answer -
In you can actually auto-register the subclasses with an inherited callback. I.e.
class LogFileReader
cattr_accessor :subclasses; self.subclasses = {}
def self.inherited(klass)
# turns SvnLogFileReader in to :svn
key = klass.to_s.gsub(Regexp.new(Regexp.new(self.to_s)),'').underscore.to_sym
# self in this context is always LogFileReader
self.subclasses[key] = klass
end
def self.create(type)
return self.subclasses[type.to_sym].new if self.subclasses[type.to_sym]
raise "No such type #{type}"
end
end
Now we have
class SvnLogFileReader < LogFileReader
def display
# do stuff here
end
end
With no need to register it
This should work too, without the need for registering class names
class LogFileReader
def self.create(name)
classified_name = name.to_s.split('_').collect!{ |w| w.capitalize }.join
Object.const_get(classified_name).new
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end
and now
LogFileReader.create(:git_log_file_reader).display
This is how I would make an extensible factory class.
module Factory
class Error < RuntimeError
end
class Base
##registry = {}
class << self
def inherited(klass)
type = klass.name.downcase.to_sym
##registry[type] = klass
end
def create(type, *args, **kwargs)
klass = ##registry[type]
return klass.new(*args, **kwargs) if klass
raise Factory::Error.new "#{type} is unknown"
end
end
end
end
class Animal < Factory::Base
attr_accessor :name
def initialize(name)
#name = name
end
def walk?
raise NotImplementedError
end
end
class Cat < Animal
def walk?; true; end
end
class Fish < Animal
def walk?; false; end
end
class Salmon < Fish
end
duck = Animal.create(:cat, "Garfield")
salmon = Animal.create(:salmon, "Alfredo")
pixou = Animal.create(:duck, "Pixou") # duck is unknown (Factory::Error)

Resources