问题
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