thingtank 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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