thingtank 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,198 @@
1
+ class ThingTank
2
+ module CharacterHandling
3
+ def self.included(klass)
4
+ klass.class_eval do
5
+
6
+ def properties_of_character(klass)
7
+ hsh = {}
8
+ (klass.character_properties || []).each do |k|
9
+ hsh.update(k.to_s => self[k.to_s]) unless self[k.to_s].nil?
10
+ end
11
+ hsh
12
+ end
13
+
14
+ def get_character(klass, db)
15
+ hsh = properties_of_character(klass)
16
+ hsh.update "_id" => self["_id"], "_rev" => self["_rev"] if (had?(klass) && !hsh.empty?)
17
+ inst = klass.new hsh, :directly_set_attributes => true, :database => db
18
+ @dependencies.save_character(inst)
19
+ inst
20
+ end
21
+
22
+ # get property as pseudo doc to play with
23
+ def with(key, &code)
24
+ self[key] ||= {}
25
+ case self[key]
26
+ when String # assume we got a doc id
27
+ doc = ThingTank.get(self[key])
28
+ (code.call(doc) ; doc.save) if code
29
+ doc
30
+ when Hash
31
+ doc = self.class.new self[key], :directly_set_attributes => true, :database => FakeBase.new(self, nil)
32
+ @dependencies.add_child(key, doc)
33
+ @dependencies.refresh(false)
34
+ (code.call(doc) ; doc.save) if code
35
+ doc
36
+ when Array
37
+ with_all(key)
38
+ else
39
+ raise "not supported: #{self[key].inspect}"
40
+ end
41
+ end
42
+
43
+ def with_all(key, props=nil)
44
+ self[key] ||= []
45
+ props ||= [self[key]].flatten
46
+ docs = props.collect { |prop| self.class.new prop, :directly_set_attributes => true, :database => FakeBase.new(self, nil) }
47
+ @dependencies.add_child(key, docs)
48
+ docs.each { |doc| yield doc ; doc.save } if block_given?
49
+ docs
50
+ end
51
+
52
+ def with_nth(key,n)
53
+ self[key] ||= []
54
+ props = [self[key]].flatten
55
+ n = 0 if n == :first
56
+ n = props.size-1 if n == :last
57
+ n = 0 if n < 0
58
+ while n > props.size - 1
59
+ props << {}
60
+ end
61
+ docs = with_all(key, props)
62
+ (yield docs[n] ; docs[n].save) if block_given?
63
+ docs[n]
64
+ end
65
+
66
+ def with_first(key,&code)
67
+ with_nth(key,:first, &code)
68
+ end
69
+
70
+ def with_last(key,&code)
71
+ with_nth(key,:last, &code)
72
+ end
73
+
74
+ def property_to_character(key, klass)
75
+ case self[key]
76
+ when Hash
77
+ with(key).to_character(klass)
78
+ when Array
79
+ with_all(key).collect { |doc| doc.to_character(klass) }
80
+ end
81
+ end
82
+
83
+ # register a character
84
+ def register_character(klass)
85
+ self['characters'] ||= []
86
+ self['characters'] << klass.to_s unless self['characters'].include? klass.to_s
87
+ end
88
+
89
+ def unregister_character(character)
90
+ if has?(character)
91
+ self["characters"] ||= []
92
+ self["characters"].delete character.to_s
93
+ end
94
+ end
95
+
96
+ def add_character(klass, key=nil, &code)
97
+ if key
98
+ key = key.to_s
99
+ if val = self[key]
100
+ self[key] = [val] unless val.is_a? Array
101
+ self[key] << {}
102
+ last_character(klass, key, &code)
103
+ else
104
+ self[key] = {}
105
+ to_character(klass, key, &code)
106
+ end
107
+ else
108
+ to_character(klass, &code)
109
+ end
110
+ end
111
+
112
+ def _root
113
+ @dependencies.find_root_doc
114
+ end
115
+
116
+ def delete_character(character_doc)
117
+ klass = character_doc.class
118
+ deleteable_attributes(klass).each { |k| delete(k) ; character_doc[k] = nil }
119
+ @dependencies.already_saved(character_doc) # prevent an endless loop
120
+ unregister_character(klass)
121
+ @dependencies.remove_character(klass)
122
+ @dependencies.refresh_parent()
123
+ end
124
+
125
+ def deleteable_attributes(klass)
126
+ character_attrs = attributes_by_characters
127
+ character = klass.to_s
128
+ deleteable_attrs = klass.character_properties.select do |prop|
129
+ character_attrs[prop].empty? || (character_attrs[prop].size == 1 && character_attrs[prop].first == character)
130
+ end
131
+ %w| _id _rev type update_me characters|.each{ |k| deleteable_attrs.delete k }
132
+ return deleteable_attrs
133
+ end
134
+
135
+ def attributes_by_characters()
136
+ attributes = {}
137
+ self["characters"].each do |character|
138
+ character.constantize.character_properties.each do |prop|
139
+ attributes[prop] ||= []
140
+ attributes[prop] << character unless attributes[prop].include?(character)
141
+ end
142
+ end
143
+ return attributes
144
+ end
145
+
146
+ def to_character(klass, key=nil, add_character=true, &code)
147
+ @dependencies.add_character(klass) if add_character && key.nil?
148
+ character = key ? property_to_character(key, klass) : FakeBase.new(self, klass, add_character).get()
149
+ (code.call(character) ; character.flush_to_doc) if code
150
+ character
151
+ end
152
+
153
+ alias :as :to_character
154
+ alias :has :to_character
155
+ alias :act_as :to_character
156
+ alias :be :to_character
157
+ alias :have :to_character
158
+ alias :are :to_character
159
+ alias :is :to_character
160
+
161
+ def save_character_attributes(character_doc)
162
+ @dependencies.already_saved(character_doc) # prevent an endless loop
163
+ attributes = character_doc.to_character_hash(true)
164
+ unsavebales = attributes.keys - (character_doc.class.character_properties || [])
165
+ raise "character #{character_doc.class} tried to save properties that it does not have: #{unsavebales.inspect}" unless unsavebales.empty?
166
+ self.update_attributes attributes
167
+ @dependencies.refresh_parent()
168
+ end
169
+
170
+ # if the doc when coming from db already had this character
171
+ def had?(klass)
172
+ return false unless self["characters"] and self["characters"].include? klass.name
173
+ return true
174
+ end
175
+
176
+ # has the document the character, is it supposed to have it and does it have the necessary properties
177
+ def has?(klass)
178
+ return true if @dependencies.has_character?(klass)
179
+ return false unless self["characters"] and self["characters"].include? klass.name
180
+ could_be? klass
181
+ end
182
+ alias :is? :has?
183
+
184
+ # could the document have the character
185
+ def could_have?(klass)
186
+ to_character(klass, nil, false).valid?
187
+ end
188
+ alias :could_be? :could_have?
189
+
190
+ # characters that are not valid
191
+ def invalid_characters
192
+ (self["characters"] || []).select { |character| !self.as(character.constantize).valid? }
193
+ end
194
+
195
+ end
196
+ end
197
+ end
198
+ end
@@ -1,6 +1,6 @@
1
1
  class ThingTank
