Ruby - Overriding string methods in a separate file - ruby

I have a file, 'example.rb', where I want to use custom methods on String by overriding the string class.
I know this can be done as
puts "abcd".twice
class String
def twice
self*2
end
end
But I want to have the custom methods definition in another file, say 'my_String.rb'. How do I do this?

Do your monkey patching in "my_string.rb" (or whatever) and have the file required in your script.
# my_string.rb
class String
def twice
self*2
end
end
# my_super_script.rb
require 'my_string.rb' # Assuming both these files are in the same folder
puts "abcd".twice

You simply put the String class opening method in my_string.rb and in your code you do:
require 'my_string'

Related

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

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.

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"

Instantiate class dynamically using filename

my question is that if i have a class CSVWriter inside a file named csv_writer.rb, then can i instantiate this class using my filename dynamically.
I did tried using Object.const_get() method but it takes the name of the class as string as argument.
So is there anyway to do that in ruby
Thanks
You can use the __FILE__ constant to get the name of the file.
# foo.rb
puts __FILE__
Output
"foo.rb"
Knowing this, you could do something like
# csv_writer.rb
class CsvWriter
def initialize
puts "hello"
end
end
klass = Object.const_get(
File::basename(__FILE__, ".rb")
.split("_")
.map(&:capitalize)
.join("")
)
klass.new
Output
"hello"
PS there's no programmatic way to convert csv_writer to CSVWriter. How would ruby know to capitalize csv to CSV (all-caps) but writer to only Writer?

Ruby Mock a file with StringIO

I am trying to mock file read with the help of StringIO in Ruby.
The following is my test and next to that is my method in the main class.
def test_get_symbols_from_StringIO_file
s = StringIO.new("YHOO,141414")
assert_equal(["YHOO,141414"], s.readlines)
end
def get_symbols_from_file (file_name)
IO.readlines(file_name, ',')
end
I want to know if this is the way we mock the file read and also I would like to know if there is some other method to mock the method in the class rather than doing assert equal with contents.
Your method get_symbols_from_file is never called in the test. You're just testing that StringIO#readlines works, i.e.:
StringIO.new("YHOO,141414").readlines == ["YHOO,141414"] #=> true
If you want to use a StringIO instance as a placeholder for your file, you have to change your method to take a File instance rather than a file name:
def get_symbols_from_file(file)
file.readlines(',')
end
Both, File and StringIO instances respond to readlines, so the above implementation can handle both:
def test_get_symbols_from_file
s = StringIO.new("YHOO,141414")
assert_equal(["YHOO,141414"], get_symbols_from_file(s))
end
This test however fails: readlines includes the line separator, so it returns an array with two elements "YHOO," (note the comma) and "141414". You are expecting an array with one element "YHOO,141414".
Maybe you're looking for something like this:
def test_get_symbols_from_file
s = StringIO.new("YHOO,141414")
assert_equal(["YHOO", "141414"], get_symbols_from_file(s))
end
def get_symbols_from_file(file)
file.read.split(',')
end
If you really want to use IO::readlines you could create a Tempfile:
require 'tempfile'
def test_get_symbols_from_file
Tempfile.open("foo") { |f|
f.write "YHOO,141414"
f.close
assert_equal(["YHOO", "141414"], get_symbols_from_file(f.path))
}
end

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

Resources