module ThingTank::CharacterHandling

Public Class Methods

included(klass) click to toggle source
# File lib/thingtank/character_handling.rb, line 3
def self.included(klass)
  klass.class_eval do

    def properties_of_character(klass)
      hsh = {}
      (klass.character_properties || []).each do |k| 
        hsh.update(k.to_s => self[k.to_s])  unless self[k.to_s].nil?
      end
      hsh
    end

    def get_character(klass, db)
      hsh = properties_of_character(klass)
      hsh.update "_id" => self["_id"], "_rev" => self["_rev"] if (had?(klass) && !hsh.empty?)
      inst = klass.new hsh, :directly_set_attributes => true, :database => db
      @dependencies.save_character(inst)
      inst
    end

    # get property as pseudo doc to play with
    def with(key, &code)
      self[key] ||= {}
      case self[key]
      when String # assume we got a doc id
        doc = ThingTank.get(self[key])
        (code.call(doc) ; doc.save) if code
        doc
      when Hash
        doc = self.class.new self[key], :directly_set_attributes => true, :database => FakeBase.new(self, nil)
        @dependencies.add_child(key, doc)
        @dependencies.refresh(false)
        (code.call(doc) ; doc.save) if code
        doc
      when Array
        with_all(key)
      else
        raise "not supported: #{self[key].inspect}"
      end
    end

    def with_all(key, props=nil)
      self[key] ||= []
      props ||= [self[key]].flatten
      docs = props.collect { |prop| self.class.new prop, :directly_set_attributes => true, :database => FakeBase.new(self, nil) }
      @dependencies.add_child(key, docs)
      docs.each { |doc| yield doc ; doc.save } if block_given?
      docs
    end

    def with_nth(key,n)
      self[key] ||= []
      props = [self[key]].flatten
      n = 0 if n == :first
      n = props.size-1 if n == :last
      n = 0 if n < 0
      while n > props.size - 1
        props << {}
      end
      docs = with_all(key, props)
      (yield docs[n] ; docs[n].save) if block_given?
      docs[n]
    end

    def with_first(key,&code)
      with_nth(key,:first, &code)
    end

    def with_last(key,&code)
      with_nth(key,:last, &code)
    end

    def property_to_character(key, klass)
      case self[key]
      when Hash
        with(key).to_character(klass)
      when Array
        with_all(key).collect { |doc| doc.to_character(klass) }
      end
    end

    # register a character
    def register_character(klass)
      self['characters'] ||= []
      self['characters'] << klass.to_s unless self['characters'].include? klass.to_s
    end

    def unregister_character(character)
      if has?(character)
        self["characters"] ||= []
        self["characters"].delete character.to_s
      end
    end

    def add_character(klass, key=nil, &code)
      if key
        key = key.to_s
        if val = self[key]
          self[key] = [val] unless val.is_a? Array
          self[key] << {}
          last_character(klass, key, &code)
        else
          self[key] = {}
          to_character(klass, key, &code)
        end
      else
        to_character(klass, &code)
      end
    end

    def _root
      @dependencies.find_root_doc
    end

    def delete_character(character_doc)
      klass = character_doc.class
      deleteable_attributes(klass).each { |k| delete(k) ; character_doc[k] = nil }
      @dependencies.already_saved(character_doc) # prevent an endless loop
      unregister_character(klass)
      @dependencies.remove_character(klass)
      @dependencies.refresh_parent()
    end

    def deleteable_attributes(klass)
      character_attrs = attributes_by_characters
      character = klass.to_s
      deleteable_attrs = klass.character_properties.select do |prop|
        character_attrs[prop].empty? || (character_attrs[prop].size == 1 && character_attrs[prop].first == character)
      end
      %w| _id _rev type update_me characters|.each{ |k|  deleteable_attrs.delete k }
      return deleteable_attrs
    end

    def attributes_by_characters()
      attributes = {}
      self["characters"].each do |character|
        character.constantize.character_properties.each do |prop|
          attributes[prop] ||= []
          attributes[prop] << character unless attributes[prop].include?(character)
        end
      end
      return attributes
    end

    def to_character(klass, key=nil, add_character=true, &code)
      @dependencies.add_character(klass) if add_character && key.nil?
      character = key ? property_to_character(key, klass) : FakeBase.new(self, klass, add_character).get()
      (code.call(character) ; character.flush_to_doc) if code
      # we don't do this. there is no proper way to update all given characters
      # one must not rely on a character properties after the doc has been manipulated
      # use Character#reload to get the latest from the doc object to the character and Character#reload! to reload the doc from the database and reload the character then
      #@dependencies.add_character_object(character)
      character
    end

    alias :as :to_character
    alias :has :to_character
    alias :act_as :to_character
    alias :be :to_character
    alias :have :to_character
    alias :are :to_character
    alias :is :to_character

    def save_character_attributes(character_doc)
      @dependencies.already_saved(character_doc) # prevent an endless loop
      attributes = character_doc.to_character_hash(true)
      unsavebales = attributes.keys - (character_doc.class.character_properties || [])
      raise "character #{character_doc.class} tried to save properties that it does not have: #{unsavebales.inspect}" unless unsavebales.empty?
      self.update_attributes attributes
      @dependencies.refresh_parent()
    end

    # if the doc when coming from db already had this character
    def had?(klass)
      return false unless self["characters"] and self["characters"].include? klass.name
      return true
    end

    # has the document the character, is it supposed to have it and does it have the necessary properties
    def has?(klass)
      return true if @dependencies.has_character?(klass)
      return false unless self["characters"] and self["characters"].include? klass.name
      could_be? klass
    end
    alias :is? :has?

    # could the document have the character
    def could_have?(klass)
      to_character(klass, nil, false).valid?
    end
    alias :could_be? :could_have?

    # characters that are not valid
    def invalid_characters
      (self["characters"] || []).select { |character| !self.as(character.constantize).valid? }
    end

  end
