lib/characterizable.rb in characterizable-0.0.1 vs lib/characterizable.rb in characterizable-0.0.2
- old
+ new
@@ -1,5 +1,6 @@
+require 'set'
require 'blockenspiel'
require 'active_support'
require 'active_support/version'
%w{
active_support/core_ext/class/attribute_accessors
@@ -16,143 +17,159 @@
klass.cattr_accessor :characterizable_base
klass.extend ClassMethods
end
def characteristics
- @_characteristics ||= ArrayOfBoundCharacteristics.new self
+ @_characteristics ||= Snapshot.new self
end
- def clear_characteristics
+ def dirty_characteristics!
@_characteristics = nil
end
-
- def known_characteristics
- characteristics.select do |c|
- c.known? and not c.trumped?
+
+ # hashes that survive as such when you select/reject/slice them
+ class SurvivorHash < Hash
+ attr_reader :survivor_args
+ def initialize(*survivor_args)
+ @survivor_args = survivor_args
end
+ def reject(&block)
+ inject(self.class.new(*survivor_args)) do |memo, ary|
+ unless block.call(*ary)
+ memo[ary[0]] = ary[1]
+ end
+ memo
+ end
+ end
+ def select(&block)
+ inject(self.class.new(*survivor_args)) do |memo, ary|
+ if block.call(*ary)
+ memo[ary[0]] = ary[1]
+ end
+ memo
+ end
+ end
+ def slice(*keys)
+ inject(self.class.new(*survivor_args)) do |memo, ary|
+ if keys.include?(ary[0])
+ memo[ary[0]] = ary[1]
+ end
+ memo
+ end
+ end
end
- def unknown_characteristics
- characteristics.select do |c|
- c.unknown? and c.requited? and not c.trumped?
+ class Snapshot < SurvivorHash
+ def initialize(*survivor_args)
+ super
+ take_snapshot
end
+ def snapshotted_obj
+ survivor_args.first
+ end
+ def []=(key, value)
+ snapshotted_obj.dirty_characteristics!
+ super
+ end
+ def take_snapshot
+ snapshotted_obj.characterizable_base.characteristics.each do |k, c|
+ if c.known?(snapshotted_obj) and c.requited?(snapshotted_obj) and not c.trumped?(snapshotted_obj)
+ self[k] = snapshotted_obj.send c.name
+ end
+ end
+ end
+ def known
+ snapshotted_obj.characterizable_base.characteristics.select do |_, c|
+ c.known?(self) and c.requited?(self) and not c.trumped?(self)
+ end
+ end
+ def unknown
+ snapshotted_obj.characterizable_base.characteristics.select do |_, c|
+ c.unknown?(self) and c.requited?(self) and not c.trumped?(self)
+ end
+ end
+ def visible_known
+ known.reject { |_, c| c.hidden? }
+ end
+ def visible_unknown
+ unknown.reject { |_, c| c.hidden? }
+ end
end
- def visible_known_characteristics
- known_characteristics.reject { |c| c.hidden? }
- end
-
- def visible_unknown_characteristics
- unknown_characteristics.reject { |c| c.hidden? }
- end
-
module ClassMethods
def characterize(&block)
self.characterizable_base = Characterizable::Base.new self
Blockenspiel.invoke block, characterizable_base
end
delegate :characteristics, :to => :characterizable_base
end
- # don't want to use a Hash, because that would be annoying to select from
- class ArrayOfCharacteristics < Array
- def [](key_or_index)
- case key_or_index
- when String, Symbol
- detect { |c| c.name == key_or_index }
- else
- super
- end
- end
- end
-
- class ArrayOfBoundCharacteristics < ArrayOfCharacteristics
- attr_reader :bound_obj
- def initialize(*args)
- @bound_obj = args.pop
- super
- load_bound_characteristics
- end
- def load_bound_characteristics
- bound_obj.characterizable_base.characteristics.each do |c|
- b_c = c.dup
- b_c.bound_obj = bound_obj
- push b_c
- end
- end
- end
-
class Base
attr_reader :klass
def initialize(klass)
@klass = klass
end
def characteristics
- @_characteristics ||= ArrayOfCharacteristics.new
+ @_characteristics ||= SurvivorHash.new
end
include Blockenspiel::DSL
def has(name, options = {}, &block)
- characteristics.push Characteristic.new(self, name, options, &block)
+ characteristics[name] = Characteristic.new(self, name, options, &block)
+ klass.module_eval(%{
+ def #{name}_with_dirty_characteristics=(new_#{name})
+ dirty_characteristics!
+ self.#{name}_without_dirty_characteristics = new_#{name}
+ end
+ alias_method_chain :#{name}=, :dirty_characteristics
+ }, __FILE__, __LINE__) if klass.instance_methods.include?("#{name}=")
end
end
class Characteristic
- class TreatedBoundAsUnbound < RuntimeError; end
- class TreatedUnboundAsBound < RuntimeError; end
attr_reader :base
attr_reader :name
attr_reader :trumps
attr_reader :prerequisite
attr_reader :hidden
attr_reader :options
def initialize(base, name, options = {}, &block)
@base = base
- @name = name.to_sym
+ @name = name
@trumps = Array.wrap(options.delete(:trumps))
@prerequisite = options.delete :prerequisite
@hidden = options.delete :hidden
@options = options
Blockenspiel.invoke block, self if block_given?
end
delegate :characteristics, :to => :base
- attr_accessor :bound_obj
- def effective_obj(obj = nil)
- raise TreatedBoundAsUnbound, "Can't treat as unbound if bound object is set" if obj and bound_obj
- raise TreatedUnboundAsBound, "Need an object if unbound" unless obj or bound_obj
- obj || bound_obj
+ def value(obj)
+ case obj
+ when Hash
+ obj[name]
+ else
+ obj.send name
+ end
end
- def value(obj = nil)
- effective_obj(obj).send name
- end
- def unknown?(obj = nil)
+ def unknown?(obj)
value(obj).nil?
end
- def known?(obj = nil)
+ def known?(obj)
not unknown?(obj)
end
- def trumped?(obj = nil)
- effective_obj(obj).characteristics.any? do |c|
+ def trumped?(obj)
+ characteristics.any? do |_, c|
c.known?(obj) and c.trumps.include?(name)
end
end
- def requited?(obj = nil)
+ def requited?(obj)
return true if prerequisite.nil?
- effective_obj(obj).characteristics[prerequisite].known? obj
+ characteristics[prerequisite].known? obj
end
def hidden?
hidden
end
include Blockenspiel::DSL
def reveals(other_name, other_options = {}, &block)
base.has other_name, other_options.merge(:prerequisite => name), &block
- base.klass.module_eval %{
- def #{name}_with_dependent_#{other_name}=(new_#{name})
- if new_#{name}.nil?
- self.#{other_name} = nil
- end
- self.#{name}_without_dependent_#{other_name} = new_#{name}
- end
- alias_method_chain :#{name}=, :dependent_#{other_name}
- }, __FILE__, __LINE__
end
end
end