2
2
 
3
- # tracks the dependencies of a ThingTank, such as saving of roles, children ThingTanks, registration of roles
3
+ # tracks the dependencies of a ThingTank, such as saving of characters, children ThingTanks, registration of characters
4
4
  class Dependencies
5
5
  def initialize(doc)
6
6
  @doc = doc
@@ -15,49 +15,49 @@ class ThingTank
15
15
  { :registration => @registration, :save => @save, :children => @children }.inspect
16
16
  end
17
17
 
18
- def register_roles
19
- @registration.each { |role| @doc.register_role(role) }
18
+ def register_characters
19
+ @registration.each { |character| @doc.register_character(character) }
20
20
  end
21
21
 
22
22
  def save_all()
23
23
  refresh true
24
- @save.each { |role_instance| role_instance.save }
24
+ @save.each { |character_instance| character_instance.save }
25
25
  end
26
26
 
27
- def add_role(role)
28
- @registration << role
27
+ def add_character(character)
28
+ @registration << character
29
29
  end
30
30
 
31
- def remove_role(role)
32
- @registration.delete(role) if has_role?(role)
31
+ def remove_character(character)
32
+ @registration.delete(character) if has_character?(character)
33
33
  end
34
34
 
35
- def has_role?(klass)
35
+ def has_character?(klass)
36
36
  @registration.include? klass
