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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
class NoBrainer::Document::Index::Synchronizer
|
|
2
|
+
Index = NoBrainer::Document::Index::Index
|
|
3
|
+
MetaStore = NoBrainer::Document::Index::MetaStore
|
|
4
|
+
|
|
5
|
+
def initialize(models)
|
|
6
|
+
@models_indexes_map = Hash[models.map do |model|
|
|
7
|
+
[model, model.indexes.values.reject { |index| index.name == model.pk_name }]
|
|
8
|
+
end]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def meta_store_on(db_name)
|
|
12
|
+
@meta_store ||= {}
|
|
13
|
+
@meta_store[db_name] ||= MetaStore.on(db_name) { MetaStore.all.to_a }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Op < Struct.new(:index, :op, :args)
|
|
17
|
+
def run(options={})
|
|
18
|
+
index.__send__(op, *args, options)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def _generate_plan_for(model, wanted_indexes)
|
|
23
|
+
current_indexes = NoBrainer.run(model.rql_table.index_status).map do |s|
|
|
24
|
+
meta = meta_store_on(model.database_name)
|
|
25
|
+
.select { |i| i.table_name == model.table_name && i.index_name == s['index'] }.last
|
|
26
|
+
Index.new(model, s['index'], s['index'], nil, nil, nil, s['geo'], s['multi'], meta)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
all_aliased_names = (wanted_indexes + current_indexes).map(&:aliased_name).uniq
|
|
30
|
+
all_aliased_names.map do |aliased_name|
|
|
31
|
+
wanted_index = wanted_indexes.select { |i| i.aliased_name == aliased_name }.first
|
|
32
|
+
current_index = current_indexes.select { |i| i.aliased_name == aliased_name }.first
|
|
33
|
+
|
|
34
|
+
next if wanted_index.try(:external)
|
|
35
|
+
|
|
36
|
+
case [!wanted_index.nil?, !current_index.nil?]
|
|
37
|
+
when [true, false] then Op.new(wanted_index, :create)
|
|
38
|
+
when [false, true] then Op.new(current_index, :delete)
|
|
39
|
+
when [true, true] then
|
|
40
|
+
case wanted_index.same_definition?(current_index)
|
|
41
|
+
when true then nil # up to date
|
|
42
|
+
when false then Op.new(current_index, :update, [wanted_index])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end.compact
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def generate_plan
|
|
49
|
+
@models_indexes_map.map { |model, indexes| _generate_plan_for(model, indexes) }.flatten(1)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sync_indexes(options={})
|
|
53
|
+
plan = generate_plan
|
|
54
|
+
plan.each { |op| op.run(options) }
|
|
55
|
+
unless options[:wait] == false
|
|
56
|
+
models = plan.map(&:index).map(&:model).uniq
|
|
57
|
+
models.each { |model| NoBrainer.run(model.rql_table.index_wait()) }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class << self
|
|
62
|
+
def instance
|
|
63
|
+
new(NoBrainer::Document.all)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
delegate :sync_indexes, :to => :instance
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
module NoBrainer::Document::Index
|
|
2
|
-
VALID_INDEX_OPTIONS = [:multi, :as]
|
|
2
|
+
VALID_INDEX_OPTIONS = [:external, :geo, :multi, :as]
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
|
+
extend NoBrainer::Autoload
|
|
5
|
+
|
|
6
|
+
autoload :Index, :Synchronizer, :MetaStore
|
|
4
7
|
|
|
5
8
|
included do
|
|
6
9
|
cattr_accessor :indexes, :instance_accessor => false
|
|
@@ -16,9 +19,9 @@ module NoBrainer::Document::Index
|
|
|
16
19
|
raise "Too many arguments: #{args}" if args.size > 1
|
|
17
20
|
|
|
18
21
|
kind, what = case args.first
|
|
19
|
-
when nil
|
|
20
|
-
when Array
|
|
21
|
-
when Proc
|
|
22
|
+
when nil then [:single, name.to_sym]
|
|
23
|
+
when Array then [:compound, args.first.map(&:to_sym)]
|
|
24
|
+
when Proc then [:proc, args.first]
|
|
22
25
|
else raise "Index argument must be a lambda or a list of fields"
|
|
23
26
|
end
|
|
24
27
|
|
|
@@ -35,7 +38,8 @@ module NoBrainer::Document::Index
|
|
|
35
38
|
as ||= name
|
|
36
39
|
as = as.to_sym
|
|
37
40
|
|
|
38
|
-
indexes[name] =
|
|
41
|
+
indexes[name] = NoBrainer::Document::Index::Index.new(self.root_class, name, as,
|
|
42
|
+
kind, what, options[:external], options[:geo], options[:multi], nil)
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
def remove_index(name)
|
|
@@ -46,12 +50,8 @@ module NoBrainer::Document::Index
|
|
|
46
50
|
!!indexes[name.to_sym]
|
|
47
51
|
end
|
|
48
52
|
|
|
49
|
-
def lookup_index_alias(attr)
|
|
50
|
-
indexes[attr.to_sym].try(:[], :as) || attr
|
|
51
|
-
end
|
|
52
|
-
|
|
53
53
|
def _field(attr, options={})
|
|
54
|
-
if has_index?(attr) && indexes[attr]
|
|
54
|
+
if has_index?(attr) && indexes[attr].kind != :single
|
|
55
55
|
raise "Cannot reuse index attr #{attr}"
|
|
56
56
|
end
|
|
57
57
|
|
|
@@ -68,71 +68,12 @@ module NoBrainer::Document::Index
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def _remove_field(attr, options={})
|
|
71
|
-
super
|
|
72
71
|
remove_index(attr) if fields[attr][:index]
|
|
72
|
+
super
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
index_args = self.indexes[index_name]
|
|
78
|
-
|
|
79
|
-
index_proc = case index_args[:kind]
|
|
80
|
-
when :single then ->(doc) { doc[lookup_field_alias(index_name)] }
|
|
81
|
-
when :compound then ->(doc) { index_args[:what].map { |field| doc[lookup_field_alias(field)] } }
|
|
82
|
-
when :proc then index_args[:what]
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
NoBrainer.run(self.rql_table.index_create(index_args[:as], index_args[:options], &index_proc))
|
|
86
|
-
wait_for_index(index_name) unless options[:wait] == false
|
|
87
|
-
|
|
88
|
-
if options[:verbose]
|
|
89
|
-
if index_name == index_args[:as]
|
|
90
|
-
STDERR.puts "Created index #{self}.#{index_name}"
|
|
91
|
-
else
|
|
92
|
-
STDERR.puts "Created index #{self}.#{index_name} as #{index_args[:as]}"
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def perform_drop_index(index_name, options={})
|
|
98
|
-
aliased_name = self.indexes[index_name].try(:[], :as) || index_name
|
|
99
|
-
NoBrainer.run(self.rql_table.index_drop(aliased_name))
|
|
100
|
-
|
|
101
|
-
if options[:verbose]
|
|
102
|
-
if index_name == index_args[:as]
|
|
103
|
-
STDERR.puts "Dropped index #{self}.#{index_name}"
|
|
104
|
-
else
|
|
105
|
-
STDERR.puts "Dreated index #{self}.#{index_name} as #{index_args[:as]}"
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def get_index_alias_reverse_map
|
|
111
|
-
Hash[self.indexes.map { |k,v| [v[:as], k] }].tap do |mapping|
|
|
112
|
-
raise "Detected clashing index aliases" if mapping.count != self.indexes.count
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def perform_update_indexes(options={})
|
|
117
|
-
alias_mapping = self.get_index_alias_reverse_map
|
|
118
|
-
current_indexes = NoBrainer.run(self.rql_table.index_list).map do |index|
|
|
119
|
-
alias_mapping[index.to_sym] || index.to_sym
|
|
120
|
-
end
|
|
121
|
-
wanted_indexes = self.indexes.keys - [self.pk_name]
|
|
122
|
-
|
|
123
|
-
(current_indexes - wanted_indexes).each do |index_name|
|
|
124
|
-
perform_drop_index(index_name, options)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
(wanted_indexes - current_indexes).each do |index_name|
|
|
128
|
-
perform_create_index(index_name, options)
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
alias_method :update_indexes, :perform_update_indexes
|
|
132
|
-
|
|
133
|
-
def wait_for_index(index_name=nil, options={})
|
|
134
|
-
args = [index_name].compact
|
|
135
|
-
NoBrainer.run(self.rql_table.index_wait(*args))
|
|
75
|
+
def lookup_index_alias(attr)
|
|
76
|
+
indexes[attr.to_sym].try(:aliased_name) || attr
|
|
136
77
|
end
|
|
137
78
|
end
|
|
138
79
|
end
|
|
@@ -19,7 +19,7 @@ module NoBrainer::Document::LazyFetch
|
|
|
19
19
|
lazy_fetch = self.class.fields_to_lazy_fetch.to_a
|
|
20
20
|
return super unless lazy_fetch.present?
|
|
21
21
|
return super if options[:pluck]
|
|
22
|
-
super(options.
|
|
22
|
+
super(options.deep_merge(:without => lazy_fetch, :lazy_fetch => lazy_fetch))
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
module ClassMethods
|
|
@@ -31,12 +31,12 @@ module NoBrainer::Document::LazyFetch
|
|
|
31
31
|
def _field(attr, options={})
|
|
32
32
|
super
|
|
33
33
|
attr = attr.to_s
|
|
34
|
-
|
|
34
|
+
model = self
|
|
35
35
|
inject_in_layer :lazy_fetch do
|
|
36
36
|
if options[:lazy_fetch]
|
|
37
|
-
|
|
37
|
+
model.for_each_subclass { |_model| _model.fields_to_lazy_fetch << attr }
|
|
38
38
|
else
|
|
39
|
-
|
|
39
|
+
model.for_each_subclass { |_model| _model.fields_to_lazy_fetch.delete(attr) }
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# Lazy loading can also specified through criteria.
|
|
@@ -57,7 +57,7 @@ module NoBrainer::Document::LazyFetch
|
|
|
57
57
|
|
|
58
58
|
def _remove_field(attr, options={})
|
|
59
59
|
super
|
|
60
|
-
for_each_subclass { |
|
|
60
|
+
for_each_subclass { |model| model.fields_to_lazy_fetch.delete(attr) }
|
|
61
61
|
inject_in_layer :lazy_fetch do
|
|
62
62
|
remove_method("#{attr}") if method_defined?("#{attr}")
|
|
63
63
|
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
|
|
@@ -60,20 +60,22 @@ module NoBrainer::Document::Persistance
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def _create(options={})
|
|
63
|
-
return false if options[:validate] && !valid?
|
|
64
|
-
|
|
65
|
-
self.
|
|
63
|
+
return false if options[:validate] != false && !valid?(nil, :clear_errors => false)
|
|
64
|
+
|
|
65
|
+
attrs = self.class.persistable_attributes(@_attributes, :instance => self)
|
|
66
|
+
result = NoBrainer.run(self.class.rql_table.insert(attrs))
|
|
67
|
+
self.pk_value ||= result['generated_keys'].to_a.first
|
|
66
68
|
@new_record = false
|
|
67
69
|
true
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
def _update(attrs)
|
|
71
|
-
|
|
72
|
-
NoBrainer.run { selector.update(
|
|
73
|
+
rql = ->(doc){ self.class.persistable_attributes(attrs, :instance => self, :rql_doc => doc) }
|
|
74
|
+
NoBrainer.run { selector.update(&rql) }
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
def _update_only_changed_attrs(options={})
|
|
76
|
-
return false if options[:validate] && !valid?
|
|
78
|
+
return false if options[:validate] != false && !valid?(nil, :clear_errors => false)
|
|
77
79
|
|
|
78
80
|
# We won't be using the `changes` values, because they went through
|
|
79
81
|
# read_attribute(), and we want the raw values.
|
|
@@ -88,23 +90,42 @@ module NoBrainer::Document::Persistance
|
|
|
88
90
|
true
|
|
89
91
|
end
|
|
90
92
|
|
|
91
|
-
def
|
|
92
|
-
options = options.reverse_merge(:validate => true)
|
|
93
|
+
def _save?(options)
|
|
93
94
|
new_record? ? _create(options) : _update_only_changed_attrs(options)
|
|
94
95
|
end
|
|
95
96
|
|
|
97
|
+
def save?(options={})
|
|
98
|
+
errors.clear
|
|
99
|
+
_save?(options)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def save(*args)
|
|
103
|
+
save?(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
96
107
|
def save!(*args)
|
|
97
|
-
save(*args)
|
|
108
|
+
save(*args)
|
|
109
|
+
:you_should_be_using_the_non_bang_version_of_save
|
|
98
110
|
end
|
|
99
111
|
|
|
100
|
-
def
|
|
112
|
+
def update?(attrs, options={})
|
|
101
113
|
assign_attributes(attrs, options)
|
|
102
|
-
save(options)
|
|
114
|
+
save?(options)
|
|
103
115
|
end
|
|
116
|
+
alias_method :update_attributes?, :update?
|
|
104
117
|
|
|
105
|
-
def
|
|
106
|
-
|
|
118
|
+
def update(*args)
|
|
119
|
+
update?(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
|
120
|
+
nil
|
|
107
121
|
end
|
|
122
|
+
alias_method :update_attributes, :update
|
|
123
|
+
|
|
124
|
+
def update!(*args)
|
|
125
|
+
update(*args)
|
|
126
|
+
:you_should_be_using_the_non_bang_version_of_update
|
|
127
|
+
end
|
|
128
|
+
alias_method :update_attributes!, :update!
|
|
108
129
|
|
|
109
130
|
def delete
|
|
110
131
|
unless @destroyed
|
|
@@ -124,10 +145,6 @@ module NoBrainer::Document::Persistance
|
|
|
124
145
|
new(attrs, options).tap { |doc| doc.save(options) }
|
|
125
146
|
end
|
|
126
147
|
|
|
127
|
-
def create!(attrs={}, options={})
|
|
128
|
-
new(attrs, options).tap { |doc| doc.save!(options) }
|
|
129
|
-
end
|
|
130
|
-
|
|
131
148
|
def insert_all(*args)
|
|
132
149
|
docs = args.shift
|
|
133
150
|
docs = [docs] unless docs.is_a?(Array)
|
|
@@ -140,16 +157,16 @@ module NoBrainer::Document::Persistance
|
|
|
140
157
|
NoBrainer.run(rql_table.sync)['synced'] == 1
|
|
141
158
|
end
|
|
142
159
|
|
|
143
|
-
def persistable_key(k)
|
|
160
|
+
def persistable_key(k, options={})
|
|
144
161
|
k
|
|
145
162
|
end
|
|
146
163
|
|
|
147
|
-
def persistable_value(k, v)
|
|
164
|
+
def persistable_value(k, v, options={})
|
|
148
165
|
v
|
|
149
166
|
end
|
|
150
167
|
|
|
151
|
-
def persistable_attributes(attrs)
|
|
152
|
-
Hash[attrs.map { |k,v| [persistable_key(k), persistable_value(k, v)] }]
|
|
168
|
+
def persistable_attributes(attrs, options={})
|
|
169
|
+
Hash[attrs.map { |k,v| [persistable_key(k, options), persistable_value(k, v, options)] }]
|
|
153
170
|
end
|
|
154
171
|
end
|
|
155
172
|
end
|
|
@@ -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
|
|
@@ -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,13 +53,15 @@ 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
|
|
|
60
60
|
def _field(attr, options={})
|
|
61
61
|
super
|
|
62
62
|
|
|
63
|
+
return unless options[:type]
|
|
64
|
+
|
|
63
65
|
NoBrainer::Document::Types.load_type_extensions(options[:type]) if options[:type]
|
|
64
66
|
|
|
65
67
|
inject_in_layer :types do
|
|
@@ -105,13 +107,13 @@ module NoBrainer::Document::Types
|
|
|
105
107
|
class << self
|
|
106
108
|
mattr_accessor :loaded_extensions
|
|
107
109
|
self.loaded_extensions = Set.new
|
|
108
|
-
def load_type_extensions(
|
|
109
|
-
unless loaded_extensions.include?(
|
|
110
|
+
def load_type_extensions(model)
|
|
111
|
+
unless loaded_extensions.include?(model)
|
|
110
112
|
begin
|
|
111
|
-
require File.join(File.dirname(__FILE__), 'types',
|
|
113
|
+
require File.join(File.dirname(__FILE__), 'types', model.name.underscore)
|
|
112
114
|
rescue LoadError
|
|
113
115
|
end
|
|
114
|
-
loaded_extensions <<
|
|
116
|
+
loaded_extensions << model
|
|
115
117
|
end
|
|
116
118
|
end
|
|
117
119
|
end
|
|
@@ -66,10 +66,10 @@ module NoBrainer::Document::Uniqueness
|
|
|
66
66
|
|
|
67
67
|
def initialize(options={})
|
|
68
68
|
super
|
|
69
|
-
|
|
69
|
+
model = options[:class]
|
|
70
70
|
self.scope = [*options[:scope]]
|
|
71
|
-
([
|
|
72
|
-
|
|
71
|
+
([model] + model.descendants).each do |_model|
|
|
72
|
+
_model.unique_validators << self
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
@@ -5,19 +5,28 @@ module NoBrainer::Document::Validation
|
|
|
5
5
|
|
|
6
6
|
included do
|
|
7
7
|
# We don't want before_validation returning false to halt the chain.
|
|
8
|
-
define_callbacks :validation, :skip_after_callbacks_if_terminated => true,
|
|
9
|
-
:terminator => proc { false }
|
|
8
|
+
define_callbacks :validation, :skip_after_callbacks_if_terminated => true,
|
|
9
|
+
:scope => [:kind, :name], :terminator => proc { false }
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def valid?(context=nil)
|
|
13
|
-
|
|
12
|
+
def valid?(context=nil, options={})
|
|
13
|
+
context ||= new_record? ? :create : :update
|
|
14
|
+
|
|
15
|
+
# copy/pasted, because we need to have control on errors.clear
|
|
16
|
+
current_context, self.validation_context = validation_context, context
|
|
17
|
+
errors.clear unless options[:clear_errors] == false
|
|
18
|
+
run_validations!
|
|
19
|
+
ensure
|
|
20
|
+
self.validation_context = current_context
|
|
14
21
|
end
|
|
15
22
|
|
|
16
23
|
module ClassMethods
|
|
17
24
|
def _field(attr, options={})
|
|
18
25
|
super
|
|
26
|
+
validates(attr, { :format => { :with => options[:format] } }) if options.has_key?(:format)
|
|
19
27
|
validates(attr, { :presence => options[:required] }) if options.has_key?(:required)
|
|
20
28
|
validates(attr, { :uniqueness => options[:unique] }) if options.has_key?(:unique)
|
|
29
|
+
validates(attr, { :uniqueness => options[:uniq] }) if options.has_key?(:uniq)
|
|
21
30
|
validates(attr, { :inclusion => {:in => options[:in]} }) if options.has_key?(:in)
|
|
22
31
|
validates(attr, options[:validates]) if options[:validates]
|
|
23
32
|
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
|
|
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
|
data/lib/no_brainer/fork.rb
CHANGED
|
@@ -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
|
|
@@ -2,24 +2,25 @@ class NoBrainer::QueryRunner::DatabaseOnDemand < NoBrainer::QueryRunner::Middlew
|
|
|
2
2
|
def call(env)
|
|
3
3
|
@runner.call(env)
|
|
4
4
|
rescue RuntimeError => e
|
|
5
|
-
if database_name =
|
|
5
|
+
if database_name = handle_database_on_demand_exception?(env, e)
|
|
6
6
|
auto_create_database(env, database_name)
|
|
7
7
|
retry
|
|
8
8
|
end
|
|
9
9
|
raise
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def
|
|
13
|
-
NoBrainer::Config.auto_create_databases
|
|
12
|
+
def handle_database_on_demand_exception?(env, e)
|
|
13
|
+
(NoBrainer::Config.auto_create_databases || env[:auto_create_databases]) &&
|
|
14
|
+
e.message =~ /^Database `(.+)` does not exist\.$/ && $1
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
private
|
|
17
18
|
|
|
18
19
|
def auto_create_database(env, database_name)
|
|
19
|
-
if env[:
|
|
20
|
+
if env[:last_auto_create_database] == database_name
|
|
20
21
|
raise "Auto database creation is not working with #{database_name}"
|
|
21
22
|
end
|
|
22
|
-
env[:
|
|
23
|
+
env[:last_auto_create_database] = database_name
|
|
23
24
|
|
|
24
25
|
NoBrainer.db_create(database_name)
|
|
25
26
|
rescue RuntimeError => e
|
|
@@ -10,8 +10,12 @@ class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
|
|
|
10
10
|
private
|
|
11
11
|
|
|
12
12
|
def log_query(env, start_time, exception=nil)
|
|
13
|
-
return if
|
|
14
|
-
|
|
13
|
+
return if handle_on_demand_exception?(env, exception)
|
|
14
|
+
|
|
15
|
+
not_indexed = env[:criteria] && env[:criteria].where_present? &&
|
|
16
|
+
!env[:criteria].where_indexed? &&
|
|
17
|
+
!env[:criteria].model.try(:perf_warnings_disabled)
|
|
18
|
+
|
|
15
19
|
level = exception ? Logger::ERROR :
|
|
16
20
|
not_indexed ? Logger::INFO : Logger::DEBUG
|
|
17
21
|
return if NoBrainer.logger.nil? || NoBrainer.logger.level > level
|
|
@@ -19,7 +23,7 @@ class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
|
|
|
19
23
|
duration = Time.now - start_time
|
|
20
24
|
|
|
21
25
|
msg_duration = (duration * 1000.0).round(1).to_s
|
|
22
|
-
msg_duration = " " * [0,
|
|
26
|
+
msg_duration = " " * [0, 6 - msg_duration.size].max + msg_duration
|
|
23
27
|
msg_duration = "[#{msg_duration}ms] "
|
|
24
28
|
|
|
25
29
|
msg_db = "[#{env[:db_name]}] " if env[:db_name] && env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
|
|
@@ -49,9 +53,9 @@ class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
|
|
|
49
53
|
NoBrainer.logger.add(level, msg)
|
|
50
54
|
end
|
|
51
55
|
|
|
52
|
-
def
|
|
56
|
+
def handle_on_demand_exception?(env, e)
|
|
53
57
|
# pretty gross I must say.
|
|
54
|
-
e && (NoBrainer::QueryRunner::DatabaseOnDemand.new(nil).
|
|
55
|
-
NoBrainer::QueryRunner::TableOnDemand.new(nil).
|
|
58
|
+
e && (NoBrainer::QueryRunner::DatabaseOnDemand.new(nil).handle_database_on_demand_exception?(env, e) ||
|
|
59
|
+
NoBrainer::QueryRunner::TableOnDemand.new(nil).handle_table_on_demand_exception?(env, e))
|
|
56
60
|
end
|
|
57
61
|
end
|
|
@@ -7,13 +7,14 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
|
|
|
7
7
|
database_name = $2
|
|
8
8
|
table_name = $3
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
model = NoBrainer::Document.all.select { |m| m.table_name == table_name }.first
|
|
11
|
+
index = model.indexes.values.select { |i| i.aliased_name == index_name.to_sym }.first if model
|
|
12
|
+
index_name = index.name if index
|
|
12
13
|
|
|
13
|
-
if
|
|
14
|
+
if model.try(:pk_name).try(:to_s) == index_name
|
|
14
15
|
err_msg = "Please update the primary key `#{index_name}` in the table `#{database_name}.#{table_name}`."
|
|
15
16
|
else
|
|
16
|
-
err_msg = "Please run `NoBrainer.
|
|
17
|
+
err_msg = "Please run `NoBrainer.sync_indexes' or `rake nobrainer:sync_indexes' to create the index `#{index_name}`"
|
|
17
18
|
err_msg += " in the table `#{database_name}.#{table_name}`."
|
|
18
19
|
err_msg += " Read http://nobrainer.io/docs/indexes for more information."
|
|
19
20
|
end
|