case insensitive equals method based on one attribute

寵の児 提交于 2019-12-10 17:30:55

问题


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

回答1:


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.




回答2:


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



回答3:


If you are using Rails:

def ==(another_country)
  return nil unless another_country
  code.try(:downcase) == another_country.code.try(:downcase)
end



回答4:


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



回答5:


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



回答6:


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.




回答7:


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



来源:https://stackoverflow.com/questions/9349668/case-insensitive-equals-method-based-on-one-attribute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!