nobrainer 0.24.0 → 0.25.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56337a0aa87999793ca7bc6ed3e1de79ef76b427
4
- data.tar.gz: 6740efd7304818a5a71638a639b48301d16eb602
3
+ metadata.gz: 950e98b70408c2d1fc05a6ee2e80f1f3e942999b
4
+ data.tar.gz: 5b4ec8da15e3d9f48e1729f07cd336af12c27433
5
5
  SHA512:
6
- metadata.gz: 69a4a851eb9e3a497a2bff26c65933437e3155b23f6573e1a9d7fcaa04980f59964b7d6f59383e3c7b9bb27014ab95eda52ca8be38d4b4f82144d2c389e211b6
7
- data.tar.gz: 03968de9d8391cd208938420fa19d1aa1f2c76191bf938132f5c04dce33f673e725f5336390c010bb70cf3ff660a79a0b7dd72cc27bca994ba6411445f797948
6
+ metadata.gz: 3719c006243290324956ec397d700cae2766598460423199bfc9ed3f731761b2d6fceec50365f2a839099b2edc3fb751aa88984a349aab4ef2132b6547444f09
7
+ data.tar.gz: 02bc9ef3d13a887305d6fdd81a7f1eebd296a63c206f9347bce5e8078f3af71c36e625d9ebe5ac0ce55a93da24b5760842ef3e2b6e55738d38df189d6384c731
@@ -42,17 +42,23 @@ class NoBrainer::Connection
42
42
  RUBY
43
43
  end
44
44
 
45
+ def default_db
46
+ parsed_uri[:db]
47
+ end
48
+
49
+ def current_db
50
+ NoBrainer.current_run_options.try(:[], :db) || default_db
51
+ end
52
+
45
53
  def drop!
46
- # XXX Thread.current[:nobrainer_options] is set by NoBrainer::QueryRunner::RunOptions
47
- db = (Thread.current[:nobrainer_options] || parsed_uri)[:db]
48
- db_drop(db)['dropped'] == 1
54
+ db_drop(current_db)['dropped'] == 1
49
55
  end
50
56
 
51
- # Note that truncating each table (purge) is much faster than dropping the
52
- # database (drop)
57
+ # Note that truncating each table (purge!) is much faster than dropping the database (drop!)
53
58
  def purge!
54
59
  table_list.each do |table_name|
55
- next if table_name == 'nobrainer_index_meta' # keeping because indexes are not going away
60
+ # keeping the index meta store because indexes are not going away
61
+ next if table_name == NoBrainer::Document::Index::MetaStore.table_name
56
62
  NoBrainer.run { |r| r.table(table_name).delete }
57
63
  end
58
64
  true
@@ -77,4 +77,12 @@ module NoBrainer::Criteria::Cache
77
77
 
78
78
  use_cache_for :first, :last, :count, :empty?, :any?
79
79
  reload_on :update_all, :destroy_all, :delete_all
80
+
81
+ private
82
+
83
+ def apply_named_scope(name, args, block)
84
+ return super unless with_cache?
85
+ @scope_cache ||= {}
86
+ @scope_cache[[name, args, block]] ||= super
87
+ end
80
88
  end
@@ -56,11 +56,6 @@ module NoBrainer::Criteria::Core
56
56
  merge(self.class.new(options), merge_options)
57
57
  end
58
58
 
59
- def run(&block)
60
- block ||= proc { to_rql }
61
- NoBrainer.run(:criteria => self, &block)
62
- end
63
-
64
59
  def compile_rql_pass1
65
60
  # This method is overriden by other modules.
66
61
  raise "Criteria not bound to a model" unless model
@@ -105,5 +100,9 @@ module NoBrainer::Criteria::Core
105
100
  def append_array(a, b)
106
101
  a ? a+b : b
107
102
  end
103
+
104
+ def merge_hash(a, b)
105
+ a ? a.merge(b) : b
106
+ end
108
107
  end
109
108
  end
@@ -15,7 +15,7 @@ module NoBrainer::Criteria::EagerLoad
15
15
  def merge!(criteria, options={})
16
16
  super.tap do
17
17
  # If we already have some cached documents, and we need to so some eager
18
- # loading, then we it now. It's easier than doing it lazily.
18
+ # loading, then we do it now. It's easier than doing it lazily.
19
19
  if self.cached? && criteria.options[:eager_load].present?
20
20
  perform_eager_load(@cache)
21
21
  end
@@ -1,27 +1,24 @@
1
1
  module NoBrainer::Criteria::Find
2
2
  extend ActiveSupport::Concern
3
3
 
4
- def find_by?(*args, &block)
5
- where(*args, &block).first
6
- end
7
-
8
4
  def find_by(*args, &block)
9
- find_by?(*args, &block).tap { |doc| raise_not_found(args) unless doc }
5
+ raise "find_by() has unclear semantics. Please use where().first instead"
10
6
  end
11
7
  alias_method :find_by!, :find_by
8
+ alias_method :find_by?, :find_by
12
9
 
13
10
  def find?(pk)
14
- without_ordering.find_by?(model.pk_name => pk)
11
+ without_ordering.where(model.pk_name => pk).first
15
12
  end
16
13
 
17
14
  def find(pk)
18
- without_ordering.find_by(model.pk_name => pk)
15
+ find?(pk).tap { |doc| raise_not_found(pk) unless doc }
19
16
  end
20
17
  alias_method :find!, :find
21
18
 
22
19
  private
23
20
 
24
- def raise_not_found(args)
25
- raise NoBrainer::Error::DocumentNotFound, "#{model} #{args.inspect.gsub(/\[{(.*)}\]/, '\1')} not found"
21
+ def raise_not_found(pk)
22
+ raise NoBrainer::Error::DocumentNotFound, "#{model} :#{model.pk_name}=>#{pk.inspect} not found"
26
23
  end
27
24
  end
