Skip to content

Instantly share code, notes, and snippets.

@Aupajo
Last active August 29, 2015 14:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Aupajo/f44884ad22fefefb7918 to your computer and use it in GitHub Desktop.
Save Aupajo/f44884ad22fefefb7918 to your computer and use it in GitHub Desktop.
class TodoItem < ActiveRecord::Base
extend TimeState
# Scopes added: finished, not_finished
# Instance methods added: finished?, finish!
time_state :finished_at
end
todo_item = TodoItem.create
todo_item.finished_at # => nil
todo_item.finished? # => false
todo_item.finish! # => true
todo_item.finished_at # => Mon, 18 May 2015 11:57:06 +1200
TodoItem.finished.count # => 1
TodoItem.not_finished.count # => 0
module TimeState
# Options for time_state
# - :adjective (used to infer method names, defaults to the field name without the "_at")
# - :verb (used to infer method names, defaults to the adjective without the "ed")
# - :predicate (specify a name, otherwise will infer from :adjective, set to false if not needed)
# - :action (specify a name, otherwise will infer from :verb, set to false if not needed)
# - :scope (specify a name, otherwise will infer from :adjective, set to false if not needed)
# - :inverse_scope (specify a name, otherwise will infer from :scope, set to false if not needed)
def time_state(attribute, options = {})
TimeStateField.new(attribute, options).define_methods!(self)
end
class TimeStateField
attr_reader :attribute, :options
def initialize(attribute, options)
@attribute = attribute
@options = options
@options.reverse_merge!(
predicate: "#{adjective}?",
action: "#{verb}!",
scope: adjective,
inverse_scope: "not_#{adjective}"
)
end
def define_methods!(klass)
define_instance_methods!(klass)
define_scopes!(klass)
end
def define_instance_methods!(klass)
opted_methods(:predicate, :action).each do |inst_method, name|
inst_method = method(inst_method)
klass.instance_eval do
define_method(name) { inst_method.call(self) }
end
end
end
def define_scopes!(klass)
opted_methods(:scope, :inverse_scope).each do |scope, name|
klass.send(:scope, name, send(scope, klass))
end
end
def opted_methods(*names)
options.compact.slice(*names)
end
def action(object)
object.update!(attribute => Time.now)
end
def predicate(object)
object.send(attribute).present?
end
def scope(klass)
-> { klass.where.not(attribute => nil) }
end
def inverse_scope(klass)
-> { klass.where(attribute => nil) }
end
def field_name
@field_name ||= attribute.to_s
end
def adjective
@adjective ||= options[:adjective] || field_name.chomp('_at')
end
def verb
@verb ||= options[:verb] || adjective.chomp('ed')
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment