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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/config.rb +53 -28
  3. data/lib/no_brainer/connection.rb +1 -0
  4. data/lib/no_brainer/connection_manager.rb +60 -0
  5. data/lib/no_brainer/criteria/after_find.rb +3 -11
  6. data/lib/no_brainer/criteria/aggregate.rb +8 -5
  7. data/lib/no_brainer/criteria/cache.rb +7 -7
  8. data/lib/no_brainer/criteria/core.rb +56 -16
  9. data/lib/no_brainer/criteria/count.rb +1 -1
  10. data/lib/no_brainer/criteria/delete.rb +1 -1
  11. data/lib/no_brainer/criteria/extend.rb +19 -0
  12. data/lib/no_brainer/criteria/first.rb +1 -1
  13. data/lib/no_brainer/criteria/index.rb +7 -13
  14. data/lib/no_brainer/criteria/limit.rb +5 -13
  15. data/lib/no_brainer/criteria/order_by.rb +22 -41
  16. data/lib/no_brainer/criteria/pluck.rb +17 -23
  17. data/lib/no_brainer/criteria/preload.rb +9 -15
  18. data/lib/no_brainer/criteria/raw.rb +5 -11
  19. data/lib/no_brainer/criteria/scope.rb +9 -15
  20. data/lib/no_brainer/criteria/update.rb +3 -3
  21. data/lib/no_brainer/criteria/where.rb +33 -56
  22. data/lib/no_brainer/criteria.rb +1 -1
  23. data/lib/no_brainer/document/aliases.rb +1 -1
  24. data/lib/no_brainer/document/association/belongs_to.rb +6 -6
  25. data/lib/no_brainer/document/association/core.rb +11 -11
  26. data/lib/no_brainer/document/association/eager_loader.rb +4 -3
  27. data/lib/no_brainer/document/association/has_many.rb +7 -7
  28. data/lib/no_brainer/document/association/has_many_through.rb +1 -1
  29. data/lib/no_brainer/document/association/has_one.rb +1 -1
  30. data/lib/no_brainer/document/association.rb +5 -5
  31. data/lib/no_brainer/document/atomic_ops.rb +213 -0
  32. data/lib/no_brainer/document/attributes.rb +19 -10
  33. data/lib/no_brainer/document/callbacks.rb +1 -1
  34. data/lib/no_brainer/document/core.rb +1 -1
  35. data/lib/no_brainer/document/criteria.rb +9 -2
  36. data/lib/no_brainer/document/dirty.rb +22 -22
  37. data/lib/no_brainer/document/dynamic_attributes.rb +2 -14
  38. data/lib/no_brainer/document/id.rb +1 -0
  39. data/lib/no_brainer/document/index/index.rb +83 -0
  40. data/lib/no_brainer/document/index/meta_store.rb +31 -0
  41. data/lib/no_brainer/document/index/synchronizer.rb +68 -0
  42. data/lib/no_brainer/document/index.rb +13 -72
  43. data/lib/no_brainer/document/lazy_fetch.rb +5 -5
  44. data/lib/no_brainer/document/missing_attributes.rb +6 -23
  45. data/lib/no_brainer/document/persistance.rb +38 -21
  46. data/lib/no_brainer/document/polymorphic.rb +1 -1
  47. data/lib/no_brainer/document/types/set.rb +23 -0
  48. data/lib/no_brainer/document/types.rb +8 -6
  49. data/lib/no_brainer/document/uniqueness.rb +3 -3
  50. data/lib/no_brainer/document/validation.rb +13 -4
  51. data/lib/no_brainer/document.rb +1 -1
  52. data/lib/no_brainer/error.rb +14 -0
  53. data/lib/no_brainer/fork.rb +1 -0
  54. data/lib/no_brainer/query_runner/connection_lock.rb +5 -1
  55. data/lib/no_brainer/query_runner/database_on_demand.rb +6 -5
  56. data/lib/no_brainer/query_runner/logger.rb +10 -6
  57. data/lib/no_brainer/query_runner/missing_index.rb +5 -4
  58. data/lib/no_brainer/query_runner/reconnect.rb +20 -17
  59. data/lib/no_brainer/query_runner/run_options.rb +3 -0
  60. data/lib/no_brainer/query_runner/table_on_demand.rb +11 -8
  61. data/lib/no_brainer/railtie/database.rake +12 -12
  62. data/lib/no_brainer/railtie.rb +5 -5
  63. data/lib/no_brainer/rql.rb +9 -0
  64. data/lib/nobrainer.rb +9 -23
  65. metadata +10 -4
  66. 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 then [:single, name.to_sym]