37
37
  end
38
38
 
39
- def save_role(role_instance)
40
- @save << role_instance unless @save.include? role_instance
39
+ def save_character(character_instance)
40
+ @save << character_instance unless @save.include? character_instance
41
41
  end
42
42
 
43
- def already_saved(role_instance)
44
- @save.delete role_instance
43
+ def already_saved(character_instance)
44
+ @save.delete character_instance
45
45
  end
46
46
 
47
47
  def refresh(save=false, with_parent=true)
48
- register_roles
48
+ register_characters
49
49
  @children.each do |key,child|
50
50
  @doc[key] = case child
51
51
  when Array
52
52
  child.collect do |d|
53
53
  d.dependencies.refresh(save, false)
54
54
  d.save if save
55
- d.to_role_hash
55
+ d.to_character_hash
56
56
  end
57
57
  else
58
58
  child.dependencies.refresh(save, false)
59
59
  child.save if save
60
- child.to_role_hash
60
+ child.to_character_hash
61
61
  end
62
62
  end
63
63
  refresh_parent() if with_parent
@@ -1,11 +1,11 @@
1
- # a replacement database for roles
1
+ # a replacement database for characters
2
2
  class ThingTank::FakeBase
3
3
 
4
- def initialize(doc, role, add_dep = true)
4
+ def initialize(doc, character, add_dep = true)
5
5
  @db = doc.database
6
6
  @doc = doc
7
- @role = role
8
- @doc.dependencies.add_role(role) if add_dep
7
+ @character = character
8
+ @doc.dependencies.add_character(character) if add_dep
9
9
  end
10
10
 
11
11
  def method_missing(meth, *args)
@@ -17,7 +17,7 @@ class ThingTank::FakeBase
17
17
  super
18
18
  end
19
19
 
20
- def to_role(klass)
20
+ def to_character(klass)
21
21
  self.class.new(@doc, klass).get()
22
22
  end
23
23
 
@@ -25,25 +25,25 @@ class ThingTank::FakeBase
25
25
  {"ok" => ok}
26
26
  end
27
27
 
28
- def save_doc(role_doc)
29
- if role_doc.is_a? ThingTank # I dunno why this happens
28
+ def save_doc(character_doc)
29
+ if character_doc.is_a? ThingTank # I dunno why this happens
30
30
  @doc.dependencies.refresh_parent()
31
31
  else
32
- @doc.save_role_attributes(role_doc) if role_doc.changed?
32
+ @doc.save_character_attributes(character_doc) if character_doc.changed?
33
33
  end
34
34
  result
35
35
  end
36
36
 
37
- def delete_doc(*role_docs)
38
- role_docs.each do |role_doc|
39
- # delete only the attributes from me that are no part of another role, since we will have different doc ids
37
+ def delete_doc(*character_docs)
38
+ character_docs.each do |character_doc|
39
+ # delete only the attributes from me that are no part of another character, since we will have different doc ids
40
40
  # for each doc we will need to identify the remaining attributes
