thingtank 0.0.1
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.
- 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
|