nobrainer 0.16.0 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/no_brainer/config.rb +3 -2
- data/lib/no_brainer/connection_manager.rb +60 -0
- data/lib/no_brainer/document.rb +1 -1
- data/lib/no_brainer/document/aliases.rb +1 -1
- data/lib/no_brainer/document/atomic_ops.rb +205 -0
- data/lib/no_brainer/document/attributes.rb +10 -2
- data/lib/no_brainer/document/callbacks.rb +1 -1
- data/lib/no_brainer/document/dirty.rb +21 -19
- data/lib/no_brainer/document/dynamic_attributes.rb +2 -14
- data/lib/no_brainer/document/id.rb +1 -0
- data/lib/no_brainer/document/index.rb +21 -14
- data/lib/no_brainer/document/missing_attributes.rb +6 -23
- data/lib/no_brainer/document/persistance.rb +16 -19
- data/lib/no_brainer/document/types.rb +2 -2
- data/lib/no_brainer/document/types/set.rb +23 -0
- data/lib/no_brainer/error.rb +14 -0
- data/lib/no_brainer/query_runner/connection_lock.rb +5 -1
- data/lib/nobrainer.rb +4 -20
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6cf2f24165387c78884eaffc90c04da535bc571f
|
4
|
+
data.tar.gz: b8d3b1bd887c454e3f017664a559c70bec9a6312
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa00190b18bbd8a498c855ef04fe7720c169c51edbb0d360939dc2ea5c4b76db47361615bc86153207e22be8096946bdce120c35d3b3da6fe99a9db4a4b58f94
|
7
|
+
data.tar.gz: 034d21dabdce9c900b11c899e20d5e69af34560ec2980bbfdba386974bc30828ac6fb9d590fc73a4ac9b568dbfdbdb9a9fb60760c4aeceac963396fd8128e679
|
data/lib/no_brainer/config.rb
CHANGED
@@ -6,7 +6,7 @@ module NoBrainer::Config
|
|
6
6
|
:auto_create_databases, :auto_create_tables,
|
7
7
|
:max_reconnection_tries, :durability,
|
8
8
|
:user_timezone, :db_timezone, :colorize_logger,
|
9
|
-
:distributed_lock_class
|
9
|
+
:distributed_lock_class, :per_thread_connection
|
10
10
|
|
11
11
|
def apply_defaults
|
12
12
|
self.rethinkdb_url = default_rethinkdb_url
|
@@ -20,6 +20,7 @@ module NoBrainer::Config
|
|
20
20
|
self.db_timezone = :utc
|
21
21
|
self.colorize_logger = true
|
22
22
|
self.distributed_lock_class = nil
|
23
|
+
self.per_thread_connection = false
|
23
24
|
end
|
24
25
|
|
25
26
|
def reset!
|
@@ -33,7 +34,7 @@ module NoBrainer::Config
|
|
33
34
|
assert_valid_options!
|
34
35
|
@configured = true
|
35
36
|
|
36
|
-
NoBrainer.disconnect_if_url_changed
|
37
|
+
NoBrainer::ConnectionManager.disconnect_if_url_changed
|
37
38
|
end
|
38
39
|
|
39
40
|
def configured?
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module NoBrainer::ConnectionManager
|
2
|
+
extend self
|
3
|
+
|
4
|
+
@lock = Mutex.new
|
5
|
+
|
6
|
+
def synchronize(&block)
|
7
|
+
if NoBrainer::Config.per_thread_connection
|
8
|
+
block.call
|
9
|
+
else
|
10
|
+
@lock.synchronize { block.call }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_new_connection
|
15
|
+
url = NoBrainer::Config.rethinkdb_url
|
16
|
+
raise "Please specify a database connection to RethinkDB" unless url
|
17
|
+
NoBrainer::Connection.new(url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_connection
|
21
|
+
if NoBrainer::Config.per_thread_connection
|
22
|
+
Thread.current[:nobrainer_connection]
|
23
|
+
else
|
24
|
+
@connection
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def current_connection=(value)
|
29
|
+
if NoBrainer::Config.per_thread_connection
|
30
|
+
Thread.current[:nobrainer_connection] = value
|
31
|
+
else
|
32
|
+
@connection = value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def connection
|
37
|
+
c = self.current_connection
|
38
|
+
return c if c
|
39
|
+
|
40
|
+
synchronize do
|
41
|
+
self.current_connection ||= get_new_connection
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def _disconnect
|
46
|
+
self.current_connection.try(:disconnect, :noreply_wait => true) rescue nil
|
47
|
+
self.current_connection = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def disconnect
|
51
|
+
synchronize { _disconnect }
|
52
|
+
end
|
53
|
+
|
54
|
+
def disconnect_if_url_changed
|
55
|
+
synchronize do
|
56
|
+
c = current_connection
|
57
|
+
_disconnect if c && c.uri != NoBrainer::Config.rethinkdb_url
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/no_brainer/document.rb
CHANGED
@@ -7,7 +7,7 @@ module NoBrainer::Document
|
|
7
7
|
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly,
|
8
8
|
:Validation, :Persistance, :Types, :Uniqueness, :Callbacks, :Dirty, :Id,
|
9
9
|
:Association, :Serialization, :Criteria, :Polymorphic, :Index, :Aliases,
|
10
|
-
:MissingAttributes, :LazyFetch
|
10
|
+
:MissingAttributes, :LazyFetch, :AtomicOps
|
11
11
|
|
12
12
|
autoload :DynamicAttributes, :Timestamps
|
13
13
|
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module NoBrainer::Document::AtomicOps
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class PendingAtomic
|
5
|
+
def self._new(instance, field, user_value, options={})
|
6
|
+
klass = case user_value
|
7
|
+
when Array then PendingAtomicArray
|
8
|
+
when Set then PendingAtomicSet
|
9
|
+
else self
|
10
|
+
end
|
11
|
+
klass.new(instance, field, user_value, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(instance, field, user_value, options={})
|
15
|
+
@instance = instance
|
16
|
+
@field = field
|
17
|
+
@user_value = user_value
|
18
|
+
@value_tainted = instance._is_attribute_tainted?(field)
|
19
|
+
@options = options
|
20
|
+
@ops = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_access?
|
24
|
+
@options[:write_access] == true
|
25
|
+
end
|
26
|
+
|
27
|
+
def ensure_writeable!
|
28
|
+
unless write_access?
|
29
|
+
@options[:write_access] = true
|
30
|
+
@instance.write_attribute(@field, self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
"<#{@field} with pending atomic operations>"
|
36
|
+
end
|
37
|
+
alias inspect to_s
|
38
|
+
|
39
|
+
def method_missing(method_name, *a, &b)
|
40
|
+
@ops << [method_name, a, b]
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def compile_rql_value(rql_doc)
|
45
|
+
field = @instance.class.lookup_field_alias(@field)
|
46
|
+
value = @value_tainted ? RethinkDB::RQL.new.expr(@user_value) : rql_doc[field]
|
47
|
+
@ops.each { |method_name, a, b| value = value.__send__(method_name, *a, &b) }
|
48
|
+
value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class PendingAtomicArray < PendingAtomic
|
53
|
+
def -(value)
|
54
|
+
@ops << [:difference, [value.to_a]]
|
55
|
+
self
|
56
|
+
end
|
57
|
+
def difference(v); self - v; end
|
58
|
+
|
59
|
+
def delete(value)
|
60
|
+
difference([value])
|
61
|
+
end
|
62
|
+
|
63
|
+
def +(value)
|
64
|
+
@ops << [:+, [value.to_a]]
|
65
|
+
self
|
66
|
+
end
|
67
|
+
def add(v); self + v; end
|
68
|
+
|
69
|
+
def &(value)
|
70
|
+
@ops << [:set_intersection, [value.to_a]]
|
71
|
+
self
|
72
|
+
end
|
73
|
+
def intersection(v); self & v; end
|
74
|
+
|
75
|
+
def |(value)
|
76
|
+
@ops << [:set_union, [value.to_a]]
|
77
|
+
self
|
78
|
+
end
|
79
|
+
def union(v); self | v; end
|
80
|
+
|
81
|
+
def <<(value)
|
82
|
+
@ops << [:append, [value]]
|
83
|
+
ensure_writeable!
|
84
|
+
self
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class PendingAtomicSet < PendingAtomicArray
|
89
|
+
def -(value)
|
90
|
+
@ops << [:set_difference, [value.to_a]]
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
def +(value)
|
95
|
+
@ops << [:set_union, [value.to_a]]
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def <<(value)
|
100
|
+
@ops << [:set_union, [[value]]]
|
101
|
+
ensure_writeable!
|
102
|
+
self
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def clear_dirtiness(options={})
|
107
|
+
super
|
108
|
+
@_tainted_attributes = Set.new
|
109
|
+
end
|
110
|
+
|
111
|
+
def _taint_attribute(name)
|
112
|
+
@_tainted_attributes << name
|
113
|
+
end
|
114
|
+
|
115
|
+
def _is_attribute_tainted?(name)
|
116
|
+
@_tainted_attributes.include?(name)
|
117
|
+
end
|
118
|
+
|
119
|
+
def in_atomic?
|
120
|
+
!!Thread.current[:nobrainer_atomic]
|
121
|
+
end
|
122
|
+
|
123
|
+
def in_other_atomic?
|
124
|
+
v = Thread.current[:nobrainer_atomic]
|
125
|
+
!v.nil? && !v.equal?(self)
|
126
|
+
end
|
127
|
+
|
128
|
+
def ensure_exclusive_atomic!
|
129
|
+
raise NoBrainer::Error::AtomicBlock.new('You may not access other documents within an atomic block') if in_other_atomic?
|
130
|
+
end
|
131
|
+
|
132
|
+
def queue_atomic(&block)
|
133
|
+
ensure_exclusive_atomic!
|
134
|
+
|
135
|
+
begin
|
136
|
+
old_atomic, Thread.current[:nobrainer_atomic] = Thread.current[:nobrainer_atomic], self
|
137
|
+
block.call(RethinkDB::RQL.new)
|
138
|
+
ensure
|
139
|
+
Thread.current[:nobrainer_atomic] = old_atomic
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def _read_attribute(name)
|
144
|
+
ensure_exclusive_atomic!
|
145
|
+
value = super
|
146
|
+
|
147
|
+
case [in_atomic?, value.is_a?(PendingAtomic)]
|
148
|
+
when [true, true] then value
|
149
|
+
when [true, false] then PendingAtomic._new(self, name.to_s, value, :write_access => false)
|
150
|
+
when [false, true] then raise NoBrainer::Error::CannotReadAtomic.new(self, name, value)
|
151
|
+
when [false, false] then value
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def _write_attribute(name, value)
|
156
|
+
ensure_exclusive_atomic!
|
157
|
+
|
158
|
+
case [in_atomic?, value.is_a?(PendingAtomic)]
|
159
|
+
when [true, true] then super
|
160
|
+
when [true, false] then raise NoBrainer::Error::AtomicBlock.new('Avoid the use of atomic blocks for non atomic operations')
|
161
|
+
when [false, true] then raise NoBrainer::Error::AtomicBlock.new('Use atomic blocks for atomic operations')
|
162
|
+
when [false, false] then super.tap { _taint_attribute(name.to_s) }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def assign_attributes(attrs, options={})
|
167
|
+
ensure_exclusive_atomic!
|
168
|
+
super
|
169
|
+
end
|
170
|
+
|
171
|
+
def save?(options={})
|
172
|
+
raise NoBrainer::Error::AtomicBlock.new('You may persist documents only outside of queue_atomic blocks') if in_atomic?
|
173
|
+
super
|
174
|
+
end
|
175
|
+
|
176
|
+
def read_attribute_for_change(attr)
|
177
|
+
super
|
178
|
+
rescue NoBrainer::Error::CannotReadAtomic => e
|
179
|
+
e.value
|
180
|
+
end
|
181
|
+
|
182
|
+
def read_attribute_for_validation(attr)
|
183
|
+
super
|
184
|
+
rescue NoBrainer::Error::CannotReadAtomic => e
|
185
|
+
e.value
|
186
|
+
end
|
187
|
+
|
188
|
+
module ClassMethods
|
189
|
+
def persistable_value(k, v, options={})
|
190
|
+
v.is_a?(PendingAtomic) ? v.compile_rql_value(options[:rql_doc]) : super
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
class ActiveModel::EachValidator
|
196
|
+
# XXX Monkey Patching :(
|
197
|
+
def validate(record)
|
198
|
+
attributes.each do |attribute|
|
199
|
+
value = record.read_attribute_for_validation(attribute)
|
200
|
+
next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- This is the added line
|
201
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
202
|
+
validate_each(record, attribute, value)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -25,6 +25,14 @@ module NoBrainer::Document::Attributes
|
|
25
25
|
Hash[readable_attributes.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
|
26
26
|
end
|
27
27
|
|
28
|
+
def _read_attribute(name)
|
29
|
+
@_attributes[name]
|
30
|
+
end
|
31
|
+
|
32
|
+
def _write_attribute(name, value)
|
33
|
+
@_attributes[name] = value
|
34
|
+
end
|
35
|
+
|
28
36
|
def read_attribute(name)
|
29
37
|
__send__("#{name}")
|
30
38
|
end
|
@@ -103,8 +111,8 @@ module NoBrainer::Document::Attributes
|
|
103
111
|
# Using a layer so the user can use super when overriding these methods
|
104
112
|
attr = attr.to_s
|
105
113
|
inject_in_layer :attributes do
|
106
|
-
define_method("#{attr}=") { |value|
|
107
|
-
define_method("#{attr}")
|
114
|
+
define_method("#{attr}=") { |value| _write_attribute(attr, value) }
|
115
|
+
define_method("#{attr}") { _read_attribute(attr) }
|
108
116
|
end
|
109
117
|
end
|
110
118
|
|
@@ -33,10 +33,14 @@ module NoBrainer::Document::Dirty
|
|
33
33
|
changes.keys
|
34
34
|
end
|
35
35
|
|
36
|
+
def read_attribute_for_change(attr)
|
37
|
+
read_attribute(attr)
|
38
|
+
end
|
39
|
+
|
36
40
|
def changes
|
37
41
|
result = {}.with_indifferent_access
|
38
42
|
@_old_attributes.each do |attr, old_value|
|
39
|
-
current_value =
|
43
|
+
current_value = read_attribute_for_change(attr)
|
40
44
|
if current_value != old_value || !@_old_attributes_keys.include?(attr)
|
41
45
|
result[attr] = [old_value, current_value]
|
42
46
|
end
|
@@ -48,7 +52,7 @@ module NoBrainer::Document::Dirty
|
|
48
52
|
attr = args.first
|
49
53
|
current_value = begin
|
50
54
|
case args.size
|
51
|
-
when 1 then assert_access_field(attr);
|
55
|
+
when 1 then assert_access_field(attr); read_attribute_for_change(attr)
|
52
56
|
when 2 then args.last
|
53
57
|
else raise
|
54
58
|
end
|
@@ -61,6 +65,19 @@ module NoBrainer::Document::Dirty
|
|
61
65
|
end
|
62
66
|
end
|
63
67
|
|
68
|
+
def _read_attribute(name)
|
69
|
+
super.tap do |value|
|
70
|
+
# This take care of string/arrays/hashes that could change without going
|
71
|
+
# through the setter.
|
72
|
+
attribute_may_change(name, value) if value.respond_to?(:size)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def _write_attribute(name, value)
|
77
|
+
attribute_may_change(name)
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
64
81
|
module ClassMethods
|
65
82
|
def _field(attr, options={})
|
66
83
|
super
|
@@ -69,7 +86,7 @@ module NoBrainer::Document::Dirty
|
|
69
86
|
inject_in_layer :dirty_tracking do
|
70
87
|
define_method("#{attr}_change") do
|
71
88
|
if @_old_attributes.has_key?(attr)
|
72
|
-
result = [@_old_attributes[attr],
|
89
|
+
result = [@_old_attributes[attr], read_attribute_for_change(attr)]
|
73
90
|
result if result.first != result.last || !@_old_attributes_keys.include?(attr)
|
74
91
|
end
|
75
92
|
end
|
@@ -79,20 +96,7 @@ module NoBrainer::Document::Dirty
|
|
79
96
|
end
|
80
97
|
|
81
98
|
define_method("#{attr}_was") do
|
82
|
-
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] :
|
83
|
-
end
|
84
|
-
|
85
|
-
define_method("#{attr}") do
|
86
|
-
super().tap do |value|
|
87
|
-
# This take care of string/arrays/hashes that could change without going
|
88
|
-
# through the setter.
|
89
|
-
attribute_may_change(attr, value) if value.respond_to?(:size)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
define_method("#{attr}=") do |value|
|
94
|
-
attribute_may_change(attr)
|
95
|
-
super(value)
|
99
|
+
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] : read_attribute_for_change(attr)
|
96
100
|
end
|
97
101
|
end
|
98
102
|
end
|
@@ -103,8 +107,6 @@ module NoBrainer::Document::Dirty
|
|
103
107
|
remove_method("#{attr}_change")
|
104
108
|
remove_method("#{attr}_changed?")
|
105
109
|
remove_method("#{attr}_was")
|
106
|
-
remove_method("#{attr}=")
|
107
|
-
remove_method("#{attr}")
|
108
110
|
end
|
109
111
|
end
|
110
112
|
end
|
@@ -2,23 +2,11 @@ module NoBrainer::Document::DynamicAttributes
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
def read_attribute(name)
|
5
|
-
|
6
|
-
super
|
7
|
-
else
|
8
|
-
assert_access_field(name)
|
9
|
-
@_attributes[name].tap { |value| attribute_may_change(name, value) if value.respond_to?(:size) }
|
10
|
-
end
|
5
|
+
self.respond_to?("#{name}") ? super : _read_attribute(name)
|
11
6
|
end
|
12
7
|
|
13
8
|
def write_attribute(name, value)
|
14
|
-
|
15
|
-
super
|
16
|
-
else
|
17
|
-
attribute_may_change(name)
|
18
|
-
@_attributes[name] = value
|
19
|
-
clear_missing_field(name)
|
20
|
-
value
|
21
|
-
end
|
9
|
+
self.respond_to?("#{name}=") ? super : _write_attribute(name, value)
|
22
10
|
end
|
23
11
|
|
24
12
|
def readable_attributes
|
@@ -75,6 +75,7 @@ module NoBrainer::Document::Index
|
|
75
75
|
def perform_create_index(index_name, options={})
|
76
76
|
index_name = index_name.to_sym
|
77
77
|
index_args = self.indexes[index_name]
|
78
|
+
aliased_name = index_args[:as]
|
78
79
|
|
79
80
|
index_proc = case index_args[:kind]
|
80
81
|
when :single then ->(doc) { doc[lookup_field_alias(index_name)] }
|
@@ -82,29 +83,33 @@ module NoBrainer::Document::Index
|
|
82
83
|
when :proc then index_args[:what]
|
83
84
|
end
|
84
85
|
|
85
|
-
NoBrainer.run(self.rql_table.index_create(
|
86
|
+
NoBrainer.run(self.rql_table.index_create(aliased_name, index_args[:options], &index_proc))
|
86
87
|
wait_for_index(index_name) unless options[:wait] == false
|
87
88
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
STDERR.puts "Created index #{self}.#{index_name} as #{index_args[:as]}"
|
93
|
-
end
|
94
|
-
end
|
89
|
+
readable_index_name = "index #{self}.#{index_name}"
|
90
|
+
readable_index_name += " as #{aliased_name}" unless index_name == aliased_name
|
91
|
+
|
92
|
+
STDERR.puts "Created index #{readable_index_name}" if options[:verbose]
|
95
93
|
end
|
96
94
|
|
97
95
|
def perform_drop_index(index_name, options={})
|
96
|
+
index_name = index_name.to_sym
|
98
97
|
aliased_name = self.indexes[index_name].try(:[], :as) || index_name
|
99
|
-
NoBrainer.run(self.rql_table.index_drop(aliased_name))
|
100
98
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
99
|
+
readable_index_name = "index #{self}.#{index_name}"
|
100
|
+
readable_index_name += " as #{aliased_name}" unless index_name == aliased_name
|
101
|
+
|
102
|
+
if STDIN.stat.chardev? && STDERR.stat.chardev? && !options[:no_confirmation]
|
103
|
+
STDERR.print "Confirm dropping #{readable_index_name} [yna]: "
|
104
|
+
case STDIN.gets.strip.chomp
|
105
|
+
when 'y' then
|
106
|
+
when 'n' then return
|
107
|
+
when 'a' then options[:no_confirmation] = true
|
106
108
|
end
|
107
109
|
end
|
110
|
+
|
111
|
+
NoBrainer.run(self.rql_table.index_drop(aliased_name))
|
112
|
+
STDERR.puts "Dropped #{readable_index_name}" if options[:verbose]
|
108
113
|
end
|
109
114
|
|
110
115
|
def get_index_alias_reverse_map
|
@@ -120,6 +125,8 @@ module NoBrainer::Document::Index
|
|
120
125
|
end
|
121
126
|
wanted_indexes = self.indexes.keys - [self.pk_name]
|
122
127
|
|
128
|
+
# FIXME removing an aliased field is not going to work well with this method
|
129
|
+
|
123
130
|
(current_indexes - wanted_indexes).each do |index_name|
|
124
131
|
perform_drop_index(index_name, options)
|
125
132
|
end
|
@@ -45,29 +45,12 @@ module NoBrainer::Document::MissingAttributes
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
def _read_attribute(name)
|
49
|
+
assert_access_field(name)
|
50
|
+
super
|
51
|
+
end
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
super
|
52
|
-
|
53
|
-
inject_in_layer :missing_attributes do
|
54
|
-
define_method("#{attr}") do
|
55
|
-
assert_access_field(attr)
|
56
|
-
super()
|
57
|
-
end
|
58
|
-
|
59
|
-
define_method("#{attr}=") do |value|
|
60
|
-
super(value).tap { clear_missing_field(attr) }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def _remove_field(attr, options={})
|
66
|
-
super
|
67
|
-
inject_in_layer :missing_attributes do
|
68
|
-
remove_method("#{attr}=")
|
69
|
-
remove_method("#{attr}")
|
70
|
-
end
|
71
|
-
end
|
53
|
+
def _write_attribute(name, value)
|
54
|
+
super.tap { clear_missing_field(name) }
|
72
55
|
end
|
73
56
|
end
|
@@ -61,15 +61,16 @@ module NoBrainer::Document::Persistance
|
|
61
61
|
|
62
62
|
def _create(options={})
|
63
63
|
return false if options[:validate] && !valid?
|
64
|
-
|
65
|
-
|
64
|
+
attrs = self.class.persistable_attributes(@_attributes, :instance => self)
|
65
|
+
result = NoBrainer.run(self.class.rql_table.insert(attrs))
|
66
|
+
self.pk_value ||= result['generated_keys'].to_a.first
|
66
67
|
@new_record = false
|
67
68
|
true
|
68
69
|
end
|
69
70
|
|
70
71
|
def _update(attrs)
|
71
|
-
|
72
|
-
NoBrainer.run { selector.update(
|
72
|
+
rql = ->(doc){ self.class.persistable_attributes(attrs, :instance => self, :rql_doc => doc) }
|
73
|
+
NoBrainer.run { selector.update(&rql) }
|
73
74
|
end
|
74
75
|
|
75
76
|
def _update_only_changed_attrs(options={})
|
@@ -88,22 +89,22 @@ module NoBrainer::Document::Persistance
|
|
88
89
|
true
|
89
90
|
end
|
90
91
|
|
91
|
-
def save(options={})
|
92
|
+
def save?(options={})
|
92
93
|
options = options.reverse_merge(:validate => true)
|
93
94
|
new_record? ? _create(options) : _update_only_changed_attrs(options)
|
94
95
|
end
|
95
96
|
|
96
|
-
def save
|
97
|
-
save(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
97
|
+
def save(*args)
|
98
|
+
save?(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
98
99
|
end
|
99
100
|
|
100
|
-
def update_attributes(attrs, options={})
|
101
|
+
def update_attributes?(attrs, options={})
|
101
102
|
assign_attributes(attrs, options)
|
102
|
-
save(options)
|
103
|
+
save?(options)
|
103
104
|
end
|
104
105
|
|
105
|
-
def update_attributes
|
106
|
-
update_attributes(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
106
|
+
def update_attributes(*args)
|
107
|
+
update_attributes?(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
107
108
|
end
|
108
109
|
|
109
110
|
def delete
|
@@ -124,10 +125,6 @@ module NoBrainer::Document::Persistance
|
|
124
125
|
new(attrs, options).tap { |doc| doc.save(options) }
|
125
126
|
end
|
126
127
|
|
127
|
-
def create!(attrs={}, options={})
|
128
|
-
new(attrs, options).tap { |doc| doc.save!(options) }
|
129
|
-
end
|
130
|
-
|
131
128
|
def insert_all(*args)
|
132
129
|
docs = args.shift
|
133
130
|
docs = [docs] unless docs.is_a?(Array)
|
@@ -140,16 +137,16 @@ module NoBrainer::Document::Persistance
|
|
140
137
|
NoBrainer.run(rql_table.sync)['synced'] == 1
|
141
138
|
end
|
142
139
|
|
143
|
-
def persistable_key(k)
|
140
|
+
def persistable_key(k, options={})
|
144
141
|
k
|
145
142
|
end
|
146
143
|
|
147
|
-
def persistable_value(k, v)
|
144
|
+
def persistable_value(k, v, options={})
|
148
145
|
v
|
149
146
|
end
|
150
147
|
|
151
|
-
def persistable_attributes(attrs)
|
152
|
-
Hash[attrs.map { |k,v| [persistable_key(k), persistable_value(k, v)] }]
|
148
|
+
def persistable_attributes(attrs, options={})
|
149
|
+
Hash[attrs.map { |k,v| [persistable_key(k, options), persistable_value(k, v, options)] }]
|
153
150
|
end
|
154
151
|
end
|
155
152
|
end
|
@@ -22,7 +22,7 @@ module NoBrainer::Document::Types
|
|
22
22
|
module ClassMethods
|
23
23
|
def cast_user_to_model_for(attr, value)
|
24
24
|
type = fields[attr.to_sym].try(:[], :type)
|
25
|
-
return value if type.nil? || value.nil?
|
25
|
+
return value if type.nil? || value.nil? || value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic)
|
26
26
|
if type.respond_to?(:nobrainer_cast_user_to_model)
|
27
27
|
type.nobrainer_cast_user_to_model(value)
|
28
28
|
else
|
@@ -53,7 +53,7 @@ module NoBrainer::Document::Types
|
|
53
53
|
cast_model_to_db_for(attr, value)
|
54
54
|
end
|
55
55
|
|
56
|
-
def persistable_value(k, v)
|
56
|
+
def persistable_value(k, v, options={})
|
57
57
|
cast_model_to_db_for(k, super)
|
58
58
|
end
|
59
59
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Set
|
2
|
+
module NoBrainerExtentions
|
3
|
+
InvalidType = NoBrainer::Error::InvalidType
|
4
|
+
|
5
|
+
def nobrainer_cast_user_to_model(value)
|
6
|
+
case value
|
7
|
+
when Set then value
|
8
|
+
when Array then Set.new(value)
|
9
|
+
else raise InvalidType
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def nobrainer_cast_db_to_model(value)
|
14
|
+
value.is_a?(Array) ? Set.new(value) : value
|
15
|
+
end
|
16
|
+
|
17
|
+
def nobrainer_cast_model_to_db(value)
|
18
|
+
value.is_a?(Set) ? value.to_a : value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
extend NoBrainerExtentions
|
23
|
+
end
|
data/lib/no_brainer/error.rb
CHANGED
@@ -9,6 +9,20 @@ module NoBrainer::Error
|
|
9
9
|
class AssociationNotPersisted < RuntimeError; end
|
10
10
|
class ReadonlyField < RuntimeError; end
|
11
11
|
class MissingAttribute < RuntimeError; end
|
12
|
+
class AtomicBlock < RuntimeError; end
|
13
|
+
|
14
|
+
class CannotReadAtomic < RuntimeError
|
15
|
+
attr_accessor :instance, :field, :value
|
16
|
+
def initialize(instance, field, value)
|
17
|
+
@instance = instance
|
18
|
+
@field = field
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def message
|
23
|
+
"Cannot read #{field}, atomic operations are pending"
|
24
|
+
end
|
25
|
+
end
|
12
26
|
|
13
27
|
class DocumentInvalid < RuntimeError
|
14
28
|
attr_accessor :instance
|
@@ -2,6 +2,10 @@ class NoBrainer::QueryRunner::ConnectionLock < NoBrainer::QueryRunner::Middlewar
|
|
2
2
|
@@lock = Mutex.new
|
3
3
|
|
4
4
|
def call(env)
|
5
|
-
|
5
|
+
if NoBrainer::Config.per_thread_connection
|
6
|
+
@runner.call(env)
|
7
|
+
else
|
8
|
+
@@lock.synchronize { @runner.call(env) }
|
9
|
+
end
|
6
10
|
end
|
7
11
|
end
|
data/lib/nobrainer.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'set'
|
1
2
|
require 'active_support'
|
2
3
|
%w(module/delegation module/attribute_accessors class/attribute object/blank object/inclusion object/deep_dup
|
3
4
|
object/try hash/keys hash/indifferent_access hash/reverse_merge hash/deep_merge array/extract_options)
|
@@ -10,28 +11,11 @@ module NoBrainer
|
|
10
11
|
# We eager load things that could be loaded when handling the first web request.
|
11
12
|
# Code that is loaded through the DSL of NoBrainer should not be eager loaded.
|
12
13
|
autoload :Document, :IndexManager, :Loader, :Fork, :DecoratedSymbol
|
13
|
-
eager_autoload :Config, :Connection, :
|
14
|
+
eager_autoload :Config, :Connection, :ConnectionManager, :Error,
|
15
|
+
:QueryRunner, :Criteria, :RQL
|
14
16
|
|
15
17
|
class << self
|
16
|
-
|
17
|
-
def connection
|
18
|
-
@connection ||= begin
|
19
|
-
url = NoBrainer::Config.rethinkdb_url
|
20
|
-
raise "Please specify a database connection to RethinkDB" unless url
|
21
|
-
Connection.new(url)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def disconnect
|
26
|
-
@connection.try(:disconnect, :noreply_wait => true)
|
27
|
-
@connection = nil
|
28
|
-
end
|
29
|
-
|
30
|
-
def disconnect_if_url_changed
|
31
|
-
if @connection && @connection.uri != NoBrainer::Config.rethinkdb_url
|
32
|
-
disconnect
|
33
|
-
end
|
34
|
-
end
|
18
|
+
delegate :connection, :disconnect, :to => 'NoBrainer::ConnectionManager'
|
35
19
|
|
36
20
|
delegate :db_create, :db_drop, :db_list,
|
37
21
|
:table_create, :table_drop, :table_list,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nobrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Viennot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rethinkdb
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- lib/no_brainer/autoload.rb
|
79
79
|
- lib/no_brainer/config.rb
|
80
80
|
- lib/no_brainer/connection.rb
|
81
|
+
- lib/no_brainer/connection_manager.rb
|
81
82
|
- lib/no_brainer/criteria.rb
|
82
83
|
- lib/no_brainer/criteria/after_find.rb
|
83
84
|
- lib/no_brainer/criteria/aggregate.rb
|
@@ -107,6 +108,7 @@ files:
|
|
107
108
|
- lib/no_brainer/document/association/has_many_through.rb
|
108
109
|
- lib/no_brainer/document/association/has_one.rb
|
109
110
|
- lib/no_brainer/document/association/has_one_through.rb
|
111
|
+
- lib/no_brainer/document/atomic_ops.rb
|
110
112
|
- lib/no_brainer/document/attributes.rb
|
111
113
|
- lib/no_brainer/document/callbacks.rb
|
112
114
|
- lib/no_brainer/document/core.rb
|
@@ -130,6 +132,7 @@ files:
|
|
130
132
|
- lib/no_brainer/document/types/date.rb
|
131
133
|
- lib/no_brainer/document/types/float.rb
|
132
134
|
- lib/no_brainer/document/types/integer.rb
|
135
|
+
- lib/no_brainer/document/types/set.rb
|
133
136
|
- lib/no_brainer/document/types/string.rb
|
134
137
|
- lib/no_brainer/document/types/symbol.rb
|
135
138
|
- lib/no_brainer/document/types/time.rb
|
@@ -161,7 +164,10 @@ homepage: http://nobrainer.io
|
|
161
164
|
licenses:
|
162
165
|
- LGPLv3
|
163
166
|
metadata: {}
|
164
|
-
post_install_message:
|
167
|
+
post_install_message: |2
|
168
|
+
|
169
|
+
WARNING [NoBrainer] API change: save() is now save?() and save!() is now save()
|
170
|
+
WARNING [NoBrainer] Same for update_attributes
|
165
171
|
rdoc_options: []
|
166
172
|
require_paths:
|
167
173
|
- lib
|