end

Public Instance Methods

_root() click to toggle source
# File lib/thingtank/character_handling.rb, line 112
def _root
  @dependencies.find_root_doc
end
add_character(klass, key=nil, &code) click to toggle source
# File lib/thingtank/character_handling.rb, line 96
def add_character(klass, key=nil, &code)
  if key
    key = key.to_s
    if val = self[key]
      self[key] = [val] unless val.is_a? Array
      self[key] << {}
      last_character(klass, key, &code)
    else
      self[key] = {}
      to_character(klass, key, &code)
    end
  else
    to_character(klass, &code)
  end
end
attributes_by_characters() click to toggle source
# File lib/thingtank/character_handling.rb, line 135
def attributes_by_characters()
  attributes = {}
  self["characters"].each do |character|
    character.constantize.character_properties.each do |prop|
      attributes[prop] ||= []
      attributes[prop] << character unless attributes[prop].include?(character)
    end
  end
  return attributes
end
could_have?(klass) click to toggle source

could the document have the character

# File lib/thingtank/character_handling.rb, line 189
def could_have?(klass)
  to_character(klass, nil, false).valid?
end
delete_character(character_doc) click to toggle source
# File lib/thingtank/character_handling.rb, line 116
def delete_character(character_doc)
  klass = character_doc.class
  deleteable_attributes(klass).each { |k| delete(k) ; character_doc[k] = nil }
  @dependencies.already_saved(character_doc) # prevent an endless loop
  unregister_character(klass)
  @dependencies.remove_character(klass)
  @dependencies.refresh_parent()
end
deleteable_attributes(klass) click to toggle source
# File lib/thingtank/character_handling.rb, line 125
def deleteable_attributes(klass)
  character_attrs = attributes_by_characters
  character = klass.to_s
  deleteable_attrs = klass.character_properties.select do |prop|
    character_attrs[prop].empty? || (character_attrs[prop].size == 1 && character_attrs[prop].first == character)
  end
  %w| _id _rev type update_me characters|.each{ |k|  deleteable_attrs.delete k }
  return deleteable_attrs
end
get_character(klass, db) click to toggle source
# File lib/thingtank/character_handling.rb, line 14
def get_character(klass, db)
  hsh = properties_of_character(klass)
  hsh.update "_id" => self["_id"], "_rev" => self["_rev"] if (had?(klass) && !hsh.empty?)
  inst = klass.new hsh, :directly_set_attributes => true, :database => db
  @dependencies.save_character(inst)
  inst
end
had?(klass) click to toggle source

if the doc when coming from db already had this character

# File lib/thingtank/character_handling.rb, line 175
def had?(klass)
  return false unless self["characters"] and self["characters"].include? klass.name
  return true
end
has?(klass) click to toggle source

has the document the character, is it supposed to have it and does it have the necessary properties

# File lib/thingtank/character_handling.rb, line 181
def has?(klass)
  return true if @dependencies.has_character?(klass)
  return false unless self["characters"] and self["characters"].include? klass.name
  could_be? klass
