Add method in same class or in String class - Best practices - ruby

I want define a new method to validate a string like this:
# lib/rips/variables/variable.rb
class Variable
# Check if value is a valid number (...,-1,0,1...)
def number? (value)
/\A[-]?\d+\z/ === value
end
end
And it invokes like this in the same class and derived classes:
# lib/rips/variables/inmediate.rb
class Inmediate < Variable
# value = "+42" -> return false
def valid_syntax? (value)
number?(value)
end
end
But other way to do this it's adding method number? to String class:
# lib/rips/utils/string.rb
class String
# Check if value is a valid number (...,-1,0,1...)
def number?
/\A[-]?\d+\z/ === self
end
end
And now it invokes like this:
# lib/rips/variables/inmediate.rb
require "lib/rips/utils/string"
class Inmediate < Variable
# value = "+42" -> return false
def valid_syntax? (value)
value.number?
end
end
So I have number?(value) if I declare method in same class or value.number? if I add method to String class. Isn't only method, I want to add more new methods too. I prefer second way but I don't know if the best practice in this case.
Which is the best practice?

The best way is use Refinements (by link described why and how to use it functionality). It is available since ruby 2.0.

There is a third option, where you get the convenience of calling your custom methods directly on the String object, but without adding it to the String class.
module StringExtensions
def number?
/\A[-]?\d+\z/ === self
end
end
string = "1"
string.number? # => NoMethodError: undefined method `number?' for "1":String
string.extend(StringExtensions)
string.number? # => true
"2".number? # => NoMethodError: undefined method `number?' for "1":String

Related

Correct implementation and use of == operator in ruby

In continuation of another question I also have the following question.
I have a class which has a very central instance variable called var. It is so central to the class that when I print an object of the class, it should just print the instance variable.
When I compare that same object with a string which matches the instance variable, I would like it to return true, but that doesn't always work in the implementation below:
class MyClass
attr_accessor :var
def initialize(v)
#var = v
end
def to_s
#var.to_s
end
def inspect
#var.inspect
end
def ==(other)
self.var == other
end
# ... methods to manipulate #var
end
str = "test"
obj = MyClass.new("test")
puts obj # prints the var string - great this is what i want
p obj # prints the var string - great this is what i want
puts obj==str # returns true - great this is what i want
puts str==obj # returns false - oops! Fails since i didn't overload the == operator in String class. What to do? I don't think i want to "monkey patch" the String class.
I understand that it's not my overwritten == operator which is used. It's the one in the String class. What do you suggest I do to get around this? Am I going down the wrong path?
I read through the question What's the difference between equal?, eql?, ===, and ==? without getting an answer to my question.
I understand that it's not my overwritten == operator which is used. It's the one in the String class. What do you suggest I do to get around this? Am I going down the wrong path?
Before trying anything else, have a look at the docs for String#==:
If object is not an instance of String but responds to to_str, then the two strings are compared using object.==.
In other words: you have to implement to_str and return the object's string value:
class MyClass
# ...
def to_str
#var.to_s
end
end
You can also return to_s or use alias to_str to_s.
With any of the above, you'd get:
"test" == MyClass.new("test")
#=> true
Note that to_str should only be implemented by objects that are string-like.

How to define class name method like Integer() and when should I use it?

