Update Nov. 2017: Took these ideas and created a gem Ensurance
Been plowing through a massive refactoring of a legacy Rails app. One of the things I find myself doing all the time now is ensuring. It’s a handy little concern that works like this:
In the old days, we’d do this:
class SomeClass def some_method(person_id) person = Person.find(person_id) # then proceed to do something useful end end
But when testing in the console I was always having to lookup a Person record to get the id, or sometimes I’d remember the ID. Wouldn’t it be nice if I could pass anything(within reason) and be able to ensure that I have a Person record?
[granted…this is Rails specific]
module Ensurable extend ActiveSupport::Concern ## --- # Allow you to ensure you have the class you expect... it's similar to # result = value.is_a?(Person) ? value : Person.find(value) # # You can add fields to "ensure_by" (:id is included always) # e.g. # if you add `ensure_by :token` to the User class # User.ensure() works # User.ensure(:user_id) works # User.ensure(:token) works # # .ensure() returns nil if the record is not found # .ensure!() throws an exception if the record is not found class_methods do def ensure_by(*args) @_ensure_by ||= [:id] @_ensure_by += [args].flatten end def ensure(thing = nil) return nil unless thing.present? return thing if thing.is_a?(self) found = nil @_ensure_by ||= [:id] @_ensure_by.each do |ensure_field| value = thing.try(:fetch, ensure_field.to_sym, nil) || thing.try(:fetch, ensure_field.to_s, nil) || thing found = self.find_by(ensure_field => value) break if found.is_a?(self) end found end def ensure!(thing = nil) result = self.ensure(thing) raise ActiveRecord::RecordNotFound unless result result end end end
Now, you can just:
class SomeClass def some_method(person) person = Person.ensure!(person) # then proceed to do something useful end end
Once you start using it, you’ll wish all objects would be as friendly.
Example:
class Date def self.ensure(thing) thing.is_a?(Date) ? thing : Date.parse(thing.to_s) end end
enjoy. 🙂
Update: June 10, 2017
I’ve found these two extensions to Time and Date most helpful:
class Time def self.ensure(thing) case thing when Time thing when Date thing.beginning_of_day when Integer, Float Time.at(thing) when String if thing.to_i.to_s == thing Time.at(thing.to_i) elsif thing.to_f.to_s == thing Time.at(thing.to_f) else Time.parse(thing) end else raise ArgumentError.new("Unknown Type for Time to ensure: #{thing.class.name}") end end end class Date def self.ensure(thing) case thing when Date thing when String Date.parse(thing) else if thing.respond_to?(:to_date) thing.to_date else raise ArgumentError.new("Unknown Type for Date to ensure: #{thing.class.name}") end end end end