@@ -0,0 +1,101 @@
1
+ module NoBrainer::Criteria::FirstOrCreate
2
+ extend ActiveSupport::Concern
3
+
4
+ def first_or_create(create_params={}, &block)
5
+ _first_or_create(create_params, :save_method => :save?, &block)
6
+ end
7
+
8
+ def first_or_create!(create_params={}, &block)
9
+ _first_or_create(create_params, :save_method => :save!, &block)
10
+ end
11
+
12
+ private
13
+
14
+ def _first_or_create(create_params={}, options={}, &block)
15
+ raise "Cannot use .raw() with .first_or_create()" if raw?
16
+ raise "Use first_or_create() on the root class `#{model.root_class}'" unless model.is_root_class?
17
+
18
+ where_params = extract_where_params()
19
+ keys = where_params.keys
20
+
21
+ # When matching on the primary key, we'll just pretend that we have a
22
+ # uniqueness validator on it. We will be racy against other create(),
23
+ # but not on first_or_create().
24
+ # And if we get caught in a race with another create(), we'll just have a
25
+ # duplicate primary key exception.
26
+ matched_validator = true if keys == [model.pk_name]
27
+ matched_validator ||= !!get_uniqueness_validators_map[keys.sort]
28
+ unless matched_validator
29
+ # We could do without a uniqueness validator, but it's much preferable to
30
+ # have it, so that we don't conflict with others create(), not just others
31
+ # first_or_create().
32
+ raise "Please add the following uniqueness validator for first_or_create():\n" +
33
+ "class #{model}\n" +
34
+ case keys.size
35
+ when 1 then " field :#{keys.first}, :uniq => true"
36
+ when 2 then " field :#{keys.first}, :uniq => {:scope => :#{keys.last}}"
37
+ else " field :#{keys.first}, :uniq => {:scope => #{keys[1..-1].inspect}}"
38
+ end +
39
+ "\nend"
40
+ end
41
+
42
+ # We don't want to access create_params yet, because invoking the block
43
+ # might be costly (the user might be doing some API call or w/e), and
44
+ # so we want to invoke the block only if necessary.
45
+ new_instance = model.new(where_params)
46
+ lock_key_name = model._uniqueness_key_name_from_params(where_params)
47
+ new_instance._lock_for_uniqueness_once(lock_key_name)
48
+
49
+ old_instance = self.first
50
+ return old_instance if old_instance
51
+
52
+ create_params = block.call if block
53
+ create_params = create_params.symbolize_keys
54
+
55
+ keys_in_conflict = create_params.keys & where_params.keys
56
+ keys_in_conflict = keys_in_conflict.select { |k| create_params[k] == where_params[k] }
57
+ unless keys_in_conflict.empty?
58
+ raise "where() and first_or_create() were given conflicting values " +
59
+ "on the following keys: #{keys_in_conflict.inspect}"
60
+ end
61
+
62
+ if create_params[:_type]
63
+ # We have to recreate the instance because we are given a _type in
64
+ # create_params specifying a subclass. We'll have to transfert the lock
65
+ # ownership to that new instance.
66
+ new_instance = model.model_from_attrs(create_params).new(where_params).tap do |i|
67
+ i.locked_keys_for_uniqueness = new_instance.locked_keys_for_uniqueness
68
+ end
69
+ end
70
+
71
+ new_instance.assign_attributes(create_params)
72
+ new_instance.__send__(options[:save_method])
73
+ return new_instance
74
+ ensure
75
+ new_instance.try(:unlock_unique_fields)
76
+ end
77
+
78
+ def extract_where_params()
79
+ where_clauses = finalized_criteria.options[:where_ast]
80
+
81
+ unless where_clauses.is_a?(NoBrainer::Criteria::Where::MultiOperator) &&
82
+ where_clauses.op == :and && where_clauses.clauses.size > 0 &&
83
+ where_clauses.clauses.all? do |c|
84
+ c.is_a?(NoBrainer::Criteria::Where::BinaryOperator) &&
85
+ c.op == :eq && c.key_modifier == :scalar
86
+ end
87
+ raise "Please use a query of the form `.where(...).first_or_create(...)'"
88
+ end
89
+
90
+ Hash[where_clauses.clauses.map do |c|
91
+ raise "You may not use nested hash queries with first_or.create()" if c.key_path.size > 1
92
+ [c.key_path.first.to_sym, c.value]
93
+ end]
94
+ end
95
+
96
+ def get_uniqueness_validators_map
97
+ Hash[model.unique_validators
98
+ .flat_map { |validator| validator.attributes.map { |attr| [attr, validator] } }
99
+ .map { |f, validator| [[f, *validator.scope].map(&:to_sym).sort, validator] }]
100
+ end
101
+ end
@@ -0,0 +1,26 @@
1
+ module NoBrainer::Criteria::Run
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ criteria_option :initial_run_options, :merge_with => :set_scalar
6
+ criteria_option :run_with, :merge_with => :merge_hash
7
+ end
8
+
9
+ private
10
+
11
+ def run(&block)
12
+ return finalized_criteria.__send__(:run, &block) unless finalized?
13
+ ensure_same_run_option_context!
14
+
15
+ block ||= proc { to_rql }
16
+ NoBrainer.run(:criteria => self, &block)
17
+ end
18
+
19
+ def ensure_same_run_option_context!
20
+ return if @options[:initial_run_options].nil?
21
+ return if @options[:initial_run_options] == NoBrainer.current_run_options
22
+
23
+ raise "The current criteria cannot be executed as it was constructed in a different `run_with()' context\n" +
24
+ "Note: you may use `run_with()' directly in your query (e.g. Model.run_with(...).first)."
25
+ end
26
+ end
@@ -3,10 +3,6 @@ module NoBrainer::Criteria::Scope
3
3
 
4
4
  included { criteria_option :use_default_scope, :merge_with => :set_scalar }
5
5
 
6
- def scoped
7
- chain(:use_default_scope => true)
8
- end
9
-
10
6
  def unscoped
11
7
  chain(:use_default_scope => false)
12
8
  end
@@ -17,21 +13,25 @@ module NoBrainer::Criteria::Scope
17
13
 
18
14
  def method_missing(name, *args, &block)
19
15
  return super unless self.model.respond_to?(name)
16
+ apply_named_scope(name, args, block)
17
+ end
18
+
19
+ private
20
+
21
+ def apply_named_scope(name, args, block)
20
22
  criteria = self.model.method(name).call(*args, &block)
21
23
  raise "#{name} did not return a criteria" unless criteria.is_a?(NoBrainer::Criteria)
22
24
  merge(criteria)
23
25
  end
24
26
 
25
- private
26
-
27
- def _apply_default_scope
27
+ def apply_default_scope
28
28
  return self if @options[:use_default_scope] == false
29
29
  (model.default_scopes.map(&:call).compact + [self]).reduce(:merge)
30
30
  end
31
31
 
32
32
  module ClassMethods
33
33
  def _finalize_criteria(base)
34
- super.__send__(:_apply_default_scope)
34
+ super.__send__(:apply_default_scope)
35
35
  end
36
36
  end
37
37
  end
@@ -57,7 +57,7 @@ module NoBrainer::Criteria::Where
57
57
  same_op_clauses, other_clauses = clauses.partition do |v|
58
58
  v.is_a?(self.class) && (v.clauses.size == 1 || v.op == self.op)
59
59
  end
60
- simplified_clauses = other_clauses + same_op_clauses.map(&:clauses).flatten(1)
60
+ simplified_clauses = other_clauses + same_op_clauses.flat_map(&:clauses)
61
61
  simplified_clauses = BinaryOperator.simplify_clauses(op, simplified_clauses.uniq)
62
62
  self.class.new(op, simplified_clauses)
63
63
  end
@@ -70,7 +70,7 @@ module NoBrainer::Criteria::Where
70
70
  end
71
71
  end
72
72
 
73
- class BinaryOperator < Struct.new(:key, :key_modifier, :op, :value, :model, :casted_values)
73
+ class BinaryOperator < Struct.new(:key_path, :key_modifier, :op, :value, :model, :casted_values)
74
74
  def self.get_candidate_clauses(clauses, *types)
