Last active
August 29, 2015 14:21
-
-
Save Aupajo/f44884ad22fefefb7918 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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