nobrainer 0.24.0 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
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