75
75
  clauses.select { |c| c.is_a?(self) && types.include?(c.op) }
76
76
  end
@@ -79,10 +79,10 @@ module NoBrainer::Criteria::Where
79
79
  # This code assumes that simplfy() has already been called on all clauses.
80
80
  if op == :or
81
81
  eq_clauses = get_candidate_clauses(ast_clauses, :in, :eq)
82
- new_clauses = eq_clauses.group_by { |c| [c.key, c.key_modifier] }.map do |(key, key_modifier), clauses|
82
+ new_clauses = eq_clauses.group_by { |c| [c.key_path, c.key_modifier] }.map do |(key_path, key_modifier), clauses|
83
83
  if key_modifier.in?([:scalar, :any]) && clauses.size > 1
84
- values = clauses.map { |c| c.op == :in ? c.value : [c.value] }.flatten(1).uniq
85
- [BinaryOperator.new(key, key_modifier, :in, values, clauses.first.model, true)]
84
+ values = clauses.flat_map { |c| c.op == :in ? c.value : [c.value] }.uniq
85
+ [BinaryOperator.new(key_path, key_modifier, :in, values, clauses.first.model, true)]
86
86
  else
87
87
  clauses
88
88
  end
@@ -97,7 +97,7 @@ module NoBrainer::Criteria::Where
97
97
  end
98
98
 
99
99
  def simplify
100
- new_key = cast_key(key)
100
+ new_key_path = cast_key_path(key_path)
101
101
  new_op, new_value = case op
102
102
  when :in then
103
103
  case value
@@ -111,11 +111,14 @@ module NoBrainer::Criteria::Where
111
111
  [op, cast_value(value)]
112
112
  else [op, cast_value(value)]
113
113
  end
114
- BinaryOperator.new(new_key, key_modifier, new_op, new_value, model, true)
114
+ BinaryOperator.new(new_key_path, key_modifier, new_op, new_value, model, true)
115
115
  end
116
116
 
117
117
  def to_rql(doc)
118
- key = model.lookup_field_alias(self.key)
118
+ key_path = [model.lookup_field_alias(self.key_path.first), *self.key_path[1..-1]]
119
+
120
+ doc = key_path[0..-2].reduce(doc) { |d,k| d[k] }
121
+ key = key_path.last
119
122
 
120
123
  case key_modifier
121
124
  when :scalar then
@@ -148,10 +151,8 @@ module NoBrainer::Criteria::Where
148
151
  private
149
152
 
150
153
  def association
151
- # FIXME This leaks memory with dynamic attributes. The internals of type
152
- # checking will convert the key to a symbol, and Ruby does not garbage
153
- # collect symbols.
154
- @association ||= [model.association_metadata[key.to_sym]]
154
+ return nil if key_path.size > 1
155
+ @association ||= [model.association_metadata[key_path.first]]
155
156
  @association.first
156
157
  end
157
158
 
@@ -162,7 +163,7 @@ module NoBrainer::Criteria::Where
162
163
  when NoBrainer::Document::Association::BelongsTo::Metadata
163
164
  target_model = association.target_model
164
165
  unless value.is_a?(target_model)
165
- opts = { :model => model, :attr_name => key, :type => target_model, :value => value }
166
+ opts = { :model => model, :attr_name => key_path.first, :type => target_model, :value => value }
166
167
  raise NoBrainer::Error::InvalidType.new(opts)
167
168
  end
168
169
  value.pk_value
@@ -173,38 +174,43 @@ module NoBrainer::Criteria::Where
173
174
  raise "Use a geo object with `intersects`" unless value.is_a?(NoBrainer::Geo::Base)
174
175
  value
175
176
  when :near
177
+ # TODO enforce key is a geo type
176
178
  case value
177
- when Hash
178
- options = NoBrainer::Geo::Base.normalize_geo_options(value)
179
-
180
- options[:radius] = options.delete(:max_distance) if options[:max_distance]
181
- options[:radius] = options.delete(:max_dist) if options[:max_dist]
182
- options[:center] = options.delete(:point) if options[:point]
183
-
184
- unless options[:circle]
185
- unless options[:center] && options[:radius]
186
- raise "`near' takes something like {:center => P, :radius => d}"
187
- end
188
- { :circle => NoBrainer::Geo::Circle.new(options), :max_results => options[:max_results] }
179
+ when Hash
180
+ options = NoBrainer::Geo::Base.normalize_geo_options(value)
181
+
182
+ options[:radius] = options.delete(:max_distance) if options[:max_distance]
183
+ options[:radius] = options.delete(:max_dist) if options[:max_dist]
184
+ options[:center] = options.delete(:point) if options[:point]
185
+
186
+ unless options[:circle]
187
+ unless options[:center] && options[:radius]
188
+ raise "`near' takes something like {:center => P, :radius => d}"
189
189
  end
190
- when NoBrainer::Geo::Circle then { :circle => value }
191
- else raise "Incorrect use of `near': rvalue must be a hash or a circle"
190
+ { :circle => NoBrainer::Geo::Circle.new(options), :max_results => options[:max_results] }
191
+ end
192
+ when NoBrainer::Geo::Circle then { :circle => value }
193
+ else raise "Incorrect use of `near': rvalue must be a hash or a circle"
192
194
  end
193
195
  else
194
- case key_modifier
195
- when :scalar then model.cast_user_to_db_for(key, value)
196
- when :any, :all then model.cast_user_to_db_for(key, [value]).first
197
- end
196
+ # 1) Box value in array if we have an any/all modifier
197
+ # 2) Box value in hash if we have a nested query.
198
+ value = [value] if key_modifier.in?([:any, :all])
199
+ value_hash = key_path.reverse.reduce(value) { |v,k| {k => v} }
200
+ value = model.cast_user_to_db_for(*value_hash.first)
201
+ value = key_path[1..-1].reduce(value) { |h,k| h[k] }
202
+ value = value.first if key_modifier.in?([:any, :all])
203
+ value
198
204
  end
199
205
  end
200
206
  end
201
207
 
202
- def cast_key(key)
208
+ def cast_key_path(key)
203
209
  return key if casted_values
204
210
 
205
211
  case association
206
- when NoBrainer::Document::Association::BelongsTo::Metadata then association.foreign_key
207
- else model.ensure_valid_key!(key); key
212
+ when NoBrainer::Document::Association::BelongsTo::Metadata then [association.foreign_key]
213
+ else model.ensure_valid_key!(key_path.first); key_path
208
214
  end
209
215
  end
210
216
  end
@@ -240,49 +246,53 @@ module NoBrainer::Criteria::Where
240
246
  end
241
247
  end
242
248
 
243
- def parse_clause(clause)
249
+ def parse_clause(clause, options={})
244
250
  clause = sanitize_for_mass_assignment(clause)
245
251
  case clause
