нечувствительный к регистру метод equals, основанный на одном атрибуте

Оригинальный вопрос

Это действительно ужасный метод, который проверяет равенство на основе кода, но не зависит от регистра.

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

Можете ли вы указать мне правильное направление, как написать это более элегантно, не полагаясь на уродливые структуры if else?

Это решение, которое я использовал (+ 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

Расширенные тесты:

# 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

person wintersolutions    schedule 19.02.2012    source источник
comment
@Visitor: Это был мой самый продуктивный вопрос в SC на сегодняшний день. Решение в моем вопросе - самое короткое, что мы могли придумать, но в ответах можно найти много мудрости;)   -  person wintersolutions    schedule 19.02.2012


Ответы (7)


Как насчет этого,

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

Здесь, если какой-либо из code, another_country или another_country.code равен нулю, он вызовет исключение, и оператор rescue false вернет значение false.

Если все пойдет хорошо, произойдет сравнение, и на основе ввода будет возвращено true or false.

person nkm    schedule 19.02.2012

Возможно, вы могли бы разбить логику на два метода, один из которых возвращает идентификатор объекта, а другой проверяет равенство:

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
person Mladen Jablanović    schedule 19.02.2012
comment
Должен ли identity быть закрытым? - person Linuxios; 19.02.2012
comment
Зависит от обстоятельств, я думаю. - person Mladen Jablanović; 19.02.2012

Если вы используете Rails:

def ==(another_country)
  return nil unless another_country
  code.try(:downcase) == another_country.code.try(:downcase)
end
person iltempo    schedule 19.02.2012
comment
Верно. Добавлено return nil unless another_country в качестве первой строки метода. - person iltempo; 19.02.2012

nil имеет метод to_s:

def ==(another_country)
   #return nil if another_country.nil?
   self.code.to_s.downcase == another_country.code.to_s.downcase
end
person steenslag    schedule 19.02.2012
comment
Что произойдет, если another_country будет nil? - person Femaref; 19.02.2012
comment
Я закончил тем, что использовал это с дополнительным исключением Another_country.nil? потому что он не привязан к рельсам - person wintersolutions; 19.02.2012
comment
Я был бы очень удивлен, если бы ==method вернул nil, но вы правы, код OP делает это. Отредактированный код - person steenslag; 19.02.2012
comment
@steenslag Мой метод не возвращает ноль, по крайней мере, согласно отладчику, не так ли? - person wintersolutions; 19.02.2012

Поскольку любое значение, отличное от nil или false, действует как true в условиях, есть некоторые хитрости, которые вы можете сделать с кодом.

Выражение вроде

(code.nil? ? nil : code.downcase)

можно безболезненно заменить

(code.downcase if code) # or by this one (code && code.downcase)

Второй

(do_something) unless another_country.nil?

такой же как

(do_something) if another_country 
# or 
another_contry && (do_something)

Итак, в конечном итоге вы можете превратить свой метод в этот

def ==(another_country)
  code && another_country.code && 
  code.downcase == another_country.code.downcase
end

Некоторые тесты

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
person evfwcqcg    schedule 19.02.2012
comment
Ваше решение не работает, если оба кода равны nil. Я многому научился из вашего кода, и я уверен, что ваш подход к разбиению большой проблемы на маленькие подзадачи пригодится мне в будущем (+1) - person wintersolutions; 19.02.2012

def == (another_country)    
    if code.nil? || another_country.nil? || another_country.code.nil?
      return nil
    end

    code.downcase == another_country.code.downcase
end

Таким образом, сразу видно, что вы делаете - нулевая проверка и сравнение.

person Femaref    schedule 19.02.2012
comment
Я думаю, вам не хватает return. Кроме того, какой смысл возвращать nil из метода ==? Я считаю, что он всегда должен возвращать логическое значение. - person Mladen Jablanović; 19.02.2012
comment
Я просто копирую его исходное выражение — в конце есть «если». something unless true приводит к nil. В части if отсутствует возврат, да. - person Femaref; 19.02.2012

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

Проверка класса является хорошей практикой, если вы в конечном итоге получаете массив смешанных типов.

Если вас не беспокоит случай '' vs nil, вы можете немного сжать его до следующего. Я не думаю, что это того стоит.

def == (another_country)
  code.try(:casecmp, another_country.code.to_s).try(:zero?) if another_country.is_a?(Country)
end

Обратите внимание: если вы переопределяете ==, вы также должны переопределять eql? и хэш, иначе вы можете получить неожиданные результаты с помощью хэшей и перечислимых методов.

Ruby Monk — равенство Объекты

person Joshua    schedule 18.11.2014