case insensitive equals method based on one attribute - ruby

Orginal Question
This is a really horrible method, which checks for equality on base of the code but case agnostic
def ==(another_country)
(code.nil? ? nil : code.downcase) == (another_country.code.nil? ? nil : another_country.code.downcase) unless another_country.nil?
end
Can you point my in the right direction how to write this more elegant w/o reliying on ugly if else structures?
This is the solution I ended up using (+RSpecs)
# Country model
class Country < ActiveRecord::Base
attr_accessible :code
def ==(another_country)
code.to_s.downcase == another_country.code.to_s.downcase rescue false
end
end
Extensive Tests:
# RSpec
describe Country do
describe 'equality based solely on Country.code' do
before do
#country_code_de = FactoryGirl.build(:country, :code => 'de')
end
it 'should be equal if Country.code is equal' do
other_country_code_de = FactoryGirl.build(:country, :code => 'de')
#country_code_de.should == other_country_code_de
end
it 'should be not equal if Country.code is not equal' do
country_code_usa = FactoryGirl.build(:country, :code => 'usa')
#country_code_de.should_not == country_code_usa
end
it 'should be case insensitive' do
country_code_de_uppercase = FactoryGirl.build(:country, :code => 'DE')
#country_code_de.should == country_code_de_uppercase
end
it 'should not rely on id for equality' do
#country_code_de.id = 0
country_code_usa = FactoryGirl.build(:country, :code => 'usa', :id => 0)
#country_code_de.should_not == country_code_usa
end
it 'should be not equal if Country.code of one Country is nil' do
country_code_nil = FactoryGirl.build(:country, :code => nil)
#country_code_de.should_not == country_code_nil
end
it 'should be equal if Country.code for both countries is nil' do
country_code_nil = FactoryGirl.build(:country, :code => nil)
other_country_code_nil = FactoryGirl.build(:country, :code => nil)
country_code_nil.should == other_country_code_nil
end
it 'should be not equal if other Country is nil' do
#country_code_de.should_not == nil
end
it 'should be not equal if other object is not a Country' do
#country_code_de.should_not == 'test'
end
it 'should be equal for descendants of Country with same Country.code' do
class CountryChild < Country
end
country_child = CountryChild.new(:code => 'de')
#country_code_de.should == country_child
end
end
end

How about this,
def ==(another_country)
return false if code.blank? # Remove this line if you want to return true if code and antoher_country.code are nil
code.to_s.downcase == another_country.to_s.code.downcase rescue false
end
Here if any of code, another_country or another_country.code is nil, it will through up an exception and rescue false statement will return false value.
If everything goes well, the comparison will happen and true or false will be returned based on the input.

Perhaps you could break the logic into two methods, one returning the object's identity, another for checking equality:
class MyClass
def identity
return nil if code.nil?
code.downcase
end
def ==(other)
return false unless other.is_a?(MyClass)
self.identity == other.identity
end
end

If you are using Rails:
def ==(another_country)
return nil unless another_country
code.try(:downcase) == another_country.code.try(:downcase)
end

nil has a to_s method:
def ==(another_country)
#return nil if another_country.nil?
self.code.to_s.downcase == another_country.code.to_s.downcase
end

Since any value that is not nil or false acting like true in conditions, there is some tricks what you can do with the code.
The expression like
(code.nil? ? nil : code.downcase)
can be painlessly replaced by
(code.downcase if code) # or by this one (code && code.downcase)
The second one
(do_something) unless another_country.nil?
as same as
(do_something) if another_country
# or
another_contry && (do_something)
So eventually you can turn your method into this
def ==(another_country)
code && another_country.code &&
code.downcase == another_country.code.downcase
end
Some tests
class Country
attr_accessor :code
def initialize(code)
#code = code
end
def ==(another_country)
code && another_country.code &&
code.downcase == another_country.code.downcase
end
end
p Country.new("FOObar") == Country.new("fooBAR") # => true
p Country.new(nil) == Country.new(nil) # => nil
p Country.new("XXX") == Country.new(nil) # => nil
p Country.new(nil) == Country.new("XXX") # => nil

def == (another_country)
if code.nil? || another_country.nil? || another_country.code.nil?
return nil
end
code.downcase == another_country.code.downcase
end
This way, it is visible at a glance what you are doing - nil check and a comparision.

def == (another_country)
return unless another_country.is_a?(Country)
return if code.nil? || another_country.code.nil?
code.casecmp(another_country.code).zero?
end
The class check is a good practice in case you end up with an array of mixed types.
If you are not worried about the '' vs nil case you can compress it a bit to the following. I don't think it's worth it though.
def == (another_country)
code.try(:casecmp, another_country.code.to_s).try(:zero?) if another_country.is_a?(Country)
end
Note, if you are overriding == you should also override eql? and hash otherwise you can get unexpected results, with hashes and enumerable methods.
Ruby Monk - Equality of Objects

Related

Using Comparable Mixin in Binary Search Tree Node Methods

