nobrainer 0.16.0 → 0.18.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.
- checksums.yaml +4 -4
- data/lib/no_brainer/config.rb +53 -28
- data/lib/no_brainer/connection.rb +1 -0
- data/lib/no_brainer/connection_manager.rb +60 -0
- data/lib/no_brainer/criteria/after_find.rb +3 -11
- data/lib/no_brainer/criteria/aggregate.rb +8 -5
- data/lib/no_brainer/criteria/cache.rb +7 -7
- data/lib/no_brainer/criteria/core.rb +56 -16
- data/lib/no_brainer/criteria/count.rb +1 -1
- data/lib/no_brainer/criteria/delete.rb +1 -1
- data/lib/no_brainer/criteria/extend.rb +19 -0
- data/lib/no_brainer/criteria/first.rb +1 -1
- data/lib/no_brainer/criteria/index.rb +7 -13
- data/lib/no_brainer/criteria/limit.rb +5 -13
- data/lib/no_brainer/criteria/order_by.rb +22 -41
- data/lib/no_brainer/criteria/pluck.rb +17 -23
- data/lib/no_brainer/criteria/preload.rb +9 -15
- data/lib/no_brainer/criteria/raw.rb +5 -11
- data/lib/no_brainer/criteria/scope.rb +9 -15
- data/lib/no_brainer/criteria/update.rb +3 -3
- data/lib/no_brainer/criteria/where.rb +33 -56
- data/lib/no_brainer/criteria.rb +1 -1
- data/lib/no_brainer/document/aliases.rb +1 -1
- data/lib/no_brainer/document/association/belongs_to.rb +6 -6
- data/lib/no_brainer/document/association/core.rb +11 -11
- data/lib/no_brainer/document/association/eager_loader.rb +4 -3
- data/lib/no_brainer/document/association/has_many.rb +7 -7
- data/lib/no_brainer/document/association/has_many_through.rb +1 -1
- data/lib/no_brainer/document/association/has_one.rb +1 -1
- data/lib/no_brainer/document/association.rb +5 -5
- data/lib/no_brainer/document/atomic_ops.rb +213 -0
- data/lib/no_brainer/document/attributes.rb +19 -10
- data/lib/no_brainer/document/callbacks.rb +1 -1
- data/lib/no_brainer/document/core.rb +1 -1
- data/lib/no_brainer/document/criteria.rb +9 -2
- data/lib/no_brainer/document/dirty.rb +22 -22
- 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/index.rb +83 -0
- data/lib/no_brainer/document/index/meta_store.rb +31 -0
- data/lib/no_brainer/document/index/synchronizer.rb +68 -0
- data/lib/no_brainer/document/index.rb +13 -72
- data/lib/no_brainer/document/lazy_fetch.rb +5 -5
- data/lib/no_brainer/document/missing_attributes.rb +6 -23
- data/lib/no_brainer/document/persistance.rb +38 -21
- data/lib/no_brainer/document/polymorphic.rb +1 -1
- data/lib/no_brainer/document/types/set.rb +23 -0
- data/lib/no_brainer/document/types.rb +8 -6
- data/lib/no_brainer/document/uniqueness.rb +3 -3
- data/lib/no_brainer/document/validation.rb +13 -4
- data/lib/no_brainer/document.rb +1 -1
- data/lib/no_brainer/error.rb +14 -0
- data/lib/no_brainer/fork.rb +1 -0
- data/lib/no_brainer/query_runner/connection_lock.rb +5 -1
- data/lib/no_brainer/query_runner/database_on_demand.rb +6 -5
- data/lib/no_brainer/query_runner/logger.rb +10 -6
- data/lib/no_brainer/query_runner/missing_index.rb +5 -4
- data/lib/no_brainer/query_runner/reconnect.rb +20 -17
- data/lib/no_brainer/query_runner/run_options.rb +3 -0
- data/lib/no_brainer/query_runner/table_on_demand.rb +11 -8
- data/lib/no_brainer/railtie/database.rake +12 -12
- data/lib/no_brainer/railtie.rb +5 -5
- data/lib/no_brainer/rql.rb +9 -0
- data/lib/nobrainer.rb +9 -23
- metadata +10 -4
- data/lib/no_brainer/index_manager.rb +0 -9
|
@@ -7,7 +7,7 @@ class NoBrainer::Document::Association::EagerLoader
|
|
|
7
7
|
owner_key = instance_exec(&options[:owner_key])
|
|
8
8
|
target_key = instance_exec(&options[:target_key])
|
|
9
9
|
|
|
10
|
-
criteria =
|
|
10
|
+
criteria = target_model.all
|
|
11
11
|
criteria = criteria.merge(additional_criteria) if additional_criteria
|
|
12
12
|
criteria = criteria.unscoped if options[:unscoped]
|
|
13
13
|
|
|
@@ -45,14 +45,15 @@ class NoBrainer::Document::Association::EagerLoader
|
|
|
45
45
|
when Hash then preloads.each do |k,v|
|
|
46
46
|
if v.is_a?(NoBrainer::Criteria)
|
|
47
47
|
v = v.dup
|
|
48
|
-
nested_preloads, v.
|
|
48
|
+
nested_preloads, v.options[:preload] = v.options[:preload], []
|
|
49
49
|
eager_load(eager_load_association(docs, k, v), nested_preloads)
|
|
50
50
|
else
|
|
51
51
|
eager_load(eager_load_association(docs, k), v)
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
when Array then preloads.each { |v| eager_load(docs, v) }
|
|
55
|
-
|
|
55
|
+
when nil then;
|
|
56
|
+
else eager_load_association(docs, preloads) # String and Symbol
|
|
56
57
|
end
|
|
57
58
|
true
|
|
58
59
|
end
|
|
@@ -8,15 +8,15 @@ class NoBrainer::Document::Association::HasMany
|
|
|
8
8
|
|
|
9
9
|
def foreign_key
|
|
10
10
|
# TODO test :foreign_key
|
|
11
|
-
options[:foreign_key].try(:to_sym) || :"#{
|
|
11
|
+
options[:foreign_key].try(:to_sym) || :"#{owner_model.name.underscore}_#{owner_model.pk_name}"
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def primary_key
|
|
15
15
|
# TODO test :primary_key
|
|
16
|
-
options[:primary_key].try(:to_sym) ||
|
|
16
|
+
options[:primary_key].try(:to_sym) || target_model.pk_name
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def
|
|
19
|
+
def target_model
|
|
20
20
|
# TODO test :class_name
|
|
21
21
|
(options[:class_name] || target_name.to_s.singularize.camelize).constantize
|
|
22
22
|
end
|
|
@@ -27,11 +27,11 @@ class NoBrainer::Document::Association::HasMany
|
|
|
27
27
|
# selector.
|
|
28
28
|
# XXX Without caching, this is going to get CPU intensive quickly, but
|
|
29
29
|
# caching is hard (rails console reload, etc.).
|
|
30
|
-
|
|
30
|
+
target_model.association_metadata.values.select do |assoc|
|
|
31
31
|
assoc.is_a?(NoBrainer::Document::Association::BelongsTo::Metadata) and
|
|
32
32
|
assoc.foreign_key == self.foreign_key and
|
|
33
33
|
assoc.primary_key == self.primary_key and
|
|
34
|
-
assoc.
|
|
34
|
+
assoc.target_model.root_class == owner_model.root_class
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -44,7 +44,7 @@ class NoBrainer::Document::Association::HasMany
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def target_criteria
|
|
47
|
-
@target_criteria ||=
|
|
47
|
+
@target_criteria ||= target_model.where(foreign_key => owner.pk_value)
|
|
48
48
|
.after_find(set_inverse_proc)
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -54,7 +54,7 @@ class NoBrainer::Document::Association::HasMany
|
|
|
54
54
|
|
|
55
55
|
def write(new_children)
|
|
56
56
|
raise "You can't assign #{target_name}. " +
|
|
57
|
-
"Instead, you must modify delete and create #{
|
|
57
|
+
"Instead, you must modify delete and create #{target_model} manually."
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def loaded?
|
|
@@ -10,7 +10,7 @@ class NoBrainer::Document::Association::HasManyThrough
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def through_association
|
|
13
|
-
|
|
13
|
+
owner_model.association_metadata[through_association_name] or
|
|
14
14
|
raise "#{through_association_name} association not found"
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class NoBrainer::Document::Association::HasOne < NoBrainer::Document::Association::HasMany
|
|
2
2
|
class Metadata < NoBrainer::Document::Association::HasMany::Metadata
|
|
3
|
-
def
|
|
3
|
+
def target_model
|
|
4
4
|
(options[:class_name] || target_name.to_s.camelize).constantize
|
|
5
5
|
end
|
|
6
6
|
end
|
|
@@ -28,11 +28,11 @@ module NoBrainer::Document::Association
|
|
|
28
28
|
raise "Cannot change the :through option" unless r.options[:through] == options[:through]
|
|
29
29
|
r.options.merge!(options)
|
|
30
30
|
else
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
r =
|
|
34
|
-
([self] + descendants).each do |
|
|
35
|
-
|
|
31
|
+
model_name = (options[:through] ? "#{association}_through" : association.to_s).camelize
|
|
32
|
+
metadata_model = NoBrainer::Document::Association.const_get(model_name).const_get(:Metadata)
|
|
33
|
+
r = metadata_model.new(self, target, options)
|
|
34
|
+
([self] + descendants).each do |model|
|
|
35
|
+
model.association_metadata[target] = r
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
r.hook
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
module NoBrainer::Document::AtomicOps
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
class PendingAtomic
|
|
5
|
+
def self._new(instance, field, value, is_user_value, options={})
|
|
6
|
+
model = case value
|
|
7
|
+
when Array then PendingAtomicArray
|
|
8
|
+
when Set then PendingAtomicSet
|
|
9
|
+
else self
|
|
10
|
+
end
|
|
11
|
+
model.new(instance, field, value, is_user_value, options)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(instance, field, value, is_user_value, options={})
|
|
15
|
+
@instance = instance
|
|
16
|
+
@field = field.to_s
|
|
17
|
+
@value = value
|
|
18
|
+
@is_user_value = is_user_value
|
|
19
|
+
@options = options.dup
|
|
20
|
+
@ops = []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def write_access?
|
|
24
|
+
!!@options[:write_access]
|
|
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 = @is_user_value ? RethinkDB::RQL.new.expr(@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
|
+
@_touched_attributes = Set.new
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def _touch_attribute(name)
|
|
112
|
+
@_touched_attributes << name.to_s
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def _is_attribute_touched?(name)
|
|
116
|
+
@_touched_attributes.include?(name.to_s)
|
|
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, value, _is_attribute_touched?(name))
|
|
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 { _touch_attribute(name) }
|
|
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
|
+
# TODO allow reload => true as an option to save+reload in a single op.
|
|
173
|
+
raise NoBrainer::Error::AtomicBlock.new('You may persist documents only outside of queue_atomic blocks') if in_atomic?
|
|
174
|
+
super.tap do |saved|
|
|
175
|
+
if saved
|
|
176
|
+
@_attributes.each do |attr, value|
|
|
177
|
+
next unless value.is_a?(PendingAtomic)
|
|
178
|
+
@_attributes[attr] = value.class.new(self, attr, nil, false)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def read_attribute_for_change(attr)
|
|
185
|
+
super
|
|
186
|
+
rescue NoBrainer::Error::CannotReadAtomic => e
|
|
187
|
+
e.value
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def read_attribute_for_validation(attr)
|
|
191
|
+
super
|
|
192
|
+
rescue NoBrainer::Error::CannotReadAtomic => e
|
|
193
|
+
e.value
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
module ClassMethods
|
|
197
|
+
def persistable_value(k, v, options={})
|
|
198
|
+
v.is_a?(PendingAtomic) ? v.compile_rql_value(options[:rql_doc]) : super
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
class ActiveModel::EachValidator
|
|
204
|
+
# XXX Monkey Patching :(
|
|
205
|
+
def validate(record)
|
|
206
|
+
attributes.each do |attribute|
|
|
207
|
+
value = record.read_attribute_for_validation(attribute)
|
|
208
|
+
next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- This is the added line
|
|
209
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
|
210
|
+
validate_each(record, attribute, value)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module NoBrainer::Document::Attributes
|
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type, :real_type,
|
|
3
|
-
:
|
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :real_type,
|
|
3
|
+
:validates, :required, :unique, :uniq, :format, :in,
|
|
4
|
+
:readonly, :primary_key, :as, :lazy_fetch]
|
|
4
5
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] \
|
|
5
6
|
+ NoBrainer::DecoratedSymbol::MODIFIERS.keys
|
|
6
7
|
extend ActiveSupport::Concern
|
|
@@ -25,6 +26,14 @@ module NoBrainer::Document::Attributes
|
|
|
25
26
|
Hash[readable_attributes.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
|
|
26
27
|
end
|
|
27
28
|
|
|
29
|
+
def _read_attribute(name)
|
|
30
|
+
@_attributes[name]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def _write_attribute(name, value)
|
|
34
|
+
@_attributes[name] = value
|
|
35
|
+
end
|
|
36
|
+
|
|
28
37
|
def read_attribute(name)
|
|
29
38
|
__send__("#{name}")
|
|
30
39
|
end
|
|
@@ -91,7 +100,7 @@ module NoBrainer::Document::Attributes
|
|
|
91
100
|
module ClassMethods
|
|
92
101
|
def new_from_db(attrs, options={})
|
|
93
102
|
options = options.reverse_merge(:pristine => true, :from_db => true)
|
|
94
|
-
|
|
103
|
+
model_from_attrs(attrs).new(attrs, options) if attrs
|
|
95
104
|
end
|
|
96
105
|
|
|
97
106
|
def inherited(subclass)
|
|
@@ -103,8 +112,8 @@ module NoBrainer::Document::Attributes
|
|
|
103
112
|
# Using a layer so the user can use super when overriding these methods
|
|
104
113
|
attr = attr.to_s
|
|
105
114
|
inject_in_layer :attributes do
|
|
106
|
-
define_method("#{attr}=") { |value|
|
|
107
|
-
define_method("#{attr}")
|
|
115
|
+
define_method("#{attr}=") { |value| _write_attribute(attr, value) }
|
|
116
|
+
define_method("#{attr}") { _read_attribute(attr) }
|
|
108
117
|
end
|
|
109
118
|
end
|
|
110
119
|
|
|
@@ -116,9 +125,9 @@ module NoBrainer::Document::Attributes
|
|
|
116
125
|
raise "Cannot use a reserved field attr: #{attr}"
|
|
117
126
|
end
|
|
118
127
|
|
|
119
|
-
([self] + descendants).each do |
|
|
120
|
-
|
|
121
|
-
|
|
128
|
+
([self] + descendants).each do |model|
|
|
129
|
+
model.fields[attr] ||= {}
|
|
130
|
+
model.fields[attr].deep_merge!(options)
|
|
122
131
|
end
|
|
123
132
|
|
|
124
133
|
_field(attr, self.fields[attr])
|
|
@@ -136,8 +145,8 @@ module NoBrainer::Document::Attributes
|
|
|
136
145
|
|
|
137
146
|
_remove_field(attr, options)
|
|
138
147
|
|
|
139
|
-
([self] + descendants).each do |
|
|
140
|
-
|
|
148
|
+
([self] + descendants).each do |model|
|
|
149
|
+
model.fields.delete(attr)
|
|
141
150
|
end
|
|
142
151
|
end
|
|
143
152
|
|
|
@@ -5,7 +5,10 @@ module NoBrainer::Document::Criteria
|
|
|
5
5
|
self.class.selector_for(pk_value)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
included
|
|
8
|
+
included do
|
|
9
|
+
cattr_accessor :default_scope_proc, :instance_accessor => false
|
|
10
|
+
cattr_accessor :perf_warnings_disabled, :instance_accessor => false
|
|
11
|
+
end
|
|
9
12
|
|
|
10
13
|
module ClassMethods
|
|
11
14
|
delegate :to_rql, # Core
|
|
@@ -27,7 +30,7 @@ module NoBrainer::Document::Criteria
|
|
|
27
30
|
:to => :all
|
|
28
31
|
|
|
29
32
|
def all
|
|
30
|
-
NoBrainer::Criteria.new(:
|
|
33
|
+
NoBrainer::Criteria.new(:model => self)
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
def scope(name, criteria=nil, &block)
|
|
@@ -59,5 +62,9 @@ module NoBrainer::Document::Criteria
|
|
|
59
62
|
raise NoBrainer::Error::DocumentNotFound, "#{self} #{pk_name}: #{pk} not found" unless doc
|
|
60
63
|
end
|
|
61
64
|
end
|
|
65
|
+
|
|
66
|
+
def disable_perf_warnings
|
|
67
|
+
self.perf_warnings_disabled = true
|
|
68
|
+
end
|
|
62
69
|
end
|
|
63
70
|
end
|
|
@@ -17,9 +17,7 @@ module NoBrainer::Document::Dirty
|
|
|
17
17
|
def clear_dirtiness(options={})
|
|
18
18
|
if options[:keep_ivars] && options[:missing_attributes].try(:[], :pluck)
|
|
19
19
|
attrs = options[:missing_attributes][:pluck].keys
|
|
20
|
-
@_old_attributes = @_old_attributes.reject { |k,v| attrs.include?(k) }
|
|
21
|
-
else
|
|
22
|
-
@_old_attributes = {}.with_indifferent_access
|
|
20
|
+
@_old_attributes = @_old_attributes.reject { |k,v| attrs.include?(k) } else @_old_attributes = {}.with_indifferent_access
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
@_old_attributes_keys = @_attributes.keys # to track undefined -> nil changes
|
|
@@ -33,10 +31,14 @@ module NoBrainer::Document::Dirty
|
|
|
33
31
|
changes.keys
|
|
34
32
|
end
|
|
35
33
|
|
|
34
|
+
def read_attribute_for_change(attr)
|
|
35
|
+
read_attribute(attr)
|
|
36
|
+
end
|
|
37
|
+
|
|
36
38
|
def changes
|
|
37
39
|
result = {}.with_indifferent_access
|
|
38
40
|
@_old_attributes.each do |attr, old_value|
|
|
39
|
-
current_value =
|
|
41
|
+
current_value = read_attribute_for_change(attr)
|
|
40
42
|
if current_value != old_value || !@_old_attributes_keys.include?(attr)
|
|
41
43
|
result[attr] = [old_value, current_value]
|
|
42
44
|
end
|
|
@@ -48,7 +50,7 @@ module NoBrainer::Document::Dirty
|
|
|
48
50
|
attr = args.first
|
|
49
51
|
current_value = begin
|
|
50
52
|
case args.size
|
|
51
|
-
when 1 then assert_access_field(attr);
|
|
53
|
+
when 1 then assert_access_field(attr); read_attribute_for_change(attr)
|
|
52
54
|
when 2 then args.last
|
|
53
55
|
else raise
|
|
54
56
|
end
|
|
@@ -61,6 +63,19 @@ module NoBrainer::Document::Dirty
|
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
65
|
|
|
66
|
+
def _read_attribute(name)
|
|
67
|
+
super.tap do |value|
|
|
68
|
+
# This take care of string/arrays/hashes that could change without going
|
|
69
|
+
# through the setter.
|
|
70
|
+
attribute_may_change(name, value) if value.respond_to?(:size)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def _write_attribute(name, value)
|
|
75
|
+
attribute_may_change(name)
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
|
|
64
79
|
module ClassMethods
|
|
65
80
|
def _field(attr, options={})
|
|
66
81
|
super
|
|
@@ -69,7 +84,7 @@ module NoBrainer::Document::Dirty
|
|
|
69
84
|
inject_in_layer :dirty_tracking do
|
|
70
85
|
define_method("#{attr}_change") do
|
|
71
86
|
if @_old_attributes.has_key?(attr)
|
|
72
|
-
result = [@_old_attributes[attr],
|
|
87
|
+
result = [@_old_attributes[attr], read_attribute_for_change(attr)]
|
|
73
88
|
result if result.first != result.last || !@_old_attributes_keys.include?(attr)
|
|
74
89
|
end
|
|
75
90
|
end
|
|
@@ -79,20 +94,7 @@ module NoBrainer::Document::Dirty
|
|
|
79
94
|
end
|
|
80
95
|
|
|
81
96
|
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)
|
|
97
|
+
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] : read_attribute_for_change(attr)
|
|
96
98
|
end
|
|
97
99
|
end
|
|
98
100
|
end
|
|
@@ -103,8 +105,6 @@ module NoBrainer::Document::Dirty
|
|
|
103
105
|
remove_method("#{attr}_change")
|
|
104
106
|
remove_method("#{attr}_changed?")
|
|
105
107
|
remove_method("#{attr}_was")
|
|
106
|
-
remove_method("#{attr}=")
|
|
107
|
-
remove_method("#{attr}")
|
|
108
108
|
end
|
|
109
109
|
end
|
|
110
110
|
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
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
class NoBrainer::Document::Index::Index < Struct.new(
|
|
2
|
+
:model, :name, :aliased_name, :kind, :what, :external, :geo, :multi, :meta)
|
|
3
|
+
|
|
4
|
+
MetaStore = NoBrainer::Document::Index::MetaStore
|
|
5
|
+
|
|
6
|
+
def initialize(*args)
|
|
7
|
+
super
|
|
8
|
+
|
|
9
|
+
self.name = self.name.to_sym
|
|
10
|
+
self.aliased_name = self.aliased_name.to_sym
|
|
11
|
+
self.external = !!self.external
|
|
12
|
+
self.geo = !!self.geo
|
|
13
|
+
self.multi = !!self.multi
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def same_definition?(other)
|
|
17
|
+
# allow name to change through renames
|
|
18
|
+
self.model == other.model &&
|
|
19
|
+
self.geo == other.geo &&
|
|
20
|
+
self.multi == other.multi &&
|
|
21
|
+
self.serialized_rql_proc == other.serialized_rql_proc
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def human_name
|
|
25
|
+
index_name = "index #{model}.#{name}"
|
|
26
|
+
index_name += " as #{aliased_name}" unless name == aliased_name
|
|
27
|
+
index_name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def rql_proc
|
|
31
|
+
case kind
|
|
32
|
+
when :single then ->(doc) { doc[model.lookup_field_alias(what)] }
|
|
33
|
+
when :compound then ->(doc) { what.map { |field| doc[model.lookup_field_alias(field)] } }
|
|
34
|
+
when :proc then what # TODO XXX not translating the field aliases
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def serialized_rql_proc
|
|
39
|
+
meta.try(:rql_function) || (rql_proc && NoBrainer::RQL.rql_proc_as_json(rql_proc))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def show_op(verb, options={})
|
|
43
|
+
color = case verb
|
|
44
|
+
when :create then "\e[1;32m" # green
|
|
45
|
+
when :delete then "\e[1;31m" # red
|
|
46
|
+
when :update then "\e[1;33m" # yellow
|
|
47
|
+
end
|
|
48
|
+
STDERR.puts "[NoBrainer] #{color}#{verb.to_s.capitalize} #{human_name}\e[0m" if options[:verbose]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def create(options={})
|
|
52
|
+
show_op(:create, options)
|
|
53
|
+
|
|
54
|
+
opt = {}
|
|
55
|
+
opt[:multi] = true if multi
|
|
56
|
+
opt[:geo] = true if geo
|
|
57
|
+
|
|
58
|
+
NoBrainer::RQL.reset_lambda_var_counter
|
|
59
|
+
NoBrainer.run(model.rql_table.index_create(aliased_name, opt, &rql_proc))
|
|
60
|
+
|
|
61
|
+
MetaStore.on(model.database_name) do
|
|
62
|
+
MetaStore.create(:table_name => model.table_name, :index_name => aliased_name,
|
|
63
|
+
:rql_function => serialized_rql_proc)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def delete(options={})
|
|
68
|
+
show_op(:delete, options)
|
|
69
|
+
|
|
70
|
+
NoBrainer.run(model.rql_table.index_drop(aliased_name))
|
|
71
|
+
|
|
72
|
+
MetaStore.on(model.database_name) do
|
|
73
|
+
MetaStore.where(:table_name => model.table_name, :index_name => aliased_name).delete_all
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def update(wanted_index, options={})
|
|
78
|
+
wanted_index.show_op(:update, options)
|
|
79
|
+
|
|
80
|
+
self.delete(options.merge(:verbose => false))
|
|
81
|
+
wanted_index.create(options.merge(:verbose => false))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class NoBrainer::Document::Index::MetaStore
|
|
2
|
+
include NoBrainer::Document
|
|
3
|
+
include NoBrainer::Document::Timestamps
|
|
4
|
+
|
|
5
|
+
disable_perf_warnings
|
|
6
|
+
|
|
7
|
+
default_scope ->{ order_by(:created_at) }
|
|
8
|
+
|
|
9
|
+
store_in :database => ->{ Thread.current[:nobrainer_meta_store_db] },
|
|
10
|
+
:table => 'nobrainer_index_meta'
|
|
11
|
+
|
|
12
|
+
field :table_name, :type => String, :required => true
|
|
13
|
+
field :index_name, :type => String, :required => true
|
|
14
|
+
field :rql_function, :type => String, :required => true
|
|
15
|
+
|
|
16
|
+
def rql_function=(value)
|
|
17
|
+
super(JSON.dump(value))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def rql_function
|
|
21
|
+
JSON.load(super)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.on(db_name, &block)
|
|
25
|
+
old_db_name = Thread.current[:nobrainer_meta_store_db]
|
|
26
|
+
Thread.current[:nobrainer_meta_store_db] = db_name
|
|
27
|
+
NoBrainer.with(:auto_create_tables => true) { block.call }
|
|
28
|
+
ensure
|
|
29
|
+
Thread.current[:nobrainer_meta_store_db] = old_db_name
|
|
30
|
+
end
|
|
31
|
+
end
|