Module: ThingTank::CharacterHandling

Included in:
ThingTank
Defined in:
lib/thingtank/character_handling.rb

Class Method Summary collapse

Class Method Details

.included(klass) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# 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