What is the "other" object and how does it work? - ruby

I have seen other used often in class comparisons, such as
def ==(other)
...
end
or
def eql?(other)
self == other
end
but I still have found no explanation of what it actually is. What's going on here?
And perhaps this is for another question, but what does starting a method with == imply?

In ruby, operators are in fact method calls. If you have two variables a and b and want to check their equality, you generally write a == b, but you could write a.==(b). The last syntax shows what happens during an equality check : ruby calls a's method == and passes it b as an argument.
You can implement custom equality check in your classes by defining the == and/or the eql? methods. In your example, other is simply the name of the argument it receives.
class Person
attr_accessor :name
def initialize name
#name = name
end
end
a = Person.new("John")
b = Person.new("John")
a == b # --> false
class Person
def == other
name == other.name
end
end
a == b # --> true
For your second question, the only methods starting with == you're allowed to implement are == and ===. Check here for the full list of restrictions on method names in ruby: What are the restrictions for method names in Ruby?

other is a parameter to this method, the object, that is being passed.
For example:
class A
def ==(other)
:lala == other
end
end
obj = A.new
obj.==(:foo) # full syntax, just like any other method
# but there's also a shorthand for operators:
obj == :foo # => false
obj == :lala # => true

other is the parameter for == and it represents the object you are comparing with.
Example
x == y
The == method (yes, its just a method!), on your x object, gets called with y as a parameter.
Welcome to Ruby, you'll love it after a while :)

other, in this case, is the object you're comparing to, so this:
class SomeClass
def ==(other)
self == other
end
end
means:
SomeClass.new == nil # here "other" is nil, this returns false
Basically def ==(other) is the implementation of the == operator, for cases where you do something specific in your class regarding comparison using ==.

For numbers you can get the sum of 2 numbers using:
a= x + y
in Ruby everything is object, Right! so x and y are objects and for Number(Object) x it has a defined method + which accept 1 paramter which is y
same for what you are trying to understand, what if you have 2 classes and you want to check if they are equal or not and its your defined class and you want to specify how the are equal for example if their name attribute is equal then you can say:
class Student
def ==(other)
self.name == other.name
end
end
fi = Student.new(name: 'Mark')
sec = Student.new(name: 'Hany')
if (fi == sec)
# do something here

Related

Keep if on object

I'm looking for a clean way to evaluate an object and return object if condition is verified, nil otherwise so that I can use a default value instead. Something like:
result = object.verify?{ |object| object.test? } || default_value
I can see several ways to implement this, but I am hoping that there is a built-in way to do that. Ex:
Going to the Array level
def verify?(&block)
Array(self).filter(block).first
end
Using instance_eval
def verify?(&block)
self.instance_eval{ |object| yield(object) ? object : nil}
end
EDIT
Here's my actual example (though the question isn't tighted to it):
class User < ActiveRecord::Base
def currency
self.billing_information.try(:address).try(:country).try(:currency_code).instance_eval{ |currency| Finance::CURRENCIES.include?(currency) ? currency : nil} || 'EUR'
end
end
I know this is ugly, but I do like the logic of it: if the object I'm looking for exists, go get the next one. The first conditions are existence (with try), then inclusion.
Regarding your actual problem - Rails provides presence_in:
Returns the receiver if it's included in the argument otherwise returns nil.
'EUR'.presence_in %w(EUR USD) #=> "EUR"
'JPY'.presence_in %w(EUR USD) #=> nil
I would probably separate the actual currency from the verified currency (so you can still access the former one):
class User < ActiveRecord::Base
def currency
billing_information.try(:address).try(:country).try(:currency_code)
end
def verified_currency
Finance::CURRENCIES.include?(currency) ? currency : 'EUR'
end
end
And move the logic for checking a currency and providing a default one into Finance:
class User < ActiveRecord::Base
def currency
billing_information.try(:address).try(:country).try(:currency_code)
end
def verified_currency
Finance.verified_currency(currency)
end
end
module Finance
CURRENCIES = %w(EUR USD)
DEFAULT_CURRENCY = 'EUR'
def self.verified_currency(currency)
CURRENCIES.include?(currency) ? currency : DEFAULT_CURRENCY
end
end
This also avoids having to evaluate User#currency twice.
The try-chain can be replaced by delegate:
class User < ActiveRecord::Base
delegate :currency, to: billing_information, allow_nil: true
def verified_currency
Finance.verified_currency(currency)
end
end
class BillingInformation < ActiveRecord::Base
delegate :currency, to: address, allow_nil: true
end
class Address < ActiveRecord::Base
delegate :currency, to: country, allow_nil: true
end
If you want the receiver when the condition is satisfied, then assuming that test? returns a truthy value when the condition is met and a falsy value otherwise:
result = object.tap{|object| break unless object.test?} || default_value
Or, following Stefan's suggestion:
result = object.tap{|object| break default_value unless object.test?}
I think #Stefan's suggestion is best, but if you insist on being obtuse, you could write:
(object.test? && object) || default
If you think you need Object#try version that can handle default values, you can monkey patch it.
require "rails"
class Object
alias_method :old_try, :try
def try(params, default = nil, &block)
old_try(params, &block) || default
end
end
object = {}
default = "I'm default"
p result = object.try(:test, default)
#=> I'm default
I think you're looking for Enumerable#detect:
detect(ifnone = nil) { |obj| block } → obj or nil
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
(1..10).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..100).find { |i| i % 5 == 0 and i % 7 == 0 } #=> 35
So you could do result = object.detect { |object| object.test? } || default_value as you've written, or the more idiomatic result = object.detect(default_value) { |object| object.test? }.