41
- doc, save = (role_doc["_id"].nil? || role_doc["_id"] == @doc.id) ?
41
+ doc, save = (character_doc["_id"].nil? || character_doc["_id"] == @doc.id) ?
42
42
  [@doc, false] :
43
- [@db.get(role_doc["_id"]), true]
43
+ [@db.get(character_doc["_id"]), true]
44
44
 
45
- doc.delete_role(role_doc)
46
- doc.save if save # if all roles are from our doc, only save once: at the end
45
+ doc.delete_character(character_doc)
46
+ doc.save if save # if all characters are from our doc, only save once: at the end
47
47
  end
48
48
  @doc.save if @doc.changed?
49
49
  result()
@@ -52,10 +52,10 @@ class ThingTank::FakeBase
52
52
  def get(id=:doc_id)
53
53
  id = @doc.id if id == :doc_id
54
54
  doc = id == @doc.id ? @doc : @db.get(id)
55
- doc.get_role(@role, self)
55
+ doc.get_character(@character, self)
56
56
  end
57
57
 
58
- def _role_doc
58
+ def _character_doc
59
59
  @doc
60
60
  end
61
61
 
@@ -4,8 +4,8 @@ class ThingTank
4
4
  klass.class_eval do
5
5
 
6
6
  # only export the changed properties
7
- def changed_to_role_hash(exclude_roles=false)
8
- skips = skip_keys(exclude_roles)
7
+ def changed_to_character_hash(exclude_characters=false)
8
+ skips = skip_keys(exclude_characters)
9
9
  hsh = {}
10
10
  changes.each do |key,arr|
11
11
  _old, _new = arr
@@ -16,22 +16,22 @@ class ThingTank
16
16
  hsh
17
17
  end
18
18
 
19
- def skip_keys(exclude_roles=false)
19
+ def skip_keys(exclude_characters=false)
20
20
  arr = %w|_id _rev type update_me updated_at created_at|
21
- arr << 'roles' if exclude_roles
21
+ arr << 'characters' if exclude_characters
22
22
  arr
23
23
  end
24
24
 
25
- def to_role_hash(exclude_roles=false)
25
+ def to_character_hash(exclude_characters=false)
26
26
  hsh = to_hash
27
- skip_keys(exclude_roles).each{ |k| hsh.delete k }
27
+ skip_keys(exclude_characters).each{ |k| hsh.delete k }
28
28
  hsh
29
29
  end
30
30
 
31
- def nth_role(klass, key, n, &code)
32
- orig_role = to_role(klass, key)
33
- list = [orig_role].flatten
34
- role = case n
31
+ def nth_character(klass, key, n, &code)
32
+ orig_character = to_character(klass, key)
33
+ list = [orig_character].flatten
34
+ character = case n
35
35
  when :last
36
36
  list.last
37
37
  when :first
@@ -40,21 +40,21 @@ class ThingTank
40
40
  list[n]
41
41
  end
42
42
  if code
43
- code.call(role)
44
- role.flush_to_doc
43
+ code.call(character)
44
+ character.flush_to_doc
45
45
  end
46
- if orig_role.is_a?(Array)
47
- role._role_doc.dependencies.refresh_parent
46
+ if orig_character.is_a?(Array)
47
+ character._character_doc.dependencies.refresh_parent
48
48
  end
49
- role
49
+ character
50
50
  end
51
51
 
52
- def last_role(klass, key, &code)
53
- nth_role(klass, key, :last, &code)
52
+ def last_character(klass, key, &code)
53
+ nth_character(klass, key, :last, &code)
54
54
  end
55
55
 
56
- def first_role(klass, key, &code)
57
- nth_role(klass, key, :first, &code)
56
+ def first_character(klass, key, &code)
57
+ nth_character(klass, key, :first, &code)
58
58
  end
59
59
 
60
60
  end
@@ -6,27 +6,27 @@ class ThingTank
6
6
  def >(klass_or_hash)
7
7
  case klass_or_hash
8
8
  when Array
