nobrainer 0.16.0 → 0.17.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.
- 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
|