246
- when Array then MultiOperator.new(:and, clause.map { |c| parse_clause(c) })
247
- when Hash then MultiOperator.new(:and, clause.map { |k,v| parse_clause_stub(k,v) })
252
+ when Array then MultiOperator.new(:and, clause.map { |c| parse_clause(c, options) })
253
+ when Hash then MultiOperator.new(:and, clause.map { |k,v| parse_clause_stub(k, v, options) })
248
254
  when Proc then Lambda.new(clause)
249
255
  when Symbol::Decoration
250
256
  case clause.args.size
251
- when 1 then parse_clause_stub(clause, clause.args.first)
257
+ when 1 then parse_clause_stub(clause, clause.args.first, options)
252
258
  else raise "Invalid argument: #{clause}"
253
259
  end
254
260
  else raise "Invalid clause: #{clause}"
255
261
  end
256
262
  end
257
263
 
258
- def parse_clause_stub(key, value)
264
+ def parse_clause_stub(key, value, options={})
259
265
  case key
260
- when :and then parse_multi_value(:and, value)
261
- when :or then parse_multi_value(:or, value)
262
- when :_and then parse_multi_value(:and, value, :safe => true)
263
- when :_or then parse_multi_value(:or, value, :safe => true)
264
- when :not then UnaryOperator.new(:not, parse_clause(value))
265
- when String, Symbol then instantiate_binary_op(key, :eq, value)
266
+ when :and then parse_multi_value(:and, value, false, options)
267
+ when :or then parse_multi_value(:or, value, false, options)
268
+ when :_and then parse_multi_value(:and, value, true, options)
269
+ when :_or then parse_multi_value(:or, value, true, options)
270
+ when :not then UnaryOperator.new(:not, parse_clause(value, options))
271
+ when String, Symbol then
272
+ case value
273
+ when Hash then parse_clause(value, options.merge(:nested_prefix => (options[:nested_prefix] || []) + [key.to_sym]))
274
+ else instantiate_binary_op(key.to_sym, :eq, value, options)
275
+ end
266
276
  when Symbol::Decoration then
267
277
  case key.decorator
268
- when :any, :all, :not then instantiate_binary_op(key, :eq, value)
269
- when :gte then instantiate_binary_op(key.symbol, :ge, value)
270
- when :lte then instantiate_binary_op(key.symbol, :le, value)
271
- else instantiate_binary_op(key.symbol, key.decorator, value)
278
+ when :any, :all, :not then instantiate_binary_op(key, :eq, value, options)
279
+ when :gte then instantiate_binary_op(key.symbol, :ge, value, options)
280
+ when :lte then instantiate_binary_op(key.symbol, :le, value, options)
281
+ else instantiate_binary_op(key.symbol, key.decorator, value, options)
272
282
  end
273
283
  else raise "Invalid key: #{key}"
274
284
  end
275
285
  end
276
286
 
277
- def parse_multi_value(op, value, options={})
287
+ def parse_multi_value(op, value, multi_safe, options={})
278
288
  raise "The `#{op}' operator takes an array as argument" unless value.is_a?(Array)
279
- if value.size == 1 && value.first.is_a?(Hash) && !options[:safe]
289
+ if value.size == 1 && value.first.is_a?(Hash) && !multi_safe
280
290
  raise "The `#{op}' operator was provided an array with a single hash element.\n" +
281
291
  "In Ruby, [:a => :b, :c => :d] means [{:a => :b, :c => :d}] which is not the same as [{:a => :b}, {:c => :d}].\n" +
282
292
  "To prevent mistakes, the former construct is prohibited as you probably mean the latter.\n" +
283
293
  "However, if you know what you are doing, you can use the `_#{op}' operator instead."
284
294
  end
285
- MultiOperator.new(op, value.map { |v| parse_clause(v) })
295
+ MultiOperator.new(op, value.map { |v| parse_clause(v, options) })
286
296
  end
287
297
 
288
298
  def translate_regexp_to_re2_syntax(value)
@@ -298,21 +308,22 @@ module NoBrainer::Criteria::Where
298
308
  "(?#{flags})#{value.source}"
299
309
  end
300
310
 
301
- def instantiate_binary_op(key, op, value)
311
+ def instantiate_binary_op(key, op, value, options={})
302
312
  op, value = case value
303
313
  when Range then [:between, value]
304
314
  when Regexp then [:match, translate_regexp_to_re2_syntax(value)]
305
315
  else [:eq, value]
306
316
  end if op == :eq
307
317
 
318
+ nested_prefix = options[:nested_prefix] || []
308
319
  case key
309
320
  when Symbol::Decoration
310
321
  raise "Use only one .not, .all or .any modifiers in the query" if key.symbol.is_a?(Symbol::Decoration)
311
322
  case key.decorator
312
- when :any, :all then BinaryOperator.new(key.symbol, key.decorator, op, value, self.model)
313
- when :not then UnaryOperator.new(:not, BinaryOperator.new(key.symbol, :scalar, op, value, self.model))
323
+ when :any, :all then BinaryOperator.new(nested_prefix + [key.symbol], key.decorator, op, value, self.model)
324
+ when :not then UnaryOperator.new(:not, BinaryOperator.new(nested_prefix + [key.symbol], :scalar, op, value, self.model))
314
325
  end
315
- else BinaryOperator.new(key, :scalar, op, value, self.model)
326
+ else BinaryOperator.new(nested_prefix + [key], :scalar, op, value, self.model)
316
327
  end
317
328
  end
318
329
 
@@ -353,9 +364,9 @@ module NoBrainer::Criteria::Where
353
364
  clauses = get_candidate_clauses(:eq, :in, :between, :near, :intersects)
354
365
  return nil unless clauses.present?
355
366
 
356
- usable_indexes = Hash[get_usable_indexes.map { |i| [i.name, i] }]
367
+ usable_indexes = Hash[get_usable_indexes.map { |i| [[i.name], i] }]
357
368
  clauses.map do |clause|
358
- index = usable_indexes[clause.key]
369
+ index = usable_indexes[clause.key_path]
359
370
  next unless index && clause.compatible_with_index?(index)
360
371
  next unless index.geo == [:near, :intersects].include?(clause.op)
361
372
 
@@ -377,11 +388,11 @@ module NoBrainer::Criteria::Where
377
388
  end
378
389
 
379
390
  def find_strategy_compound
380
- clauses = Hash[get_candidate_clauses(:eq).map { |c| [c.key, c] }]
391
+ clauses = Hash[get_candidate_clauses(:eq).map { |c| [c.key_path, c] }]
381
392
  return nil unless clauses.present?
382
393
 
383
394
  get_usable_indexes(:kind => :compound, :geo => false, :multi => false).each do |index|
384
- indexed_clauses = index.what.map { |field| clauses[field] }
395
+ indexed_clauses = index.what.map { |field| clauses[[field]] }
385
396
  next unless indexed_clauses.all? { |c| c.try(:compatible_with_index?, index) }
386
397
 
387
398
  return IndexStrategy.new(self, ast, indexed_clauses, index, :get_all, [indexed_clauses.map(&:value)])
@@ -390,11 +401,11 @@ module NoBrainer::Criteria::Where
390
401
  end
