Элегантный способ безопасно сменить день рубина? - PullRequest
0 голосов
/ 26 января 2019

Я должен создать список из 24 месяцев с теми же day среди них, правильно обрабатывая месяцы, у которых нет 29, 30 или 31 дня.

В настоящее время я делаю:

def dates_list(first_month, assigned_day)
 (0...24).map do |period|
    begin
      (first_month + period.months).change(day: assigned_day)
    rescue ArgumentError
      (first_month + period.months).end_of_month
    end
  end
end

Мне нужно спастись от ArgumentError, поскольку некоторые случаи поднимают его:

Date.parse('10-Feb-2019').change(day: 30)
# => ArgumentError: invalid date

Я ищу сейф и элегантный решение, которое может уже существовать в рубине или рельсахЧто-то вроде:

Date.parse('10-Feb-2019').safe_change(day: 30) # => 28-Feb-2019

Так что я могу написать:

def dates_list(first_month, assigned_day)
  (0...24).map do |period|
    (first_month + period.months).safe_change(day: assigned_day)
  end
end

Это существует, или мне понадобится обезьяна патч Date?

Временные решения (какметод, который уже создает этот список) очень приветствуются.

1 Ответ

0 голосов
/ 26 января 2019

UPDATE

Дискуссия о том, что делать с отрицательным значением и 0 днями, заставила меня понять, что эта функция пытается угадать намерение пользователя. И это также жестко определяет, сколько месяцев генерировать, и генерировать по месяцам.

Это заставило меня задуматься, что делает этот метод? Он генерирует список наступающих месяцев фиксированного размера, изменяет их фиксированным образом и угадывает, чего хочет пользователь. Если описание вашей функции включает в себя « и », вам, вероятно, понадобится несколько функций. Мы отделяем создание списка дат от изменения списка. Мы заменяем жестко закодированные детали параметрами. И вместо того, чтобы угадывать, что хочет пользователь, мы позволяем ему сообщить нам блок.

def date_generator(from, by:, how_many:)
  (0...how_many).map do |period|
    date = from + period.send(by)
    yield date
  end
end

Пользователь может очень четко сказать, что он хочет изменить. Никаких сюрпризов ни для пользователя, ни для читателя.

p date_generator(Date.parse('2019-02-01'), by: :month, how_many: 24) { |month|
  month.change(day: month.end_of_month.day)
}

Мы можем сделать этот шаг дальше, превратив его в Перечислитель . Тогда вы можете иметь столько, сколько захотите, и делать с ними все, что захотите, используя обычные перечисляемые методы ..

INFINITY = 1.0/0.0
def date_iterator(from, by:)
  Enumerator.new do |block|
    (0..INFINITY).each do |period|
      date = from + period.send(by)
      block << date
    end
  end
end

p date_iterator(Date.parse('2019-02-01'), by: :month)
    .take(24).map { |date|
      date.change(day: date.end_of_month.day)
    }

Теперь вы можете генерировать любой список дат, повторяющийся по любому полю, любой длины, с любыми изменениями. Вместо того, чтобы быть скрытым в методе, то, что происходит, очень ясно для читателя. И если у вас есть особый, общий случай, вы можете обернуть это в метод.

И последний шаг - сделать метод Date.

class Date
  INFINITY = 1.0/0.0
  def iterator(by:)
    Enumerator.new do |block|
      (0..INFINITY).each do |period|
        date = self + period.send(by)
        block << date
      end
    end
  end
end

Date.parse('2019-02-01')
  .iterator(by: :month)
  .take(24).map { |date|
    date.change(day: date.end_of_month.day)
  }

И если у вас есть особый, общий случай, вы можете написать для него особую функцию case, дать ему описательное имя и задокументировать его особое поведение.

def next_two_years_of_months(date, day:)
  if day <= 0
    raise ArgumentError, "The day must be positive"
  end

  date.iterator(by: :month)
    .take(24)
    .map { |next_date|
      next_date.change(day: [day, next_date.end_of_month.day].min)
    }
end

ПРЕДЫДУЩИЙ ОТВЕТ

Мой первый рефакторинг - удалить избыточный код.

require 'date'

def dates_list(first_month, assigned_day)
 (0...24).map do |period|
   next_month = first_month + period.months
   begin
     next_month.change(day: assigned_day)
   rescue ArgumentError
     next_month.end_of_month
   end
 end
end

На данный момент, IMO, функция в порядке. Понятно что происходит. Но вы можете сделать еще один шаг вперед.

def dates_list(first_month, assigned_day)
  (0...24).map do |period|
    next_month = first_month + period.months
    day = [assigned_day, next_month.end_of_month.day].min
    next_month.change(day: day)
  end
end

Я думаю, что это немного лучше. Это делает решение немного более явным и не учитывает другие возможные ошибки аргумента.

Если вы часто этим занимаетесь, вы можете добавить его как Date метод.

class Date
  def change_day(day)
    change(day: [day, end_of_month.day].min)
  end
end

Мне не так жарко ни на change_day, ни safe_change. Ни один из них на самом деле не говорит: «Это будет использовать день, или, если последний день месяца выходит за границы», и я не уверен, как это выразить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...