Can I call a custom method on an object with this notation? - ruby

This is an example of what I am after:
def already_taken?
# Magic goes here...
end
"Charlotte".already_taken?
Would it be possible to construct a method in a way where I can call it directly on a String object, without having to modify the String class itself?

You could patch the String class with a custom module:
module MyStringPatch
def already_taken?
'yes'
end
end
String.include MyStringPatch
"Charlotte".already_taken?

If you want to add methods to any class (String in this case), without monkey-patching it, you should consider using Refinements.
module StringRefinements
refine String do
def already_taken?
puts "yes!"
end
end
end
# in another file...
using StringRefinements
"Charlotte".already_taken?
The already_taken? method will only be available in a scope that calls using StringRefinements and nowhere else.

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

How to create a global string method

I want to create a global String method used as: "string".convert_to_date that I can use it in the same way as "abc".length or "abc".upcase.
How can I define convert_to_date method?
As an alternative to patching you can also define patches via refinements. This will make the patch only available in a certain scope. This isn't necessarily an issue with String.convert_to_date, but in a large scale project it's often recommended to avoid outright monkeypatching, to avoid conflicts with gems' code.
A refinement is defined and used like so:
module StringRefinement
refine String do
def convert_to_date
self + " world"
end
end
end
class SomeClass
using StringRefinement
"hello".convert_to_date # => "hello world"
end
"hello".convert_to_date # => NoMethodError
you can open up any class in ruby to add methods to it, for your case you can do
class String
def convert_to_date
# do something with the string, self will contain the value of the string
end
end
This will make that method available to any string object so make sure you know what you are doing and there are no side effects.
This is called monkey patching, I'm not sure if this is the best way for your use case without more context
If you're just trying to convert string date to date or time object, there already are methods like Time.parse or DateTime.parse
Thanks #Subash and #max pleaner, your answers helped me found a solution. And here is my solution:
in config/initializers/StringRefinementDate.rb:
module StringRefinementDate
def convert_to_date
self + " world"
end
end
String.include StringRefinementDate
In models, controllers, and views just use:
"hello".convert_to_date # => "hello world"

Wrap block to use refinement

Given the following refinement:
module StringRefinement
refine String do
def bar
length
end
end
end
I want to implement a module to execute blocks using my refinement:
module Demo
using StringRefinement
def self.wrap(*args, &block)
instance_eval(&block)
end
end
And now I should be able to use it like this:
Demo.wrap { puts "some text".bar }
Which doesn't work :-(
I've been playing with the block binding, yield, context, singleton_class... but I still cannot get this to work. How can I do it?
You need to move your using StringRefinement statement outside of your module.
Check the following paragraph from the docs:
You may only activate refinements at top-level, not inside any class, module or method scope.
http://ruby-doc.org/core-2.1.1/doc/syntax/refinements_rdoc.html#label-Scope

How to reopen a class using just its instance variable in Ruby?

I want to create a function that messes around with the classes passed to it. What would be the most idiomatic way to reopen those classes to add functionality? Here's what I mean:
def class_messer(target_object)
#would like to reopen class here with something like:
class target_object.class
#add methods
end
end
Obviously that syntax doesn't work. I could get the target_object's class and eval some strings, but that feels gross. Is there a more idiomatic way to do this?
I think you're looking for class_eval. If you want to reopen a class and you do not have the constant as is, but a reference, you can call class_eval on it and pass a block (or even a string) of code to be evaluated in that classes context.
def class_messer(target_object)
# assuming that target_object is an instance of desired class
target_object.class.class_eval do
#add methods
end
end
target_object.class.class_exec do
# add methods
end
Maybe it's not correct to change class, for example, if you had an instance of Array class and changed its class, then this change could impact on other instances of Array class. So instead use singleton class of instance and the definition of method will be:
target_object.send(:define_method, :new_method) do
#...
end
or
class << target_object
def new_method
#...
end
end
You can also do this:
class << target_object.class
end

ruby method_alias in inherited class

I'm deeping into ruby metaprogramming and have next question.
Example:
module ExampleAliaser
def do_example_alias(prefix=:origin)
class_eval <<-EOS
class << self
alias_method :#{prefix}_example, :example
def example
puts "in aliase will call :#{prefix}_example"
#{prefix}_example
end
end
EOS
end
end
class Example1
def self.example
puts "Example"
end
end
Example1.extend(ExampleAliaser)
class Example1
do_example_alias(:origin)
end
class Example2 < Example1
do_example_alias(:origin)
end
Example1.example
in aliase will call :origin_example
Example
=> nil
Example2.example
in aliase will call :origin_example
in aliase will call :origin_example
in aliase will call :origin_example
SystemStackError: stack level too deep
from /Users/igorfedoronchuk/.rvm/rubies/ruby-1.9.2-p180/lib/ruby/1.9.1/irb/workspace.rb:80
Maybe IRB bug!!
So when mixin used 2 times it causes error.
What is the best way to fix such things? How to determine that mixing exists and remove it before new mixing
Follow the definition of methods to see why this is happening.
You first define Example1::example in the class definition of Example1. It writes a string to the console.
Then you extend ExampleAliaser. When you call Example1::do_example_alias, you then alias the method example to origin_example and redefine the method example to write a different string to the console and call origin_example.
Then you define the class Example2 to inherit from Example1, which now has two methods defined on it: origin_example and example. When you call Example2::do_example_alias, you alias the method example to origin_example. But remember that example was already redefined to call origin_example. So effectively, Example2::example will call itself until you run out of room on the stack.
If you want to avoid double-aliasing, you could include some kind of guard in do_example_alias:
def do_example_alias(prefix = :origin)
unless methods.include?("#{prefix}_example")
# do the aliasing
end
end
You can also undef :method_name in subclasses to remove methods that you no longer want defined.

Resources