end
invalid_characters() click to toggle source

characters that are not valid

# File lib/thingtank/character_handling.rb, line 195
def invalid_characters
  (self["characters"] || []).select { |character| !self.as(character.constantize).valid? }
end
properties_of_character(klass) click to toggle source
# File lib/thingtank/character_handling.rb, line 6
def properties_of_character(klass)
  hsh = {}
  (klass.character_properties || []).each do |k| 
    hsh.update(k.to_s => self[k.to_s])  unless self[k.to_s].nil?
  end
  hsh
end
property_to_character(key, klass) click to toggle source
# File lib/thingtank/character_handling.rb, line 74
def property_to_character(key, klass)
  case self[key]
  when Hash
    with(key).to_character(klass)
  when Array
    with_all(key).collect { |doc| doc.to_character(klass) }
  end
end
register_character(klass) click to toggle source

register a character

# File lib/thingtank/character_handling.rb, line 84
def register_character(klass)
  self['characters'] ||= []
  self['characters'] << klass.to_s unless self['characters'].include? klass.to_s
end
save_character_attributes(character_doc) click to toggle source
# File lib/thingtank/character_handling.rb, line 165
def save_character_attributes(character_doc)
  @dependencies.already_saved(character_doc) # prevent an endless loop
  attributes = character_doc.to_character_hash(true)
  unsavebales = attributes.keys - (character_doc.class.character_properties || [])
  raise "character #{character_doc.class} tried to save properties that it does not have: #{unsavebales.inspect}" unless unsavebales.empty?
  self.update_attributes attributes
  @dependencies.refresh_parent()
end
to_character(klass, key=nil, add_character=true, &code) click to toggle source
# File lib/thingtank/character_handling.rb, line 146
def to_character(klass, key=nil, add_character=true, &code)
  @dependencies.add_character(klass) if add_character && key.nil?
  character = key ? property_to_character(key, klass) : FakeBase.new(self, klass, add_character).get()
  (code.call(character) ; character.flush_to_doc) if code
  # we don't do this. there is no proper way to update all given characters
  # one must not rely on a character properties after the doc has been manipulated
  # use Character#reload to get the latest from the doc object to the character and Character#reload! to reload the doc from the database and reload the character then
  #@dependencies.add_character_object(character)
  character
end
unregister_character(character) click to toggle source
# File lib/thingtank/character_handling.rb, line 89
def unregister_character(character)
  if has?(character)
    self["characters"] ||= []
    self["characters"].delete character.to_s
  end
end
with(key, &code) click to toggle source

get property as pseudo doc to play with

# File lib/thingtank/character_handling.rb, line 23
def with(key, &code)
  self[key] ||= {}
  case self[key]
  when String # assume we got a doc id
    doc = ThingTank.get(self[key])
    (code.call(doc) ; doc.save) if code
    doc
  when Hash
    doc = self.class.new self[key], :directly_set_attributes => true, :database => FakeBase.new(self, nil)
    @dependencies.add_child(key, doc)
    @dependencies.refresh(false)
    (code.call(doc) ; doc.save) if code
    doc
  when Array
    with_all(key)
  else
    raise "not supported: #{self[key].inspect}"
  end
end
with_all(key, props=nil) { |doc ;| ... } click to toggle source
# File lib/thingtank/character_handling.rb, line 43
def with_all(key, props=nil)
  self[key] ||= []
  props ||= [self[key]].flatten
  docs = props.collect { |prop| self.class.new prop, :directly_set_attributes => true, :database => FakeBase.new(self, nil) }
  @dependencies.add_child(key, docs)
  docs.each { |doc| yield doc ; doc.save } if block_given?
  docs
end
with_first(key,&code) click to toggle source
# File lib/thingtank/character_handling.rb, line 66
def with_first(key,&code)
  with_nth(key,:first, &code)
end
with_last(key,&code) click to toggle source
# File lib/thingtank/character_handling.rb, line 70
def with_last(key,&code)
  with_nth(key,:last, &code)
end
with_nth(key,n) { |docs ;| ... } click to toggle source
# File lib/thingtank/character_handling.rb, line 52
def with_nth(key,n)
  self[key] ||= []
  props = [self[key]].flatten
  n = 0 if n == :first
  n = props.size-1 if n == :last
  n = 0 if n < 0
  while n > props.size - 1
    props << {}
  end
  docs = with_all(key, props)
  (yield docs[n] ; docs[n].save) if block_given?
  docs[n]
end