Extending BaseClass method (Polymorphism)

I played around with inheritances and tried to extend a method from the base class from the subclass. Polymorphism. Basically, I tried to extend the base method (status) with an additional option.
However, for some reason it doesn't work as expected. I thought B.new.status(1) would return 2 instead of nil.
If I place super after "y if opt == 2" then the results are vice verse.
Why is that and how can I solve this problem?
Many thanks in advance!
class A
attr_reader :x
def initialize
#x = 2
end
def status(opt)
x if opt == 1
end
end
class B < A
attr_reader :y
def initialize
super
#y = 10
end
def status(opt)
super
y if opt == 2
end
end
B.new.status(1)
=> nil
B.new.status(2)
=> 2
That's because the return value from A's status is not used in B's status. Yes, in A's status, 'x' will be returned, but that isn't used in B's status.
The result of the last statement in a method is automagically returned, but super isn't the last statement in B's status.
If the codition of an if statement is not satisfied, it returns nil. So you should do something like this:
if opt == 2
y
else
super
end

Difference between self.element = 'this' and self.send("element=", 'this')

I am trying to understand why these two things return different values.
Value is a string, and field is a text_field.
def populate_text(field, value)
self.send "user_name=", value
end
# => nil
def populate_text(value)
self.user_name = value
end
# => "value"
Why do self and send have different return values?
This class includes PageObject if that helps.
Ruby's syntax sugar for calling methods whose name ends with = always returns the righthand value, regardless of the return value of the method.
This is not the case when you use send to invoke the method. For example:
class Foo
def bar=(n)
:ohno
end
end
f = Foo.new
x = (f.bar = 42)
y = f.send("bar=", 42)
p [x,y]
#=> [42, :ohno]
So, you would get two different values if your user_name= method has a return value that is not the argument to the method.
Self.Send allows you to dynamically choose your objects, regardless of type.
This lets you data drive your test with very simple code.

Ruby define_method

