воскресенье, 29 марта 2015 г.

Примеры упрощения кода

Упрощать код очень важно, т.к. это увеличивает его читаемость. Одну и ту же задачу можно сделать одной, так и десятью строчками кода. Чем код компактнее, тем его легче поддерживать. Разумеется, не нужно доходить до фанатизма, пытаясь уместить все в одну строчку, т.к. это может привести к ухудшению его читаемости.

  1. lots.map { |lot| lot.start_date }.compact.min
    
    # Упростили
    lots.map(&:start_date).compact.min
    
  2. if age.present?
      I18n.t('firm.regular')
    else
      I18n.t('firm.new')
    end
    
    # Упростили:
    I18n.t("firm.#{age.present? ? :regular : :new}")
    
  3. if company.type_id == nil || company.type_id == CompanyType.online.id
      return false
    end
    
    # Упростили
    return false if [nil, CompanyType.online.id].include? company.type_id
    
  4. if companies.select { |company| company.store_type == company.online_type }.empty?
      return false
    end
    
    # Упростили
    return false if companies.none? { |company| company.store_type == company.online_type }
    

пятница, 27 марта 2015 г.

Rails: undefined method `to_datetime' for nil:NilClass

В Rails может возникнуть ошибка undefined method `to_datetime' for nil:NilClass при выполнении следующего кода:
filtered_products = products.select do |product|
  product.start_date && product.end_date &&
  (product.start_date <= start_date_ago && start_date_ago <= product.end_date) ||
  (start_date_ago <= product.start_date && product.start_date < first_start_date)
end

Здесь берутся товары и отбираются те из них, что попадают в нужный диапазон дат.
product.start_date - дата начала периода публикации товара
product.end_date - дата конца периода публикации товара
В начале мы проверяем, что у товара есть эти параметры, а уже после этого сравниваем их с датами first_start_date и start_date_ago, которые определены где-то выше по коду. Это позволяет нам избежать ошибки:
NoMethodError: undefined method `<' for nil:NilClass.

Почему же в приведенном выше коде возникает ошибка undefined method `to_datetime' for nil:NilClass ?

Это связано с тем, что мы по невнимательности нарушили расстановку скобок. Сначала нам нужно убедиться в наличии параметров product.start_date и product.end_date, а уже потом выполнять остальные сравнения:
filtered_products = products.select do |product|
  product.start_date && product.end_date &&
  ((product.start_date <= start_date_ago && start_date_ago <= product.end_date) ||
  (start_date_ago <= product.start_date && product.start_date < first_start_date))
end




вторник, 10 марта 2015 г.

ArgumentError: comparison of String with Date failed

Что не так со следующим кодом?
# здесь custom_lot_attributes - колонка типа 'hstore' в таблице lots
start_date = lot.custom_lot_attributes['start_date']
if start_date && start_date < Date.today
  # ...
end

При выполнии этого участка кода возникает ошибка:
ArgumentError: comparison of String with Date failed
Это связано с тем, что мы пытаемся сравнить дату со строкой. Из hstore мы получаем строку, поэтому в переменной start_date содержится дата в виде строки, к примеру "2015-03-10".
Для устранения ошибки нужно преобразовать строку в дату. Для этого в Rails есть метод to_date.
Теперь ошибки возникать не будет:

start_date = lot.custom_lot_attributes['start_date'].to_date
if start_date && start_date < Date.today
  # ...
end

А сможете ли вы найти ошибку в этом коде?
start_date = lot.custom_lot_attributes['start_date'].to_date
if start_date && start_date < Date.today
  lot.custom_lot_attributes['start_date'] = Date.today
  lot.custom_lot_attributes['end_date'] = 
    lot.custom_lot_attributes['end_date'] +
    (Date.today - lot.custom_lot_attributes['start_date'])
end

Данный код будет вызывать ошибку:
TypeError: can't convert Rational into String
Причина аналогична предыдущей, мы забыли преобразовать строковое значение, полученное из hstore, в дату.
Вот как должно быть:
start_date = lot.custom_lot_attributes['start_date'].to_date
if start_date && start_date < Date.today
  lot.custom_lot_attributes['start_date'] = Date.today
  lot.custom_lot_attributes['end_date'] = 
    lot.custom_lot_attributes['end_date'].to_date +
    (Date.today - lot.custom_lot_attributes['start_date'].to_date)
end