391
402
 
392
403
  def find_strategy_hidden_between
393
- clauses = get_candidate_clauses(:gt, :ge, :lt, :le).group_by(&:key)
404
+ clauses = get_candidate_clauses(:gt, :ge, :lt, :le).group_by(&:key_path)
394
405
  return nil unless clauses.present?
395
406
 
396
407
  get_usable_indexes(:geo => false).each do |index|
397
- matched_clauses = clauses[index.name].try(:select) { |c| c.compatible_with_index?(index) }
408
+ matched_clauses = clauses[[index.name]].try(:select) { |c| c.compatible_with_index?(index) }
398
409
  next unless matched_clauses.present?
399
410
 
400
411
  op_clauses = Hash[matched_clauses.map { |c| [c.op, c] }]
@@ -2,8 +2,8 @@ require 'rethinkdb'
2
2
 
3
3
  class NoBrainer::Criteria
4
4
  extend NoBrainer::Autoload
5
- autoload_and_include :Core, :Raw, :AfterFind, :Where, :OrderBy, :Limit,
6
- :Pluck, :Count, :Delete, :Enumerable, :First, :Find,
7
- :Aggregate, :EagerLoad, :Update, :Cache, :Index,
8
- :Extend, :Scope
5
+ autoload_and_include :Core, :Run, :Raw, :Scope, :AfterFind, :Where, :OrderBy,
6
+ :Limit, :Pluck, :Count, :Delete, :Enumerable, :Find,
7
+ :First, :FirstOrCreate, :Aggregate, :EagerLoad, :Update,
8
+ :Cache, :Index, :Extend
9
9
  end
@@ -17,7 +17,7 @@ module NoBrainer::Document::Criteria
17
17
  :raw, # Raw
18
18
  :limit, :offset, :skip, # Limit
19
19
  :order_by, :reverse_order, :without_ordering, :order_by_indexed?, :order_by_index_name, # OrderBy
20
- :scoped, :unscoped, # Scope
20
+ :unscoped, # Scope
21
21
  :where, :where_indexed?, :where_index_name, :where_index_type, # Where
22
22
  :with_index, :without_index, :used_index, # Index
23
23
  :with_cache, :without_cache, # Cache
@@ -26,6 +26,7 @@ module NoBrainer::Document::Criteria
26
26
  :preload, :eager_load, # EagerLoad
27
27
  :each, :to_a, # Enumerable
28
28
  :first, :last, :first!, :last!, :sample, # First
29
+ :first_or_create, :first_or_create!, # FirstOrCreate
29
30
  :min, :max, :sum, :avg, # Aggregate
30
31
  :update_all, :replace_all, # Update
31
32
  :pluck, :without, :lazy_fetch, :without_plucking, # Pluck
@@ -33,7 +34,8 @@ module NoBrainer::Document::Criteria
33
34
  :to => :all
34
35
 
35
36
  def all
36
- NoBrainer::Criteria.new(:model => self)
37
+ NoBrainer::Criteria.new(:initial_run_options => NoBrainer.current_run_options,
38
+ :model => self)
37
39
  end
38
40
 
39
41
  def scope(name, criteria=nil, &block)