I have the following test which I must pass:
def test_can_find_by_arbitrary_fields
assert #library.respond_to? :find_by_artist
assert !#library.respond_to?(:find_by_bitrate)
#library.add_song({ :artist => 'Green Day',
:name => 'American Idiot',
:bitrate => 192 })
assert #library.respond_to?(:find_by_bitrate)
end
and I am not sure how I can do it.
I tried doing:
def respond_to?(method)
if self.public_methods.include? method
true
elsif (method == :find_by_bitrate)
define_method :find_by_bitrate, ->(default = nrb) { #songs.select |a| a[:bitrate] == nrb }
false
else
false
end
but it says "define_method is undefined". Are there any ways I can define the find_by_bitrate method?
You may define methods the first time they're called in method_missing.
Whether or not you should is open to some debate, but it's a better option than respond_to?.
class Foo
def method_missing(sym)
puts "Method missing; defining."
self.class.send(:define_method, sym) do
puts "Called #{sym}."
end
end
end
Sanity check:
f = Foo.new
=> #<Foo:0x007fa6aa09d3c0>
f.wat
=> Method wat missing; defining.
f.wat
=> Called wat.
f2 = Foo.new
=> Called wat.
I don't think you should be redefining respond_to? method. The point of the test is (probably) that the #library object should have a find_by_artist method defined and no find_by_bitrate until you add a song with a bitrate. I.e. the add_song method should define method find_by_bitrate when it sees a song with a bitrate (?).
Also, define_method is a private method of Class. Above, you're trying to call it from an instance method. See "Ruby: define_method vs. def", there's more on this stuff.
There's a lot of info missing to properly answer this. The test implies that find_by_artist is always defined even when #library is empty, but that there are dynamic methods available on other attributes (eg: bitrate) that are valid only when library contains a record with such a method.
One should not redefine respond_to? in any case. There is an explicit hook method for answering respond_to? for dynamic methods: Object#respond_to_missing?.
So a simple way to make your test pass is to be sure the #library object has a concrete method #find_by_artist and a respond to hook that checks whether any of it's elements a have the requested attribute. If I assume #library is a collection object Library which keeps an enumeration of songs in #songs
class Library
def find_by_artist artist
#songs.select { |song| song['artist'] == artist }
end
def method_missing meth, arg
m = /^find_by_(.+)$/.match meth.to_s
return super unless attr = m && m[1]
#songs.select { |song| song[attr] == arg }
end
def respond_to_missing? meth, include_private
m = /^find_by_(.+)$/.match meth.to_s
return super unless attr = m && m[1]
#songs.any? { |song| song.has_key? attr }
end
end
This has a performance problem in that respond_to? now incurs a search of all the songs. One could optimize by keeping a set of the union of all attributes contained in #songs and updating it in methods which add/update/delete elements in the collection.

Uniqueness of Ruby Instances

If I create two String instances with the same content separately they are identical. This is not the case with custom classes by default (see example below).
If I have my own class (Test below) and I have a variable (#v below) which is unique, ie. two Test instances with the same #v should be treated as identical, then how would I go about telling Ruby this is the case?
Consider this example:
class Test
def initialize(v)
#v = v
end
end
a = {Test.new('a') => 1, Test.new('b') => 2}
a.delete(Test.new('a'))
p a
# # Desired output:
# => {#<Test:0x100124ef8 #v="b">=>2}
You need to define an == method that defines what equality means for your class. In this case, you would want:
class Test
def initialize(v)
#v = v
end
def ==(other)
#v == other.instance_variable_get(:#v)
end
end
You are using objects of class Test as keys for the hash. In order for that to function properly (and consequently a.delete), you need to define two methods inside Test: Test#hash and Test#eql?
From: http://ruby-doc.org/core/classes/Hash.html
Hash uses key.eql? to test keys for
equality. If you need to use instances
of your own classes as keys in a Hash,
it is recommended that you define both
the eql? and hash methods. The hash
method must have the property that
a.eql?(b) implies a.hash == b.hash.
I found a different way to tackle this, by keeping track of all the instances of Test internally I can return the premade instance rather than making a new one and telling ruby they're equivalent:
class Test
def self.new(v)
begin
return ##instances[v] if ##instances[v]
rescue
end
new_test = self.allocate
new_test.instance_variable_set(:#v,v)
(##instances ||= {})[v] = new_test
end
end
Now Test.new('a') == Test.new('a') and Test.new('a') === Test.new('a') :)
Most of the time, an object you need to be comparable and/or hashable is composed of member variables which are either primitives (integers, strings, etc) or are themselves comparable/hashable. In those cases, this module:
module Hashable
include Comparable
def ==(other)
other.is_a?(self.class) && other.send(:parts) == parts
end
alias_method :eql?, :==
def hash
parts.hash
end
end
can simply be included in your class to take care of all of the busywork. All you have to do is define a "parts" method that returns all of the values that comprise the object's state:
class Foo
include Hashable
def initialize(a, b)
#a = a
#b = b
end
private
def parts
[#a, #b]
end
end
Objects built this way are comparable (they have <, <=, ==, >=, >, != and equ?) and they can be hash keys.

Resources