lib/characterizable.rb in characterizable-0.0.17 vs lib/characterizable.rb in characterizable-0.1.0

- old
+ new

@@ -10,10 +10,16 @@ active_support/core_ext/module/aliasing }.each do |active_support_3_requirement| require active_support_3_requirement end if ActiveSupport::VERSION::MAJOR == 3 +$:.unshift File.dirname(__FILE__) +require 'characterizable/base' +require 'characterizable/better_hash' +require 'characterizable/characteristic' +require 'characterizable/snapshot' + module Characterizable def self.included(klass) klass.cattr_accessor :characterizable_base klass.extend ClassMethods end @@ -23,200 +29,23 @@ end def expire_snapshot! @_characteristics = nil end - - class BetterHash < ::Hash - # In Ruby 1.9, running select/reject/etc. gives you back a hash - # if RUBY_VERSION < '1.9' - def to_hash - Hash.new.replace self - end - def as_json(*) - to_hash - end - def reject(&block) - inject(Characterizable::BetterHash.new) do |memo, ary| - unless block.call(*ary) - memo[ary[0]] = ary[1] - end - memo - end - end - def select(&block) - inject(Characterizable::BetterHash.new) do |memo, ary| - if block.call(*ary) - memo[ary[0]] = ary[1] - end - memo - end - end - # I need this because otherwise it will try to do self.class.new on subclasses - # which would get "0 for 1" arguments error with Snapshot, among other things - def slice(*keep) - inject(Characterizable::BetterHash.new) do |memo, ary| - if keep.include?(ary[0]) - memo[ary[0]] = ary[1] - end - memo - end - end - # end + + def display_characteristic(name) + characteristic = self.class.characteristics[name] + characteristic.display(characteristics) if characteristic end - class Snapshot < BetterHash - attr_reader :universe - def initialize(universe) - @universe = universe - _take_snapshot - end - def _take_snapshot - universe.characterizable_base.characteristics.each do |_, c| - if c.known?(universe) - if c.effective?(universe) - self[c.name] = c.value(universe) - elsif c.trumped?(universe) - trumped_keys.push c.name - elsif !c.revealed?(universe) - wasted_keys.push c.name - lacking_keys.push c.prerequisite - end - end - end - end - def []=(key, value) - universe.expire_snapshot! - super - end - def wasted_keys - @wasted_keys ||= Array.new - end - def trumped_keys - @trumped_keys ||= Array.new - end - def lacking_keys - @lacking_keys ||= Array.new - end - def effective - universe.characterizable_base.characteristics.select { |_, c| c.effective?(self) } - end - def potential - universe.characterizable_base.characteristics.select { |_, c| c.potential?(self) } - end - def wasted - universe.characterizable_base.characteristics.slice(*wasted_keys) - end - def lacking - universe.characterizable_base.characteristics.slice(*(lacking_keys - wasted_keys)) - end - def trumped - universe.characterizable_base.characteristics.slice(*trumped_keys) - end - end - module ClassMethods def characterize(&block) self.characterizable_base ||= Characterizable::Base.new self Blockenspiel.invoke block, characterizable_base end def characteristics characterizable_base.characteristics end end - class CharacteristicAlreadyDefined < ArgumentError - end - - class Base - attr_reader :klass - def initialize(klass) - @klass = klass - end - def characteristics - @_characteristics ||= BetterHash.new - end - include Blockenspiel::DSL - def has(name, options = {}, &block) - raise CharacteristicAlreadyDefined, "The characteristic #{name} has already been defined on #{klass}!" if characteristics.has_key?(name) - characteristics[name] = Characteristic.new(self, name, options, &block) - begin - # quacks like an activemodel - klass.define_attribute_methods unless klass.respond_to?(:attribute_methods_generated?) and klass.attribute_methods_generated? - rescue - # for example, if a table doesn't exist... just ignore it - end - begin - klass.module_eval(%{ - def #{name}_with_expire_snapshot=(new_#{name}) - expire_snapshot! - self.#{name}_without_expire_snapshot = new_#{name} - end - alias_method_chain :#{name}=, :expire_snapshot - }, __FILE__, __LINE__) #if klass.instance_methods.include?("#{name}=") - rescue - end - end - end - class Characteristic - attr_reader :base - attr_reader :name - attr_reader :trumps - attr_reader :prerequisite - attr_reader :options - def initialize(base, name, options = {}, &block) - @base = base - @name = name - @trumps = Array.wrap options.delete(:trumps) - @prerequisite = options.delete(:prerequisite) - @options = options - Blockenspiel.invoke block, self if block_given? - end - def as_json(*) - { :name => name, :trumps => trumps, :prerequisite => prerequisite, :options => options } - end - def inspect - "<Characterizable::Characteristic name=#{name.inspect} trumps=#{trumps.inspect} prerequisite=#{prerequisite.inspect} options=#{options.inspect}>" - end - def characteristics - base.characteristics - end - def value(universe) - case universe - when Hash - universe[name] - else - universe.send name if universe.respond_to?(name) - end - end - def known?(universe) - not value(universe).nil? - end - def potential?(universe) - not known?(universe) and revealed? universe and not trumped? universe - end - def effective?(universe, ignoring = nil) - known?(universe) and revealed? universe and not trumped? universe, ignoring - end - def trumped?(universe, ignoring = nil) - characteristics.each do |_, other| - if other.trumps.include? name and not ignoring == other.name - if trumps.include? other.name - # special case: mutual trumping. current characteristic is trumped if its friend is otherwise effective and it is not otherwise effective - return true if other.effective? universe, name and not effective? universe, other.name - else - return true if other.effective? universe - end - end - end - false - end - def revealed?(universe) - return true if prerequisite.nil? - characteristics[prerequisite].effective? universe - end - include Blockenspiel::DSL - def reveals(other_name, other_options = {}, &block) - base.has other_name, other_options.merge(:prerequisite => name), &block - end - end + class CharacteristicAlreadyDefined < ArgumentError; end end