9
- last_role(klass_or_hash.first, klass_or_hash.last)
9
+ last_character(klass_or_hash.first, klass_or_hash.last)
10
10
  else
11
- to_role(klass_or_hash)
11
+ to_character(klass_or_hash)
12
12
  end
13
13
  end
14
14
 
15
15
  def <(klass_or_hash)
16
16
  case klass_or_hash
17
17
  when Array
18
- first_role(klass_or_hash.first, klass_or_hash.last)
18
+ first_character(klass_or_hash.first, klass_or_hash.last)
19
19
  else
20
- to_role(klass_or_hash)
20
+ to_character(klass_or_hash)
21
21
  end
22
22
  end
23
23
 
24
24
  def << (arr)
25
25
  klass, key, code= arr.flatten
26
26
  if code
27
- add_role(klass, key, &code)
27
+ add_character(klass, key, &code)
28
28
  else
29
- add_role(klass, key)
29
+ add_character(klass, key)
30
30
  end
31
31
  end
32
32
 
@@ -7,12 +7,12 @@ class ThingTank < CouchRest::Model::Base
7
7
  mass_assign_any_attribute true # should always allow to set non defined arbitrary properties
8
8
  timestamps!
9
9
 
10
- def role_view_by(klass, *keys)
10
+ def character_view_by(klass, *keys)
11
11
  opts = keys.pop if keys.last.is_a?(Hash)
12
12
  opts ||= {}
13
13
  opts[:guards] ||= []
14
14
  # there is no "inArray" like function in couchdb, see http://stackoverflow.com/questions/3740464/i-have-to-write-every-function-i-need-for-couchdb
15
- opts[:guards] << "((doc['roles'] !== undefined) && (function (item,arr) { for(p=0;p<arr.length;p++) if (item == arr[p]) return true; return false;})('#{klass.to_s}',doc['roles']))"
15
+ opts[:guards] << "((doc['characters'] !== undefined) && (function (item,arr) { for(p=0;p<arr.length;p++) if (item == arr[p]) return true; return false;})('#{klass.to_s}',doc['characters']))"
16
16
  keys << opts
17
17
  view_by(*keys)
18
18
  end
data/lib/thingtank.rb CHANGED
@@ -2,6 +2,11 @@ if RUBY_VERSION =~ /1.8/
2
2
  require 'backports'
3
3
  end
4
4
 
5
+ # TODO
6
+ # - check if we could get useful inspiration from https://github.com/givmo/couch_record
7
+ # - improve views
8
+ # - validation for characters
9
+
5
10
  require_relative File.join('thingtank', 'thingtank.rb')
6
11
  require_relative File.join('couchrest', 'extensions', 'view.rb')
7
12
  require_relative File.join('thingtank', 'dependencies.rb')
@@ -10,19 +15,19 @@ require_relative File.join('thingtank', 'force_update.rb')
10
15
  require_relative File.join('thingtank', 'shared_methods.rb')
11
16
  require_relative File.join('thingtank', 'instance_methods.rb')
12
17
  require_relative File.join('thingtank', 'shortcuts.rb')
13
- require_relative File.join('thingtank', 'role_handling.rb')
14
- require_relative File.join('thingtank', 'role.rb')
18
+ require_relative File.join('thingtank', 'character_handling.rb')
19
+ require_relative File.join('thingtank', 'character.rb')
15
20
  require_relative File.join('thingtank', 'fakebase.rb')
16
21
 
17
22
  # TODO:
18
- # role validator
23
+ # character validator
19
24
 
20
25
  class ThingTank
21
26
 
22
27
  include ThingTank::ForceUpdate
23
28
  include ThingTank::SharedMethods
24
29
  include ThingTank::InstanceMethods
25
- include ThingTank::RoleHandling
30
+ include ThingTank::CharacterHandling
26
31
  include ThingTank::Shortcuts
27
32
  include ThingTank::Callbacks
28
33