Utiliser les Ranges Ruby pour manipuler des dates
Un nouvel exemple de la puissance de la librairie standard de Ruby

Il est assez fréquent d’avoir besoin de définir des périodes temporelles dans une application, le plus souvent elle est modélisée par une date de début et une date de fin, et de comparer ces dates à la date courante. Problème : cela donne souvent lieu à des méthodes dont la lisibilité laisse à désirer.
class User
def paying_member?
subscription_started_at <= Date.current &&
(subscription_ended_at.nil? || subscription_ended_at > Date.current)
end
end
Et puis j'ai vu ce tweet de Matt Swanson qui m'a rappelé qu'il était possible de créer des Range
Ruby avec des dates pour simplifier certaines de ces opérations
📆 Refactoring code that past-me wrote because I forget how nice Ruby's Range#cover? and Range#overlap? methods are, especially with Date ranges. Super readable. pic.twitter.com/ofckklPE8y
— matt swanson 🤔 🦢 (@_swanson) October 6, 2020
Comme on peut le voir, mon exemple précédent se trouve fortement simplifié et plus lisible grâce à l'utilisation de la classe Range
par rapport à la version initiale.
class User
def paying_member?
subscription_period.cover?(Date.current)
end
def subscription_period
subscription_started_at..subscription_ended_at
end
end
Et maintenant que nous avons un objet sous la main pour modéliser la période d'abonnement d'un utilisateur il devient plus facile de modifier le comportement de notre application ou d'ajouter des fonctionnalités.
Par exemple, si il est acté que la date de fin d'un abonnement correspond au premier jour où l'utilisateur n'est plus abonné, on peut modifier notre période pour exclure la date de fin du Range
class User
# ...
def subscription_period
subscription_started_at...subscription_ended_at
end
end
Dans une application Rails on peut aussi profiter des méthodes Range#includes?
ou Range#overlaps?
ajoutées par ActiveSupport. On peut les utiliser pour par exemple partitionner les utilisateurs selon le moment où ils ont souscrit, ou par exemple savoir si 2 abonnés ont été abonné au service sur la même période de temps.
class User
# ...
def subscriber_kind
case subscription_period
when innovator_period then 'Innovator'
when early_adopter_period then 'Early adopter'
when early_majority_period then 'Early majority'
when late_majority_period then 'Late majority'
when laggards_period then 'Laggard'
end
def share_subscription_with(user)
subscription_period.overlaps?(user.subscription_period)
end
end
Et pour finir, voici quelques lien pour aller plus loin sur le sujet