I am implementing a Binary Search Tree using Ruby. I implemented a comparable mixin to conveniently compare Node objects within the tree (compares the data attribute), however I am finding a NoMethodError when the "leaf?" method is being called:
<=>': undefined method 'data' for nil:NilClass (NoMethodError)
Here is my implementation:
# Node Class
class BstNode
include Comparable
attr_accessor :left
attr_accessor :right
attr_reader :data
def initialize(val)
#data = val
#left = nil
#right = nil
end
def <=>(node)
return self.data <=> node.data
end
def leaf?
return self.left == nil && self.right == nil
end
end
# Tree Class
class BstTree
attr_accessor :root
def initialize(arr)
#root = build_tree(arr)
end
def build_tree(arr)
arr.each.with_index do |val, i|
if i == 0
self.root = BstNode.new(arr[i])
else
insert_node(val)
end
end
return self.root
end
def insert_node(val, node = self.root)
curr = node
left = val < curr.data ? true : false
if curr.leaf?
if left
curr.left = BstNode.new(val)
else
curr.right = BstNode.new(val)
end
elsif curr.left == nil && left
curr.left = BstNode.new(val)
elsif curr.right == nil && !left
curr.right = BstNode.new(val)
else
return left ? insert_node(val, curr.left) : insert_node(val, curr.right)
end
return true
end
end
tree = BstTree.new([4])
puts tree.insert_node(6)
puts tree.insert_node(8)
Any comments or suggestions are appreciated.
Basically Comparable module implements methods like <, <=, ==, >, >=, between? on the object(full list here https://docs.ruby-lang.org/en/2.5.0/Comparable.html) and all of them are basing their functionality of the <=>(space-ship operator).
So when you compare BstNode with nil like BstNode.new(8) == nil the == method will call space-ship operator to resolve the comparison. Because your implementation of it tries to call data method on whatever is passed, it will try to do so even on the nil (equivalent of calling nil.data).
Just add safe navigator(&.) to the space-ship like this, and if think you will be good.
def <=>(node)
return self.data <=> node&.data
end

Ruby/Rspec: should be_false vs should == false

Here's my code:
class Dictionary
def entries
#entries ||= {}
end
def add(hash)
if hash.class == Hash
hash.each_pair do |k, v|
entries[k] = v
end
else
makehash = {hash => nil}
self.add(makehash)
end
#entries = entries
end
def keywords
#entries.keys
end
def include?(k)
if #entries == nil
false
elsif self.keywords.include?(k)
true
else
false
end
end
end
And here's the test I'm running it against:
require 'dictionary'
describe Dictionary do
before do
#d = Dictionary.new
end
it 'can check whether a given keyword exists' do
#d.include?('fish').should be_false
end
Now, that test will fail. However, if I change it to
it 'can check whether a given keyword exists' do
#d.include?('fish').should == false
end
then it passes.
How can I change my code so that should be_false passes instead of should == false? Thanks.
be_false matches falsey values (nil and false) and
be_true matches truthy values (other than nil or false)
From Rspec > 3.0,
be_false is renamed to be_falsey and
be_true is renamed to be_truthy
If you want to exactly match false, you should use
obj.should eq false
See the Documentation for more info about 'be' matchers

Not Defining Second Method?

I want this code to define two methods, nilguard and falseguard, which guard against nil and false values.
Object.class_eval do
#list = [false, nil]
#list.each do |i|
define_method :"#{i}guard" do |other|
if self == i
return other
else
return self
end
end
end
end
For some reason, it only defines falseguard, which works fine. Why is this, why isn't it defining the other method?
nil.to_s == '', so your second method will just be called guard. You might want to use something like this instead:
#list = { false: false, nil: nil }
#list.each do |s, i|
define_method "#{s}guard" do |other|
# ...

How to make the function accept a block

I am having a class G and my custom function func which i expect to take a block like this:
class G
def func(&block)
return '1' unless block_given?
# More code
end
end
I think that now when i do
G g = new G
g.func {|t| t}
block_given? should return true but its returning false
I hve tried following variants as well to no resort
g.func do |t|
end
Any help would be appreciated.
It's working fine if you correct some minor syntax errors. Note that there is no type declaration for ruby variables and object instantiation is done through an instance method of class Class instead with keyword (like in Java):
class G
def func(&block)
return '1' unless block_given?
block.call
end
end
g = G.new
g.func { puts 'block was called' }
g.func
# Output:
# irb(main):046:0>g.func { puts 'block was called' }
# block was called
# => nil
# irb(main):047:0>g.func
# => "1"
(Adding my output, although Matt beat me to it.)
> class G
> def func(&block)
> return '1' unless block_given?
> # More code
> end
> end
=> nil
> g = G.new
=> #<G:0x856e444>
> g.func { |t| puts "hi" }
=> nil
> g.func
=> "1"

What's the right way to implement equality in ruby

For a simple struct-like class:
class Tiger
attr_accessor :name, :num_stripes
end
what is the correct way to implement equality correctly, to ensure that ==, ===, eql?, etc work, and so that instances of the class play nicely in sets, hashes, etc.
EDIT
Also, what's a nice way to implement equality when you want to compare based on state that's not exposed outside the class? For example:
class Lady
attr_accessor :name
def initialize(age)
#age = age
end
end
here I'd like my equality method to take #age into account, but the Lady doesn't expose her age to clients. Would I have to use instance_variable_get in this situation?
To simplify comparison operators for objects with more than one state variable, create a method that returns all of the object's state as an array. Then just compare the two states:
class Thing
def initialize(a, b, c)
#a = a
#b = b
#c = c
end
def ==(o)
o.class == self.class && o.state == state
end
protected
def state
[#a, #b, #c]
end
end
p Thing.new(1, 2, 3) == Thing.new(1, 2, 3) # => true
p Thing.new(1, 2, 3) == Thing.new(1, 2, 4) # => false
Also, if you want instances of your class to be usable as a hash key, then add:
alias_method :eql?, :==
def hash
state.hash
end
These need to be public.
To test all your instance variables equality at once:
def ==(other)
other.class == self.class && other.state == self.state
end
def state
self.instance_variables.map { |variable| self.instance_variable_get variable }
end
Usually with the == operator.
def == (other)
if other.class == self.class
#name == other.name && #num_stripes == other.num_stripes
else
false
end
end

Resources