thingtank 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +20 -0
- data/README.md +329 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/examples/bear_julius.rb +50 -0
- data/examples/first_marriage.rb +81 -0
- data/examples/immortal_julius.rb +25 -0
- data/examples/marriage_improvement.rb +120 -0
- data/examples/second_marriage.rb +78 -0
- data/lib/couchrest/extensions/view.rb +17 -0
- data/lib/thingtank/callbacks.rb +32 -0
- data/lib/thingtank/dependencies.rb +94 -0
- data/lib/thingtank/fakebase.rb +66 -0
- data/lib/thingtank/force_update.rb +24 -0
- data/lib/thingtank/instance_methods.rb +45 -0
- data/lib/thingtank/role.rb +110 -0
- data/lib/thingtank/role_handling.rb +198 -0
- data/lib/thingtank/shared_methods.rb +63 -0
- data/lib/thingtank/shortcuts.rb +36 -0
- data/lib/thingtank/thingtank.rb +21 -0
- data/lib/thingtank.rb +27 -0
- data/test/examples/test_bear_julius.rb +15 -0
- data/test/examples/test_first_marriage.rb +20 -0
- data/test/examples/test_immortal_julius.rb +16 -0
- data/test/examples/test_marriage_improvement.rb +22 -0
- data/test/examples/test_second_marriage.rb +18 -0
- data/test/helper.rb +118 -0
- data/test/test_fakebase.rb +389 -0
- data/test/test_thingtank.rb +282 -0
- data/test/test_views.rb +80 -0
- metadata +172 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'marriage_improvement.rb'
|
2
|
+
|
3
|
+
class Dead
|
4
|
+
|
5
|
+
# all callbacks of roles are called and defined like corresponding callbacks of the doc
|
6
|
+
before_save do
|
7
|
+
if _doc.is?(Spouse) && _doc['married_state'] == 'married'
|
8
|
+
Spouse.get(_doc.last_role(Married, 'married').spouse).widowed(self["date_of_death"])
|
9
|
+
end
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class Person
|
16
|
+
def dies(date)
|
17
|
+
_doc.is(Dead) do |d|
|
18
|
+
d.date_of_death = date
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Married
|
24
|
+
def widow(date)
|
25
|
+
self["state"] = 'widowed'
|
26
|
+
self['end'] = date
|
27
|
+
_doc.save
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Spouse
|
32
|
+
def widowed(date)
|
33
|
+
self["married_state"] = 'widowed'
|
34
|
+
married { |m| m.widow(date) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def test_second_marriage()
|
40
|
+
julius = test_improved_marriage()
|
41
|
+
julius.as(Person).marry "68-65 BC", :gender => "f", :name => 'Pompeia'
|
42
|
+
|
43
|
+
assert_equal 2, julius["married"].size
|
44
|
+
assert_equal 'Cornelia', Person.get(julius["married"].first["spouse"]).name
|
45
|
+
assert_equal 'Pompeia', Person.get(julius["married"].last["spouse"]).name
|
46
|
+
assert_equal 'Pompeia', julius.has(Spouse).name
|
47
|
+
assert_equal 'married', julius["married"].first["state"]
|
48
|
+
assert_equal 'married', julius["married"].last["state"]
|
49
|
+
|
50
|
+
return julius
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_second_marriage_after_conny_died()
|
54
|
+
julius = test_bear_julius()
|
55
|
+
conny = create(:gender => "f", :name => 'Cornelia')
|
56
|
+
conny.is(Person)
|
57
|
+
julius.as(Person).marry "84 BC", conny
|
58
|
+
julius.save
|
59
|
+
julius.reload
|
60
|
+
|
61
|
+
conny = load(julius["married"]["spouse"])
|
62
|
+
conny.save
|
63
|
+
conny.reload
|
64
|
+
conny.as(Person).dies "68-65 BC"
|
65
|
+
conny.save
|
66
|
+
|
67
|
+
julius.reload
|
68
|
+
|
69
|
+
julius.as(Person).marry "68-65 BC", :gender => "f", :name => 'Pompeia'
|
70
|
+
|
71
|
+
assert_equal 2, julius["married"].size
|
72
|
+
assert_equal 'Cornelia', Person.get(julius["married"].first["spouse"]).name
|
73
|
+
assert_equal 'widowed', julius["married"].first["state"]
|
74
|
+
assert_equal 'Pompeia', Person.get(julius["married"].last["spouse"]).name
|
75
|
+
assert_equal 'married', julius["married"].last["state"]
|
76
|
+
assert_equal 'Pompeia', julius.has(Spouse).name
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# https://github.com/couchrest/couchrest_model/blob/master/lib/couchrest/model/designs/view.rb overwritten
|
2
|
+
|
3
|
+
class CouchRest::Model::Designs::DesignMapper
|
4
|
+
|
5
|
+
# generate a view to show only ThingTanks of a certain role, define them all in a ThingTank subclass (not in a role)
|
6
|
+
def role_view(klass, name, opts={})
|
7
|
+
name = "#{klass.to_s.downcase}_#{name}"
|
8
|
+
opts ||= {}
|
9
|
+
opts[:guards] ||= []
|
10
|
+
# there is no "inArray" like function in couchdb, see http://stackoverflow.com/questions/3740464/i-have-to-write-every-function-i-need-for-couchdb
|
11
|
+
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']))"
|
12
|
+
view(name, opts)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class ThingTank
|
2
|
+
module Callbacks
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
|
6
|
+
before_validation do
|
7
|
+
# update_attributes seems to be intentionally broken for mass_assign_any_attribute, see
|
8
|
+
# http://groups.google.com/group/couchrest/browse_thread/thread/3b6aeb6469a0ea35?pli=1
|
9
|
+
# try to fix it be changing a property (ugly hack), also see https://github.com/couchrest/couchrest_model/issues/114
|
10
|
+
#disable_dirty #true
|
11
|
+
couchrest_attribute_will_change!('update_me') # or update_me_will_change!
|
12
|
+
end
|
13
|
+
|
14
|
+
before_save do
|
15
|
+
@dependencies.save_all()
|
16
|
+
end
|
17
|
+
|
18
|
+
# mimic the destroy_document method from https://github.com/langalex/couch_potato/blob/master/lib/couch_potato/database.rb
|
19
|
+
before_destroy do
|
20
|
+
ok = true
|
21
|
+
(self["roles"] || []).each do |klass|
|
22
|
+
document = self.as(klass.constantize)
|
23
|
+
(ok = false) if false == document.run_callbacks(:destroy) do
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
ok
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class ThingTank
|
2
|
+
|
3
|
+
# tracks the dependencies of a ThingTank, such as saving of roles, children ThingTanks, registration of roles
|
4
|
+
class Dependencies
|
5
|
+
def initialize(doc)
|
6
|
+
@doc = doc
|
7
|
+
@registration = []
|
8
|
+
@save = []
|
9
|
+
@children = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :parent
|
13
|
+
|
14
|
+
def register_roles
|
15
|
+
@registration.each { |role| @doc.register_role(role) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def save_all()
|
19
|
+
refresh true
|
20
|
+
@save.each { |role_instance| role_instance.save }
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_role(role)
|
24
|
+
@registration << role
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove_role(role)
|
28
|
+
@registration.delete(role) if has_role?(role)
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_role?(klass)
|
32
|
+
@registration.include? klass
|
33
|
+
end
|
34
|
+
|
35
|
+
def save_role(role_instance)
|
36
|
+
@save << role_instance unless @save.include? role_instance
|
37
|
+
end
|
38
|
+
|
39
|
+
def already_saved(role_instance)
|
40
|
+
@save.delete role_instance
|
41
|
+
end
|
42
|
+
|
43
|
+
def refresh(save=false, with_parent=true)
|
44
|
+
register_roles
|
45
|
+
@children.each do |key,child|
|
46
|
+
@doc[key] = case child
|
47
|
+
when Array
|
48
|
+
child.collect do |d|
|
49
|
+
d.dependencies.refresh(save, false)
|
50
|
+
d.save if save
|
51
|
+
d.to_role_hash
|
52
|
+
end
|
53
|
+
else
|
54
|
+
child.dependencies.refresh(save, false)
|
55
|
+
child.save if save
|
56
|
+
child.to_role_hash
|
57
|
+
end
|
58
|
+
end
|
59
|
+
refresh_parent() if with_parent
|
60
|
+
end
|
61
|
+
|
62
|
+
def refresh_parent()
|
63
|
+
@parent.dependencies.refresh if @parent
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_child(key,child)
|
67
|
+
raise "something wrong here #{key} #{child.inspect}" unless child.is_a?(ThingTank) || child.is_a?(Array)
|
68
|
+
if child.is_a? Array
|
69
|
+
child.each do |c|
|
70
|
+
c.dependencies.set_parent(@doc)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
child.dependencies.set_parent(@doc)
|
74
|
+
end
|
75
|
+
@children[key] = child
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_root_doc()
|
79
|
+
return @doc unless @parent
|
80
|
+
_d = @doc
|
81
|
+
doc = _d
|
82
|
+
while doc.dependencies.parent do
|
83
|
+
doc = doc.dependencies.parent
|
84
|
+
end
|
85
|
+
return doc
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def set_parent(parent)
|
91
|
+
@parent = parent
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# a replacement database for roles
|
2
|
+
class ThingTank::FakeBase
|
3
|
+
|
4
|
+
def initialize(doc, role)
|
5
|
+
@db = doc.database
|
6
|
+
@doc = doc
|
7
|
+
@role = role
|
8
|
+
@doc.dependencies.add_role(role)
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(meth, *args)
|
12
|
+
raise "don't call #{self.class}: #{meth.inspect}(#{args.inspect})"
|
13
|
+
end
|
14
|
+
|
15
|
+
def is_a?(klass)
|
16
|
+
return true if klass == CouchRest::Database
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_role(klass)
|
21
|
+
self.class.new(@doc, klass).get()
|
22
|
+
end
|
23
|
+
|
24
|
+
def result(ok=true)
|
25
|
+
{"ok" => ok}
|
26
|
+
end
|
27
|
+
|
28
|
+
def save_doc(role_doc)
|
29
|
+
if role_doc.is_a? ThingTank # I dunno why this happens
|
30
|
+
@doc.dependencies.refresh_parent()
|
31
|
+
else
|
32
|
+
@doc.save_role_attributes(role_doc) if role_doc.changed?
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
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
|
40
|
+
# for each doc we will need to identify the remaining attributes
|
41
|
+
doc, save = (role_doc["_id"].nil? || role_doc["_id"] == @doc.id) ?
|
42
|
+
[@doc, false] :
|
43
|
+
[@db.get(role_doc["_id"]), true]
|
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
|
47
|
+
end
|
48
|
+
@doc.save if @doc.changed?
|
49
|
+
result()
|
50
|
+
end
|
51
|
+
|
52
|
+
def get(id=:doc_id)
|
53
|
+
id = @doc.id if id == :doc_id
|
54
|
+
doc = id == @doc.id ? @doc : @db.get(id)
|
55
|
+
doc.get_role(@role, self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def _role_doc
|
59
|
+
@doc
|
60
|
+
end
|
61
|
+
|
62
|
+
def _doc
|
63
|
+
@doc._root
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class ThingTank
|
2
|
+
|
3
|
+
# update_attributes seems to be broken for mass_assign_any_attribute, see
|
4
|
+
# http://groups.google.com/group/couchrest/browse_thread/thread/3b6aeb6469a0ea35?pli=1
|
5
|
+
# try to fix it be changing a property (ugly hack), also see https://github.com/couchrest/couchrest_model/issues/114
|
6
|
+
module ForceUpdate
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
|
10
|
+
base.class_eval do
|
11
|
+
|
12
|
+
property :update_me
|
13
|
+
|
14
|
+
before_validation do
|
15
|
+
couchrest_attribute_will_change!('update_me') # or update_me_will_change! will also do
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class ThingTank
|
2
|
+
|
3
|
+
module InstanceMethods
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
|
7
|
+
base.class_eval do
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
super
|
11
|
+
@dependencies = ThingTank::Dependencies.new(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def dependencies
|
15
|
+
@dependencies
|
16
|
+
end
|
17
|
+
|
18
|
+
def first(key)
|
19
|
+
[self[key.to_s]].flatten.last
|
20
|
+
end
|
21
|
+
|
22
|
+
def last(key)
|
23
|
+
[self[key.to_s]].flatten.first
|
24
|
+
end
|
25
|
+
|
26
|
+
# export a property to its own document and replaces the original reference with the doc["_id"]
|
27
|
+
def export(property)
|
28
|
+
raise ArgumentError, "doc.database required for extracting" unless database
|
29
|
+
new_doc = self[property.to_s]
|
30
|
+
raise ArgumentError, "#{new_doc.inspect} is no CouchRest::Document or Hash" unless new_doc.is_a?(CouchRest::Document) or new_doc.is_a?(Hash)
|
31
|
+
result = database.save_doc new_doc
|
32
|
+
if result['ok']
|
33
|
+
self["#{property}_id"] = result["id"]
|
34
|
+
self[property.to_s] = nil
|
35
|
+
end
|
36
|
+
result['ok']
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
|
2
|
+
class ThingTank
|
3
|
+
|
4
|
+
class Role < CouchRest::Model::Base
|
5
|
+
|
6
|
+
include ThingTank::ForceUpdate
|
7
|
+
include ThingTank::SharedMethods
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def property(name, *args)
|
12
|
+
@role_properties ||= []
|
13
|
+
@role_properties << name.to_s
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def role_properties
|
18
|
+
@role_properties
|
19
|
+
end
|
20
|
+
|
21
|
+
def -(key)
|
22
|
+
[self,key]
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(id, db = database)
|
26
|
+
doc = ThingTank.get(id)
|
27
|
+
return nil if doc.nil?
|
28
|
+
return doc.to_role(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get!(id, db = database)
|
32
|
+
doc = ThingTank.get!(id)
|
33
|
+
doc.to_role(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
def wants(*modules)
|
37
|
+
@wishes ||= []
|
38
|
+
@wishes = @wishes.concat modules
|
39
|
+
end
|
40
|
+
|
41
|
+
def wishes
|
42
|
+
@wishes
|
43
|
+
end
|
44
|
+
|
45
|
+
def design
|
46
|
+
raise "design is not supported in ThingTank::Role, please use the 'role_view' method in a ThingTank subclass design definition"
|
47
|
+
end
|
48
|
+
|
49
|
+
def view_by(*args)
|
50
|
+
raise "view_by is not supported in ThingTank::Role, please use the 'role_view_by' method in a ThingTank subclass"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def -(key)
|
56
|
+
[self,key]
|
57
|
+
end
|
58
|
+
|
59
|
+
def _doc
|
60
|
+
if database.is_a?(FakeBase)
|
61
|
+
database._doc
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# the virtual _doc that contains me, you should not need it normally
|
68
|
+
def _role_doc
|
69
|
+
if database.is_a?(FakeBase)
|
70
|
+
database._role_doc
|
71
|
+
else
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def flush_to_doc
|
77
|
+
if changed?
|
78
|
+
changed_to_role_hash().each do |k,v|
|
79
|
+
_role_doc[k] = v
|
80
|
+
end
|
81
|
+
_role_doc.save
|
82
|
+
@changed_attributes.clear
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def reload
|
87
|
+
attrs = _role_doc.as(self.class).to_role_hash
|
88
|
+
prepare_all_attributes(attrs, :directly_set_attributes => true)
|
89
|
+
@changed_attributes.clear
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_role(klass, key, &code)
|
94
|
+
_role_doc.to_role(klass, key, &code)
|
95
|
+
end
|
96
|
+
|
97
|
+
def first(key)
|
98
|
+
_role_doc.first(key)
|
99
|
+
end
|
100
|
+
|
101
|
+
def last(key)
|
102
|
+
_role_doc.last(key)
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_role(klass, key=nil, &code)
|
106
|
+
_role_doc.add_role(klass, key, &code)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
class ThingTank
|
2
|
+
module RoleHandling
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
|
6
|
+
def properties_of_role(klass)
|
7
|
+
hsh = {}
|
8
|
+
(klass.role_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_role(klass, db)
|
15
|
+
hsh = properties_of_role(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_role(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_role(key, klass)
|
75
|
+
case self[key]
|
76
|
+
when Hash
|
77
|
+
with(key).to_role(klass)
|
78
|
+
when Array
|
79
|
+
with_all(key).collect { |doc| doc.to_role(klass) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# register a role
|
84
|
+
def register_role(klass)
|
85
|
+
self['roles'] ||= []
|
86
|
+
self['roles'] << klass.to_s unless self['roles'].include? klass.to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
def unregister_role(role)
|
90
|
+
if has?(role)
|
91
|
+
self["roles"] ||= []
|
92
|
+
self["roles"].delete role.to_s
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_role(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_role(klass, key, &code)
|
103
|
+
else
|
104
|
+
self[key] = {}
|
105
|
+
to_role(klass, key, &code)
|
106
|
+
end
|
107
|
+
else
|
108
|
+
to_role(klass, &code)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def _root
|
113
|
+
@dependencies.find_root_doc
|
114
|
+
end
|
115
|
+
|
116
|
+
def delete_role(role_doc)
|
117
|
+
klass = role_doc.class
|
118
|
+
deleteable_attributes(klass).each { |k| delete(k) ; role_doc[k] = nil }
|
119
|
+
@dependencies.already_saved(role_doc) # prevent an endless loop
|
120
|
+
unregister_role(klass)
|
121
|
+
@dependencies.remove_role(klass)
|
122
|
+
@dependencies.refresh_parent()
|
123
|
+
end
|
124
|
+
|
125
|
+
def deleteable_attributes(klass)
|
126
|
+
role_attrs = attributes_by_roles
|
127
|
+
role = klass.to_s
|
128
|
+
deleteable_attrs = klass.role_properties.select do |prop|
|
129
|
+
role_attrs[prop].empty? || (role_attrs[prop].size == 1 && role_attrs[prop].first == role)
|
130
|
+
end
|
131
|
+
%w| _id _rev type update_me roles|.each{ |k| deleteable_attrs.delete k }
|
132
|
+
return deleteable_attrs
|
133
|
+
end
|
134
|
+
|
135
|
+
def attributes_by_roles()
|
136
|
+
attributes = {}
|
137
|
+
self["roles"].each do |role|
|
138
|
+
role.constantize.role_properties.each do |prop|
|
139
|
+
attributes[prop] ||= []
|
140
|
+
attributes[prop] << role
|
141
|
+
end
|
142
|
+
end
|
143
|
+
return attributes
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_role(klass, key=nil, add_role=true, &code)
|
147
|
+
@dependencies.add_role(klass) if add_role && key.nil?
|
148
|
+
role = key ? property_to_role(key, klass) : FakeBase.new(self, klass).get()
|
149
|
+
(code.call(role) ; role.flush_to_doc) if code
|
150
|
+
role
|
151
|
+
end
|
152
|
+
|
153
|
+
alias :as :to_role
|
154
|
+
alias :has :to_role
|
155
|
+
alias :act_as :to_role
|
156
|
+
alias :be :to_role
|
157
|
+
alias :have :to_role
|
158
|
+
alias :are :to_role
|
159
|
+
alias :is :to_role
|
160
|
+
|
161
|
+
def save_role_attributes(role_doc)
|
162
|
+
@dependencies.already_saved(role_doc) # prevent an endless loop
|
163
|
+
attributes = role_doc.to_role_hash(true)
|
164
|
+
unsavebales = attributes.keys - (role_doc.class.role_properties || [])
|
165
|
+
raise "role #{role_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 role
|
171
|
+
def had?(klass)
|
172
|
+
return false unless self["roles"] and self["roles"].include? klass.name
|
173
|
+
return true
|
174
|
+
end
|
175
|
+
|
176
|
+
# has the document the role, is it supposed to have it and does it have the necessary properties
|
177
|
+
def has?(klass)
|
178
|
+
return true if @dependencies.has_role?(klass)
|
179
|
+
return false unless self["roles"] and self["roles"].include? klass.name
|
180
|
+
could_be? klass
|
181
|
+
end
|
182
|
+
alias :is? :has?
|
183
|
+
|
184
|
+
# could the document have the role
|
185
|
+
def could_have?(klass)
|
186
|
+
to_role(klass, nil, false).valid?
|
187
|
+
end
|
188
|
+
alias :could_be? :could_have?
|
189
|
+
|
190
|
+
# roles that are not valid
|
191
|
+
def invalid_roles
|
192
|
+
(self["roles"] || []).select { |role| !self.as(role.constantize).valid? }
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|