Some classes like Integer able to create a instance by
Integer(1) #=> 1
It seems the class name works as method name.
How can I create a method like this and when should I use it instead of define a initialize method?
Integer is a Kernel method. In fact, it is defined as Kernel.Integer.
You can simply create a new method that acts as initializer for your custom class:
class Foo
def initialize(arg)
#arg = arg
end
end
def Foo(arg)
Foo.new(arg)
end
Foo("hello")
# => #<Foo:0x007fa7140a0e20 #arg="hello">
However, you should avoid to pollute the main namespace with such methods. Integer (and a few others) exists because the Integer class has no initializer.
Integer.new(1)
# => NoMethodError: undefined method `new' for Integer:Class
Integer can be considered a factory method: it attempts to convert the input into an Integer, and returns the most appropriate concrete class:
Integer(1).class
# => Fixnum
Integer(1000 ** 1000).class
# => Bignum
Unless you have a real reason to create a similar initializer, I'd just avoid it. You can easily create static methods attached to your class that converts the input into an instance.
It’s not a class, it’s a method (Kernel#Integer) that begins with a capital letter.
def Foo(x = 1)
"bar to the #{x}!"
end
Foo(10) #=> "bar to the 10!"
It can co-exist with a constant of the same name as well:
module Foo; end
Foo.new #=> #<Foo:0x007ffcdb5151f0>
Foo() #=> "bar to the 1!"
Generally, though, it’s thought that creating methods that begin with a capital letter is a bad idea and confusing.

Ruby : how to create a .downcase! style method?

I'm learning Ruby, and I was wondering :
How to create a .downcase! style method ?
I could obviously make a method callable like downcase("CAPS LOCK")
But I want string.downcase.
Obviously that's an example, I know the downcase method exists.
How do you do that ?
Thanks !
When you define a method, it is available and called in the context of an object. That means every method call has an implicit scope. downcase really means self.downcase.
For example if you define a method directly, it's added as a private method on the main context for the script.
self
# => main
def downcase
end
# private_methods means self.private_methods
private_methods.include?(:downcase)
# => true
If you want an object instance to call a method in it's context, define the method on the class.
class String
def my_downcase
end
end
"string".methods.include?(:my_downcase)
# => true
"string".my_downcase
Adding methods to built-in types is called monkey-patching and it's sort of frowned upon. Better to subclass the object to add functionality. The String constructor takes a string which is great for subclassing, and String#replace works for modifying the string in-place.
class CoolString < String
def initialize(str)
super(str)
end
def downcase!
replace(downcase)
end
end
str = CoolString.new("STRING")
str.downcase!
str
# => "string"
You can extend core classes easily:
class String
def make_it_downcase!
replace downcase # Calls downcase on the current value and replaces it with the result.
end
end
See it in action:
$ irb
irb(main):001:0> class String
irb(main):002:1> def make_it_downcase!
irb(main):003:2> replace downcase
irb(main):004:2> end
irb(main):005:1> end
=> :make_it_downcase!
irb(main):006:0> test = "TEST"
=> "TEST"
irb(main):007:0> test.make_it_downcase!
=> "test"
irb(main):008:0> test
=> "test"
You can extend the string class like so:
class String
def downcase
#implementation
end
end

Ruby: Defining methods dynamically based on variable values

I want to achieve something like below. Defining method names based on array arguments and Call them.
arr = ['alpha', 'beta', 'gamma']
arr.each { |role|
# If the method already exists don't define new
if ! method_exist? "init_#{role}"
define "init_#{role}"
p "I am method init_#{role}"
end
}
init_beta
init_gamma
Edit:
If such a method already exists, don't define a new method.
Do as below :
arr = ['alpha', 'beta', 'gamma']
arr.each do |role|
# this code is defining all the methods at the top level. Thus call to
# `method_defined?` will check if any method named as the string argument
# is already defined in the top level already. If not, then define the method.
unless self.class.private_method_defined?( "init_#{role}" )
# this will define methods, if not exist as an instance methods on
# the top level.
self.class.send(:define_method, "init_#{role}" ) do
p "I am method init_#{role}"
end
end
end
init_beta # => "I am method init_beta"
init_gamma # => "I am method init_gamma"
Look at the documentation of private_method_defined and define_method.
Note : I have used private_method_defined?, as on top level, all instance method you will be defining ( using def or define_method in the default accessibility level ), become as private instance methods of Object. Now as per your need you can also check protected_method_defined? and public_method_defined? accordingly.

Name of the current object/class instance

I need to load a YAML file (I'm experimenting with SettingsLogic) and I'd like the instance to load the YAML with the same name as it. Briefly:
class MySettings < SettingsLogic
source "whatever_the_instance_is_called.yml"
# Do some other stuff here
end
basic_config = MySettings.new # loads & parses basic_config.yml
advanced_cfg = MySettings.new # loads & parses advanced_cfg.yml
...and so on...
The reason for this I don't yet know what configuration files I'll have to load, and typing:
my_config = MySettings.new("my_config.yml")
or
my_config = MySettings.new(:MyConfig)
just seems to be repeating myself.
I took a look around both Google and Stackoverflow, and the closest I came to an answer is either "Get Instance Name" or a discussion about how meaningless an instance name is! (I'm probably getting the query wrong, however.)
I have tried instance#class, and instance#name; I also tried instance#_id2ref(self).
What am I missing?!
Thanks in advance!
O.K., so with local variable assignment, there are snags, such as that assignment might occur slightly later than local variable symbol addition to the local variable list. But here is my module ConstMagicErsatz that I used to implement something similar to out-of-the box Ruby constant magic:
a = Class.new
a.name #=> nil - anonymous
ABC = a # constant magic at work
a.name #=> "ABC"
The advantage here is that you don't have to write ABC = Class.new( name: "ABC" ), name gets assigned 'magically'. This also works with Struct class:
Koko = Struct.new
Koko.name #=> "Koko"
but with no other classes. So here goes my ConstMagicErsatz that allows you to do
class MySettings < SettingsLogic
include ConstMagicErsatz
end
ABC = MySettings.new
ABC.name #=> "ABC"
As well as
a = MySettings.new name: "ABC"
a.name #=> "ABC"
Here it goes:
module ConstMagicErsatz
def self.included receiver
receiver.class_variable_set :##instances, Hash.new
receiver.class_variable_set :##nameless_instances, Array.new
receiver.extend ConstMagicClassMethods
end
# The receiver class will obtain #name pseudo getter method.
def name
self.class.const_magic
name_string = self.class.instances[ self ].to_s
name_string.nil? ? nil : name_string.demodulize
end
# The receiver class will obtain #name setter method
def name= ɴ
self.class.const_magic
self.class.instances[ self ] = ɴ.to_s
end
module ConstMagicClassMethods
# #new method will consume either:
# 1. any parameter named :name or :ɴ from among the named parameters,
# or,
# 2. the first parameter from among the ordered parameters,
# and invoke #new of the receiver class with the remaining arguments.
def new( *args, &block )
oo = args.extract_options!
# consume :name named argument if it was supplied
ɴς = if oo[:name] then oo.delete( :name ).to_s
elsif oo[:ɴ] then oo.delete( :ɴ ).to_s
else nil end
# but do not consume the first ordered argument
# and call #new method of the receiver class with the remaining args:
instance = super *args, oo, &block
# having obtained the instance, attach the name to it
instances.merge!( instance => ɴς )
return instance
end
# The method will search the namespace for constants to which the objects
# of the receiver class, that are so far nameless, are assigned, and name
# them by the first such constant found. The method returns the number of
# remaining nameless instances.
def const_magic
self.nameless_instances =
class_variable_get( :##instances ).select{ |key, val| val.null? }.keys
return 0 if nameless_instances.size == 0
catch :no_nameless_instances do search_namespace_and_subspaces Object end
return nameless_instances.size
end # def const_magic
# ##instances getter and setter for the target class
def instances; const_magic; class_variable_get :##instances end
def instances= val; class_variable_set :##instances, val end
# ##nameless_instances getter for the target class
def nameless_instances; class_variable_get :##nameless_instances end
def nameless_instances= val; class_variable_set :##nameless_instances, val end
private
# Checks all the constants in some module's namespace, recursivy
def search_namespace_and_subspaces( ɱodule, occupied = [] )
occupied << ɱodule.object_id # mark the module "occupied"
# Get all the constants of ɱodule namespace (in reverse - more effic.)
const_symbols = ɱodule.constants( false ).reverse
# check contents of these constant for wanted objects
const_symbols.each do |sym|
# puts "#{ɱodule}::#{sym}" # DEBUG
# get the constant contents
obj = ɱodule.const_get( sym ) rescue nil
# is it a wanted object?
if nameless_instances.map( &:object_id ).include? obj.object_id then
class_variable_get( :##instances )[ obj ] = ɱodule.name + "::#{sym}"
nameless_instances.delete obj
# and stop working in case there are no more unnamed instances
throw :no_nameless_instances if nameless_instances.empty?
end
end
# and recursively descend into the subspaces
const_symbols.each do |sym|
obj = ɱodule.const_get sym rescue nil # get the const value
search_namespace_and_subspaces( obj, occupied ) unless
occupied.include? obj.object_id if obj.kind_of? Module
end
end
end # module ConstMagicClassMethods
end # module ConstMagicErsatz
The above code implements automatic searching of whole Ruby namespace with the aim of finding which constant refers to the given instance, whenever #name method is called.
The only constraint using constants gives you, is that you have to capitalize it. Of course, what you want would be modifying the metaclass of the object after it is already born and assigned to a constant. Since, again, there is no hook, you have to finde the occasion to do this, such as when the new object is first used for its purpose. So, having
ABC = MySettings.new
and then, when the first use of your MySettings instance occurs, before doing anything else, to patch its metaclass:
class MySettings
def do_something_useful
# before doing it
instance_name = self.name
singleton_class.class_exec { source "#{instance_name}.yml" }
end
# do other useful things
end
Shouldn't you be able to do either
File.open(File.join(File.expand_path(File.dir_name(__FILE__)), foo.class), "r")
or
require foo.class
The first one need not be that complicated necessarily. But if I'm understanding you correctly, you can just use foo.class directly in a require or file load statement.
Adjust as necessary for YAML loading, but #class returns a plain old string.
Well if you have tons of variables to instantiate, I'd personally just create a Hash to hold them, it's cleaner this way. Now to instantiate all of this, you could do a loop other all your yaml files :
my_settings = {}
[:basic_config, :advanced_cfg, :some_yaml, :some_yaml2].each do |yaml_to_parse|
my_settings[yaml_to_parse] = MySettings.new(yaml_to_parse)
end
Make sure your initialize method in MySettings deals with the symbol you give it!
Then get your variables like this :
my_settings[:advanced_cfg]
Unfortunately, Ruby has no hooks for variable assignment, but this can be worked around. The strategy outline is as follows: First, you will need to get your MySettings.new method to eval code in the caller's binding. Then, you will find the list of local variable symbols in the caller's binding by calling local_variables method there. Afterwards, you will iterate over them to find which one refers to the instance returned by super call in your custom MySettings.new method. And you will pass its symbol to source method call.

Resources