@@ -59,10 +59,8 @@ class NoBrainer::Document::Index::Index < Struct.new(
59
59
  NoBrainer::RQL.reset_lambda_var_counter
60
60
  NoBrainer.run(model.rql_table.index_create(aliased_name, opt, &rql_proc))
61
61
 
62
- MetaStore.on(model.database_name) do
63
- MetaStore.create(:table_name => model.table_name, :index_name => aliased_name,
64
- :rql_function => serialized_rql_proc)
65
- end
62
+ MetaStore.create(:table_name => model.table_name, :index_name => aliased_name,
63
+ :rql_function => serialized_rql_proc)
66
64
  end
67
65
 
68
66
  def delete(options={})
@@ -70,9 +68,7 @@ class NoBrainer::Document::Index::Index < Struct.new(
70
68
 
71
69
  NoBrainer.run(model.rql_table.index_drop(aliased_name))
72
70
 
73
- MetaStore.on(model.database_name) do
74
- MetaStore.where(:table_name => model.table_name, :index_name => aliased_name).delete_all
75
- end
71
+ MetaStore.where(:table_name => model.table_name, :index_name => aliased_name).delete_all
76
72
  end
77
73
 
78
74
  def update(wanted_index, options={})
@@ -6,8 +6,7 @@ class NoBrainer::Document::Index::MetaStore
6
6
 
7
7
  default_scope ->{ order_by(:created_at) }
8
8
 
9
- store_in :database => ->{ Thread.current[:nobrainer_meta_store_db] },
10
- :table => 'nobrainer_index_meta'
9
+ store_in :table => 'nobrainer_index_meta'
11
10
 
12
11
  field :table_name, :type => String, :required => true
13
12
  field :index_name, :type => String, :required => true
@@ -20,12 +19,4 @@ class NoBrainer::Document::Index::MetaStore
20
19
  def rql_function
21
20
  JSON.load(super)
22
21
  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
- block.call
28
- ensure
29
- Thread.current[:nobrainer_meta_store_db] = old_db_name
30
- end
31
22
  end
@@ -8,9 +8,8 @@ class NoBrainer::Document::Index::Synchronizer
8
8
  end]
9
9
  end
10
10
 
11
- def meta_store_on(db_name)
12
- @meta_store ||= {}
13
- @meta_store[db_name] ||= MetaStore.on(db_name) { MetaStore.all.to_a }
11
+ def meta_store
12
+ @meta_store ||= MetaStore.to_a
14
13
  end
15
14
 
16
15
  class Op < Struct.new(:index, :op, :args)
@@ -21,8 +20,7 @@ class NoBrainer::Document::Index::Synchronizer
21
20
 
22
21
  def _generate_plan_for(model, wanted_indexes)
23
22
  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
23
+ meta = meta_store.select { |i| i.table_name == model.table_name && i.index_name == s['index'] }.last
26
24
  Index.new(model, s['index'], s['index'], nil, nil, nil, s['geo'], s['multi'], meta)
27
25
  end
28
26
 
@@ -46,7 +44,7 @@ class NoBrainer::Document::Index::Synchronizer
46
44
  end
47
45
 
48
46
  def generate_plan
49
- @models_indexes_map.map { |model, indexes| _generate_plan_for(model, indexes) }.flatten(1)
47
+ @models_indexes_map.flat_map { |model, indexes| _generate_plan_for(model, indexes) }
50
48
  end
51
49
 
52
50
  def sync_indexes(options={})
@@ -37,7 +37,11 @@ module NoBrainer::Document::Polymorphic
37
37
  end
38
38
 
39
39
  def model_from_attrs(attrs)
40
- attrs['_type'].try(:constantize) || root_class
40
+ class_name = attrs['_type'] || attrs[:_type]
41
+ return root_class unless class_name
42
+ class_name.to_s.constantize.tap { |cls| raise NameError unless cls <= self }
43
+ rescue NameError
44
+ raise NoBrainer::Error::InvalidPolymorphicType, "Invalid polymorphic class: `#{class_name}' is not a `#{self}'"
41
45
  end
42
46
 
43
47
  def all
@@ -11,12 +11,13 @@ module NoBrainer::Document::StoreIn
11
11
  module ClassMethods
12
12
  def store_in(options)
13
13
  raise "store_in() must be called on the parent class" unless is_root_class?
14
- self.store_in_options.merge!(options)
15
- end
16
14
 
17
- def database_name
18
- db = self.store_in_options[:database]
19
- (db.is_a?(Proc) ? db.call : db).try(:to_s)
15
+ if options[:database] || options[:db]
16
+ STDERR.puts "[NoBrainer] `store_in(db: ...)' has been removed. Use `run_with(db: ...)' instead. Sorry."
17
+ end
18
+
19
+ options.assert_valid_keys(:table)
20
+ self.store_in_options.merge!(options)
20
21
  end
21
22
 
22
23
  def table_name
@@ -26,10 +27,7 @@ module NoBrainer::Document::StoreIn
26
27
  end
27
28
 
28
29
  def rql_table
29
- db = self.database_name
30
- rql = RethinkDB::RQL.new
31
- rql = rql.db(db) if db
32
- rql.table(table_name)
30
+ RethinkDB::RQL.new.table(table_name)
33
31
  end
34
32
  end
35
33
  end
@@ -8,41 +8,38 @@ module NoBrainer::Document::Validation::Uniqueness
8
8
  unlock_unique_fields
9
9
  end
10
10
 
11
- def _lock_key_from_field(field)
12
- value = read_attribute(field).to_s
13
- ['nobrainer', self.class.database_name || NoBrainer.connection.parsed_uri[:db],
14
- self.class.table_name, field, value.empty? ? 'nil' : value].join(':')
11
+ def _lock_for_uniqueness_once(key)
12
+ @locked_keys_for_uniqueness ||= {}
13
+ @locked_keys_for_uniqueness[key] ||= NoBrainer::Config.distributed_lock_class.new(key).tap(&:lock)
15
14
  end
16
15
 
17
- def lock_unique_fields
18
- return unless NoBrainer::Config.distributed_lock_class && !self.class.unique_validators.empty?
16
+ def unlock_unique_fields
17
+ @locked_keys_for_uniqueness.to_h.values.each(&:unlock)
18
+ @locked_keys_for_uniqueness = {}
19
+ end
19
20
 
21
+ def lock_unique_fields
20
22
  self.class.unique_validators
21
- .map { |validator| validator.attributes.map { |attr| [attr, validator] } }
22
- .flatten(1)
23
+ .flat_map { |validator| validator.attributes.map { |attr| [attr, validator] } }
23
24
  .select { |f, validator| validator.should_validate_field?(self, f) }
24
- .map { |f, options| _lock_key_from_field(f) }
25
- .sort
26
- .uniq
27
- .each do |key|
28
- lock = NoBrainer::Config.distributed_lock_class.new(key)
29
- lock.lock
30
- @locked_unique_fields ||= []
31
- @locked_unique_fields << lock
32
- end
33
- end
34
-
35
- def unlock_unique_fields
36
- return unless @locked_unique_fields
37
- @locked_unique_fields.pop.unlock until @locked_unique_fields.empty?
25
+ .map { |f, validator| [f, *validator.scope].map { |k| [k, read_attribute(k)] } }
26
+ .map { |params| self.class._uniqueness_key_name_from_params(params) }
27
+ .sort.each { |key| _lock_for_uniqueness_once(key) }
38
28
  end
39
29
 
40
30
  included do
41
31
  singleton_class.send(:attr_accessor, :unique_validators)
42
32
  self.unique_validators = []
33
+ attr_accessor :locked_keys_for_uniqueness
43
34
  end
44
35
 
45
36
  module ClassMethods
37
+ def _uniqueness_key_name_from_params(params)
38
+ ['uniq', NoBrainer.current_db, self.table_name,
39
+ *params.map { |k,v| [k.to_s, (v = v.to_s; v.empty? ? 'nil' : v)] }.sort
40
+ ].join(':')
41
+ end
42
+
46
43
  def validates_uniqueness_of(*attr_names)
47
44
  validates_with(UniquenessValidator, _merge_attributes(attr_names))
48
45
  end
@@ -54,14 +51,14 @@ module NoBrainer::Document::Validation::Uniqueness
54
51
  end
55
52
 
56
53
  class UniquenessValidator < ActiveModel::EachValidator
57
- attr_accessor :scope
54
+ attr_accessor :scope, :model
58
55
 
59
56
  def initialize(options={})
60
57
  super
61
- model = options[:class]
62
- self.scope = [*options[:scope]]
63
- ([model] + model.descendants).each do |_model|
64
- _model.unique_validators << self
58
+ self.model = options[:class]
59
+ self.scope = [*options[:scope]].map(&:to_sym)
60
+ ([model] + model.descendants).each do |subclass|
61
+ subclass.unique_validators << self
65
62
  end
66
63
  end
67
64
 
@@ -70,7 +67,7 @@ module NoBrainer::Document::Validation::Uniqueness
70
67
  end
71
68
 
72
69
  def validate_each(doc, attr, value)
73
- criteria = doc.root_class.unscoped.where(attr => value)
70
+ criteria = self.model.unscoped.where(attr => value)
74
71
  criteria = apply_scopes(criteria, doc)
75
72
  criteria = exclude_doc(criteria, doc) if doc.persisted?
76
73
  doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless criteria.empty?
@@ -12,6 +12,7 @@ module NoBrainer::Error
12
12
  class AtomicBlock < RuntimeError; end
13
13
  class LostLock < RuntimeError; end
14
14
  class LockUnavailable < RuntimeError; end
15
+ class InvalidPolymorphicType < RuntimeError; end
15
16
 
16
17
  class DocumentInvalid < RuntimeError
17
18
  attr_accessor :instance
@@ -30,12 +30,16 @@ class NoBrainer::Lock
30
30
  super(options.merge(:key => key))
31
31
  end
32
32
 
33
- def lock(options={}, &block)
34
- if block
35
- lock(options)
36
- return block.call.tap { unlock }
33
+ def synchronize(options={}, &block)
34
+ lock(options)
35
+ begin
36
+ block.call
37
+ ensure
38
+ unlock
37
39
  end
40
+ end
38
41
 
42
+ def lock(options={})
39
43
  options.assert_valid_keys(:expire, :timeout)
40
44
  timeout = NoBrainer::Config.lock_options.merge(options)[:timeout]
41
45
  sleep_amount = 0.1
@@ -13,9 +13,8 @@ class NoBrainer::Profiler::Logger
13
13
  msg_duration = "[#{msg_duration}ms] "
14
14
 
15
15
  env[:query_type] = NoBrainer::RQL.type_of(env[:query])
16
- env[:custom_db_name] = env[:db_name] if env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
17
16
 
18
- msg_db = "[#{env[:custom_db_name]}] " if env[:custom_db_name]
17
+ msg_db = "[#{env[:options][:db]}] " if env[:options][:db]
19
18
  msg_query = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
20
19
 
21
20
  msg_exception = "#{env[:exception].class} #{env[:exception].message.split("\n").first}" if env[:exception]
@@ -2,8 +2,8 @@ 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 = handle_database_on_demand_exception?(env, e)
6
- auto_create_database(env, database_name)
5
+ if db_name = handle_database_on_demand_exception?(env, e)
6
+ auto_create_database(env, db_name)
7
7
  retry
8
8
  end
9
9
  raise
@@ -15,15 +15,15 @@ class NoBrainer::QueryRunner::DatabaseOnDemand < NoBrainer::QueryRunner::Middlew
15
15
 
16
16
  private
17
17
 
18
- def auto_create_database(env, database_name)
19
- if env[:last_auto_create_database] == database_name
20
- raise "Auto database creation is not working with #{database_name}"
18
+ def auto_create_database(env, db_name)
19
+ if env[:last_auto_create_database] == db_name
20
+ raise "Auto database creation is not working with #{db_name}"
21
21
  end
22
- env[:last_auto_create_database] = database_name
22
+ env[:last_auto_create_database] = db_name
23
23
 
24
- NoBrainer.db_create(database_name)
24
+ NoBrainer.db_create(db_name)
25
25
  rescue RuntimeError => e
26
26
  # We might have raced with another db_create
27
- raise unless e.message =~ /Database `#{database_name}` already exists/
27
+ raise unless e.message =~ /Database `#{db_name}` already exists/
28
28
  end
29
29
  end
@@ -1,5 +1,7 @@
1
1
  class NoBrainer::QueryRunner::Driver < NoBrainer::QueryRunner::Middleware
2
2
  def call(env)
3
- env[:query].run(NoBrainer.connection.raw, env[:options])
3
+ options = env[:options]
4
+ options = options.merge(:db => RethinkDB::RQL.new.db(options[:db])) if options[:db]
5
+ env[:query].run(NoBrainer.connection.raw, options)
4
6
  end
5
7
  end
@@ -4,7 +4,7 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
4
4
  rescue RethinkDB::RqlRuntimeError => e
5
5
  if e.message =~ /^Index `(.+)` was not found on table `(.+)\.(.+)`\.$/
6
6
  index_name = $1
7
- database_name = $2
7
+ db_name = $2
8
8
  table_name = $3
9
9
 
10
10
  model = NoBrainer::Document.all.select { |m| m.table_name == table_name }.first
@@ -12,10 +12,10 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
12
12
  index_name = index.name if index
13
13
 
14
14
  if model.try(:pk_name).try(:to_s) == index_name.to_s
15
- err_msg = "Please update the primary key `#{index_name}` in the table `#{database_name}.#{table_name}`."
15
+ err_msg = "Please update the primary key `#{index_name}` in the table `#{db_name}.#{table_name}`."
16
16
  else
17
17
  err_msg = "Please run `NoBrainer.sync_indexes' or `rake nobrainer:sync_indexes' to create the index `#{index_name}`"
18
- err_msg += " in the table `#{database_name}.#{table_name}`."
18
+ err_msg += " in the table `#{db_name}.#{table_name}`."
19
19
  err_msg += " Read http://nobrainer.io/docs/indexes for more information."
20
20
  end
21
21
 
@@ -22,7 +22,6 @@ class NoBrainer::QueryRunner::Profiler < NoBrainer::QueryRunner::Middleware
22
22
 
23
23
  env[:model] = env[:criteria] && env[:criteria].model
24
24
  env[:query_type] = NoBrainer::RQL.type_of(env[:query])
25
- env[:custom_db_name] = env[:db_name] if env[:db_name] && env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
26
25
 
27
26
  NoBrainer::Profiler.registered_profilers.each do |profiler|
28
27
  begin
@@ -1,35 +1,55 @@
1
1
  class NoBrainer::QueryRunner::RunOptions < NoBrainer::QueryRunner::Middleware
2
- # XXX NoBrainer::Database#drop() uses Thread.current[:nobrainer_options]
3
-
4
2
  def self.with_database(db_name, &block)
3
+ STDERR.puts "[NoBrainer] `with_database()' is deprecated, please use `with(db: ...)' instead"
5
4
  with(:db => db_name, &block)
6
5
  end
7
6
 
8
7
  def self.with(options={}, &block)
9
- old_options = Thread.current[:nobrainer_options]
10
- Thread.current[:nobrainer_options] = (old_options || {}).merge(options.symbolize_keys)
11
- block.call if block
8
+ STDERR.puts "[NoBrainer] `with(...)' is deprecated, please use `run_with(...)' instead"
9
+ run_with(options, &block)
10
+ end
11
+
12
+ def self.current_run_options
13
+ Thread.current[:nobrainer_run_with] || {}
14
+ end
15
+
16
+ def self.run_with(options={}, &block)
17
+ options = options.symbolize_keys
18
+
19
+ if options[:database]
20
+ STDERR.puts "[NoBrainer] `run_with(database: ...)' is deprecated, please use `run_with(db: ...)' instead"
21
+ options[:db] = options.delete(:database)
22
+ end
23
+
24
+ old_options = Thread.current[:nobrainer_run_with]
25
+ Thread.current[:nobrainer_run_with] = (old_options || {}).merge(options)
26
+ block.call
12
27
  ensure
13
- Thread.current[:nobrainer_options] = old_options
28
+ Thread.current[:nobrainer_run_with] = old_options
14
29
  end
15
30
 
16
31
  def call(env)
17
- env[:options].symbolize_keys!
18
- if Thread.current[:nobrainer_options]
19
- env[:options].reverse_merge!(Thread.current[:nobrainer_options])
20
- end
32
+ options = env[:options].symbolize_keys
33
+ options = self.class.current_run_options.merge(options)
21
34
 
22
35
  if NoBrainer::Config.durability.to_s != 'hard'
23
- env[:options].reverse_merge!(:durability => NoBrainer::Config.durability)
36
+ options[:durability] ||= NoBrainer::Config.durability
24
37
  end
25
38
 
26
- if env[:options][:db] && !env[:options][:db].is_a?(RethinkDB::RQL)
27
- env[:db_name] = env[:options][:db].to_s
28
- env[:options][:db] = RethinkDB::RQL.new.db(env[:db_name])
39
+ options[:db] = options[:db].to_s if options[:db]
40
+ if options[:db].blank? || options[:db] == NoBrainer.default_db
41
+ options.delete(:db)
29
42
  end
30
43
 
31
- env[:criteria] = env[:options].delete(:criteria)
44
+ env[:criteria] = options.delete(:criteria)
45
+
46
+ if options[:profile] && env[:criteria].try(:raw?) == false
47
+ STDERR.puts "[NoBrainer]"
48
+ STDERR.puts "[NoBrainer]\e[1;31m Please use `.raw' in your criteria when profiling\e[0m"
49
+ STDERR.puts "[NoBrainer]"
50
+ end
32
51
 
52
+ env[:options] = options
33
53
  @runner.call(env)
34
54
  end
35
55
  end
@@ -15,24 +15,24 @@ class NoBrainer::QueryRunner::TableOnDemand < NoBrainer::QueryRunner::Middleware
15
15
 
16
16
  private
17
17
 
18
- def auto_create_table(env, database_name, table_name)
18
+ def auto_create_table(env, db_name, table_name)
19
19
  model ||= NoBrainer::Document::Core._all.select { |m| m.table_name == table_name }.first
20
20
  model ||= NoBrainer::Document::Core._all_nobrainer.select { |m| m.table_name == table_name }.first
21
21
 
22
22
  if model.nil?
23
- raise "Auto table creation is not working for `#{database_name}.#{table_name}` -- Can't find the corresponding model."
23
+ raise "Auto table creation is not working for `#{db_name}.#{table_name}` -- Can't find the corresponding model."
24
24
  end
25
25
 
26
- if env[:last_auto_create_table] == [database_name, table_name]
27
- raise "Auto table creation is not working for `#{database_name}.#{table_name}`"
26
+ if env[:last_auto_create_table] == [db_name, table_name]
27
+ raise "Auto table creation is not working for `#{db_name}.#{table_name}`"
28
28
  end
29
- env[:last_auto_create_table] = [database_name, table_name]
29
+ env[:last_auto_create_table] = [db_name, table_name]
30
30
 
31
- NoBrainer.with_database(database_name) do
31
+ NoBrainer.run_with(:db => db_name) do
32
32
  NoBrainer.table_create(table_name, :primary_key => model.lookup_field_alias(model.pk_name))
33
33
  end
34
34
  rescue RuntimeError => e
35
35
  # We might have raced with another table create
36
- raise unless e.message =~ /Table `#{database_name}\.#{table_name}` already exists/
36
+ raise unless e.message =~ /Table `#{db_name}\.#{table_name}` already exists/
37
37
  end
38
38
  end
data/lib/nobrainer.rb CHANGED
@@ -23,12 +23,14 @@ module NoBrainer
23
23
 
24
24
  delegate :db_create, :db_drop, :db_list,
25
25
  :table_create, :table_drop, :table_list,
26
- :drop!, :purge!, :to => :connection
26
+ :drop!, :purge!, :default_db, :current_db, :to => :connection
27
27
 
28
- delegate :configure, :logger, :to => 'NoBrainer::Config'
29
- delegate :run, :to => 'NoBrainer::QueryRunner'
30
- delegate :sync_indexes, :to => 'NoBrainer::Document::Index::Synchronizer'
31
- delegate :with, :with_database, :to => 'NoBrainer::QueryRunner::RunOptions'
28
+ delegate :configure, :logger, :to => 'NoBrainer::Config'
29
+ delegate :run, :to => 'NoBrainer::QueryRunner'
30
+ delegate :sync_indexes, :to => 'NoBrainer::Document::Index::Synchronizer'
31
+ delegate :current_run_options, :run_with, :to => 'NoBrainer::QueryRunner::RunOptions'
32
+
33
+ delegate :with, :with_database, :to => 'NoBrainer::QueryRunner::RunOptions' # deprecated
32
34
 
33
35
  def jruby?
34
36
  RUBY_PLATFORM == 'java'
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.24.0
4
+ version: 0.25.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: 2015-05-20 00:00:00.000000000 Z
11
+ date: 2015-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rethinkdb
@@ -105,11 +105,13 @@ files:
105
105
  - lib/no_brainer/criteria/extend.rb
106
106
  - lib/no_brainer/criteria/find.rb
107
107
  - lib/no_brainer/criteria/first.rb
108
+ - lib/no_brainer/criteria/first_or_create.rb
108
109
  - lib/no_brainer/criteria/index.rb
109
110
  - lib/no_brainer/criteria/limit.rb
110
111
  - lib/no_brainer/criteria/order_by.rb
111
112
  - lib/no_brainer/criteria/pluck.rb
112
113
  - lib/no_brainer/criteria/raw.rb
114
+ - lib/no_brainer/criteria/run.rb
113
115
  - lib/no_brainer/criteria/scope.rb
114
116
  - lib/no_brainer/criteria/update.rb
115
117
  - lib/no_brainer/criteria/where.rb
@@ -172,7 +174,6 @@ files:
172
174
  - lib/no_brainer/loader.rb
173
175
  - lib/no_brainer/locale/en.yml
174
176
  - lib/no_brainer/lock.rb
175
- - lib/no_brainer/logger.rb
176
177
  - lib/no_brainer/profiler.rb
177
178
  - lib/no_brainer/profiler/controller_runtime.rb
178
179
  - lib/no_brainer/profiler/logger.rb
@@ -1,46 +0,0 @@
1
- class NoBrainer::Logger
2
- def on_query(env)
3
- # env[:end_time] = Time.now
4
- # env[:duration] = env[:end_time] - env[:start_time]
5
- # env[:exception] = exception
6
- # env[:query_type] = NoBrainer::RQL.type_of(env[:query])
7
-
8
- not_indexed = env[:criteria] && env[:criteria].where_present? &&
9
- !env[:criteria].where_indexed? &&
10
- !env[:criteria].model.try(:perf_warnings_disabled)
11
-
12
- level = exception ? Logger::ERROR :
13
- not_indexed ? Logger::INFO : Logger::DEBUG
14
- return if NoBrainer.logger.nil? || NoBrainer.logger.level > level
15
-
16
- msg_duration = (duration * 1000.0).round(1).to_s
17
- msg_duration = " " * [0, 6 - msg_duration.size].max + msg_duration
18
- msg_duration = "[#{msg_duration}ms] "
19
-
20
- msg_db = "[#{env[:db_name]}] " if env[:db_name] && env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
21
- msg_query = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
22
-
23
- msg_exception = "#{exception.class} #{exception.message.split("\n").first}" if exception
24
- msg_exception ||= "perf: filtering without using an index" if not_indexed
25
-
26
- msg_last = nil
27
-
28
- if NoBrainer::Config.colorize_logger
29
- query_color = case NoBrainer::RQL.type_of(env[:query])
30
- when :write then "\e[1;31m" # red
31
- when :read then "\e[1;32m" # green
32
- when :management then "\e[1;33m" # yellow
33
- end
34
- msg_duration = [query_color, msg_duration].join
35
- msg_db = ["\e[0;34m", msg_db, query_color].join if msg_db
36
- if msg_exception
37
- exception_color = "\e[0;31m" if level == Logger::ERROR
38
- msg_exception = ["\e[0;39m", " -- ", exception_color, msg_exception].compact.join
39
- end
40
- msg_last = "\e[0m"
41
- end
42
-
43
- msg = [msg_duration, msg_db, msg_query, msg_exception, msg_last].join
44
- NoBrainer.logger.add(level, msg)
45
- end
46
- end