20
- when Array then [:compound, args.first.map(&:to_sym)]
21
- when Proc then [:proc, args.first]
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] = {:kind => kind, :what => what, :as => as, :options => options}
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][:kind] != :single
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 perform_create_index(index_name, options={})
76
- index_name = index_name.to_sym
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.merge(:without => lazy_fetch, :lazy_fetch => lazy_fetch))
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
- klass = self
34
+ model = self
35
35
  inject_in_layer :lazy_fetch do
36
36
  if options[:lazy_fetch]
37
- klass.for_each_subclass { |_klass| _klass.fields_to_lazy_fetch << attr }
37
+ model.for_each_subclass { |_model| _model.fields_to_lazy_fetch << attr }
38
38
  else
39
- klass.for_each_subclass { |_klass| _klass.fields_to_lazy_fetch.delete(attr) }
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 { |klass| klass.fields_to_lazy_fetch.delete(attr) }
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
- module ClassMethods
50
- def _field(attr, options={})
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
- keys = self.class.insert_all(@_attributes)
65
- self.pk_value ||= keys.first
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
- attrs = self.class.persistable_attributes(attrs)
72
- NoBrainer.run { selector.update(attrs) }
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 save(options={})
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) or raise NoBrainer::Error::DocumentInvalid, self
108
+ save(*args)
109
+ :you_should_be_using_the_non_bang_version_of_save
98
110
  end
99
111
 
100
- def update_attributes(attrs, options={})
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 update_attributes!(*args)
106
- update_attributes(*args) or raise NoBrainer::Error::DocumentInvalid, self
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
@@ -36,7 +36,7 @@ module NoBrainer::Document::Polymorphic
36
36
  for_each_subclass.map(&:type_value)
37
37
  end
38
38
 
39
- def klass_from_attrs(attrs)
39
+ def model_from_attrs(attrs)
40
40
  attrs['_type'].try(:constantize) || root_class
41
41
  end
42
42
 
@@ -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(klass)
109
- unless loaded_extensions.include?(klass)
110
+ def load_type_extensions(model)
111
+ unless loaded_extensions.include?(model)
110
112
  begin
111
- require File.join(File.dirname(__FILE__), 'types', klass.name.underscore)
113
+ require File.join(File.dirname(__FILE__), 'types', model.name.underscore)
112
114
  rescue LoadError
113
115
  end
114
- loaded_extensions << klass
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
- klass = options[:class]
69
+ model = options[:class]
70
70
  self.scope = [*options[:scope]]
71
- ([klass] + klass.descendants).each do |_klass|
72
- _klass.unique_validators << self
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, :scope => [:kind, :name],
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
- super(context || (new_record? ? :create : :update))
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
@@ -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
 
@@ -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
@@ -4,6 +4,7 @@ module NoBrainer::Fork
4
4
  alias_method :fork_without_nobrainer, :fork
5
5
 
6
6
  def fork(&block)
7
+ # Not so safe to disconnect in the child (c.f. driver's code)
7
8
  NoBrainer.disconnect
8
9
  fork_without_nobrainer(&block)
9
10
  end
@@ -2,6 +2,10 @@ class NoBrainer::QueryRunner::ConnectionLock < NoBrainer::QueryRunner::Middlewar
2
2
  @@lock = Mutex.new
3
3
 
4
4
  def call(env)
5
- @@lock.synchronize { @runner.call(env) }
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 = database_on_demand_exception?(e)
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 database_on_demand_exception?(e)
13
- NoBrainer::Config.auto_create_databases && e.message =~ /^Database `(.+)` does not exist\.$/ && $1
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[:auto_create_database] == database_name
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[:auto_create_database] = database_name
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 on_demand_exception?(exception)
14
- not_indexed = env[:criteria] && env[:criteria].where_present? && !env[:criteria].where_indexed?
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, 5 - msg_duration.size].max + msg_duration
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 on_demand_exception?(e)
56
+ def handle_on_demand_exception?(env, e)
53
57
  # pretty gross I must say.
54
- e && (NoBrainer::QueryRunner::DatabaseOnDemand.new(nil).database_on_demand_exception?(e) ||
55
- NoBrainer::QueryRunner::TableOnDemand.new(nil).table_on_demand_exception?(e))
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
- klass = NoBrainer::Document.all.select { |m| m.table_name == table_name }.first
11
- index_name = klass.get_index_alias_reverse_map[index_name.to_sym]
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 klass && klass.pk_name.to_s == index_name
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.update_indexes' or `rake db:update_indexes' to create the index `#{index_name}`"
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