nobrainer 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/config.rb +52 -28
  3. data/lib/no_brainer/connection.rb +1 -0
  4. data/lib/no_brainer/connection_manager.rb +1 -1
  5. data/lib/no_brainer/criteria/after_find.rb +3 -11
  6. data/lib/no_brainer/criteria/aggregate.rb +8 -5
  7. data/lib/no_brainer/criteria/cache.rb +7 -7
  8. data/lib/no_brainer/criteria/core.rb +56 -16
  9. data/lib/no_brainer/criteria/count.rb +1 -1
  10. data/lib/no_brainer/criteria/delete.rb +1 -1
  11. data/lib/no_brainer/criteria/extend.rb +19 -0
  12. data/lib/no_brainer/criteria/first.rb +1 -1
  13. data/lib/no_brainer/criteria/index.rb +7 -13
  14. data/lib/no_brainer/criteria/limit.rb +5 -13
  15. data/lib/no_brainer/criteria/order_by.rb +22 -41
  16. data/lib/no_brainer/criteria/pluck.rb +17 -23
  17. data/lib/no_brainer/criteria/preload.rb +9 -15
  18. data/lib/no_brainer/criteria/raw.rb +5 -11
  19. data/lib/no_brainer/criteria/scope.rb +9 -15
  20. data/lib/no_brainer/criteria/update.rb +3 -3
  21. data/lib/no_brainer/criteria/where.rb +33 -56
  22. data/lib/no_brainer/criteria.rb +1 -1
  23. data/lib/no_brainer/document/association/belongs_to.rb +6 -6
  24. data/lib/no_brainer/document/association/core.rb +11 -11
  25. data/lib/no_brainer/document/association/eager_loader.rb +4 -3
  26. data/lib/no_brainer/document/association/has_many.rb +7 -7
  27. data/lib/no_brainer/document/association/has_many_through.rb +1 -1
  28. data/lib/no_brainer/document/association/has_one.rb +1 -1
  29. data/lib/no_brainer/document/association.rb +5 -5
  30. data/lib/no_brainer/document/atomic_ops.rb +26 -18
  31. data/lib/no_brainer/document/attributes.rb +9 -8
  32. data/lib/no_brainer/document/callbacks.rb +1 -1
  33. data/lib/no_brainer/document/core.rb +1 -1
  34. data/lib/no_brainer/document/criteria.rb +9 -2
  35. data/lib/no_brainer/document/dirty.rb +1 -3
  36. data/lib/no_brainer/document/index/index.rb +83 -0
  37. data/lib/no_brainer/document/index/meta_store.rb +31 -0
  38. data/lib/no_brainer/document/index/synchronizer.rb +68 -0
  39. data/lib/no_brainer/document/index.rb +13 -79
  40. data/lib/no_brainer/document/lazy_fetch.rb +5 -5
  41. data/lib/no_brainer/document/persistance.rb +27 -7
  42. data/lib/no_brainer/document/polymorphic.rb +1 -1
  43. data/lib/no_brainer/document/types.rb +6 -4
  44. data/lib/no_brainer/document/uniqueness.rb +3 -3
  45. data/lib/no_brainer/document/validation.rb +13 -4
  46. data/lib/no_brainer/fork.rb +1 -0
  47. data/lib/no_brainer/query_runner/database_on_demand.rb +6 -5
  48. data/lib/no_brainer/query_runner/logger.rb +10 -6
  49. data/lib/no_brainer/query_runner/missing_index.rb +5 -4
  50. data/lib/no_brainer/query_runner/reconnect.rb +20 -17
  51. data/lib/no_brainer/query_runner/run_options.rb +3 -0
  52. data/lib/no_brainer/query_runner/table_on_demand.rb +11 -8
  53. data/lib/no_brainer/railtie/database.rake +12 -12
  54. data/lib/no_brainer/railtie.rb +5 -5
  55. data/lib/no_brainer/rql.rb +9 -0
  56. data/lib/nobrainer.rb +5 -3
  57. metadata +8 -8
  58. data/lib/no_brainer/index_manager.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6cf2f24165387c78884eaffc90c04da535bc571f
4
- data.tar.gz: b8d3b1bd887c454e3f017664a559c70bec9a6312
3
+ metadata.gz: 1d6a500e008eff372e800b011e917d121c07e00a
4
+ data.tar.gz: e43be89ac4963422c26559727c9a68011ee25f8a
5
5
  SHA512:
6
- metadata.gz: fa00190b18bbd8a498c855ef04fe7720c169c51edbb0d360939dc2ea5c4b76db47361615bc86153207e22be8096946bdce120c35d3b3da6fe99a9db4a4b58f94
7
- data.tar.gz: 034d21dabdce9c900b11c899e20d5e69af34560ec2980bbfdba386974bc30828ac6fb9d590fc73a4ac9b568dbfdbdb9a9fb60760c4aeceac963396fd8128e679
6
+ metadata.gz: 56015e819aba3537ab60ea5f039d4b391ec803cc61b5bf246e44da642787e62606e9c02339b17f05976d5428ec3e4fa0e335cb51694b840bd2ba7821b6d66b20
7
+ data.tar.gz: d4b78185fed14f5baa28165462a4088096da640d93c0dfb3f20c851e968deceb1f48f13602850075a7eea2408fa23e52c2694d140bea1c9a937d7d49d5084f53
@@ -1,36 +1,49 @@
1
1
  require 'logger'
2
2
 
3
3
  module NoBrainer::Config
4
+ SETTINGS = {
5
+ :app_name => { :default => ->{ default_app_name } },
6
+ :environment => { :default => ->{ default_environment } },
7
+ :rethinkdb_url => { :default => ->{ default_rethinkdb_url } },
8
+ :logger => { :default => ->{ default_logger } },
9
+ :warn_on_active_record => { :default => ->{ true }, :valid_values => [true, false] },
10
+ :auto_create_databases => { :default => ->{ true }, :valid_values => [true, false] },
11
+ :auto_create_tables => { :default => ->{ true }, :valid_values => [true, false] },
12
+ :max_retries_on_connection_failure => { :default => ->{ default_max_retries_on_connection_failure } },
13
+ :durability => { :default => ->{ default_durability }, :valid_values => [:hard, :soft] },
14
+ :user_timezone => { :default => ->{ :local }, :valid_values => [:unchanged, :utc, :local] },
15
+ :db_timezone => { :default => ->{ :utc }, :valid_values => [:unchanged, :utc, :local] },
16
+ :colorize_logger => { :default => ->{ true }, :valid_values => [true, false] },
17
+ :distributed_lock_class => { :default => ->{ nil } },
18
+ :per_thread_connection => { :default => ->{ false }, :valid_values => [true, false] },
19
+ }
20
+
4
21
  class << self
5
- mattr_accessor :rethinkdb_url, :logger, :warn_on_active_record,
6
- :auto_create_databases, :auto_create_tables,
7
- :max_reconnection_tries, :durability,
8
- :user_timezone, :db_timezone, :colorize_logger,
9
- :distributed_lock_class, :per_thread_connection
22
+ attr_accessor(*SETTINGS.keys)
23
+
24
+ def max_reconnection_tries=(value)
25
+ STDERR.puts "[NoBrainer] config.max_reconnection_tries is deprecated and will be removed"
26
+ STDERR.puts "[NoBrainer] use config.max_retries_on_connection_failure instead."
27
+ self.max_retries_on_connection_failure = value
28
+ end
10
29
 
11
30
  def apply_defaults
12
- self.rethinkdb_url = default_rethinkdb_url
13
- self.logger = default_logger
14
- self.warn_on_active_record = true
15
- self.auto_create_databases = true
16
- self.auto_create_tables = true
17
- self.max_reconnection_tries = 10
18
- self.durability = default_durability
19
- self.user_timezone = :local
20
- self.db_timezone = :utc
21
- self.colorize_logger = true
22
- self.distributed_lock_class = nil
23
- self.per_thread_connection = false
31
+ @applied_defaults_for = SETTINGS.keys.reject { |k| instance_variable_defined?("@#{k}") }
32
+ @applied_defaults_for.each { |k| __send__("#{k}=", SETTINGS[k][:default].call) }
33
+ end
34
+
35
+ def assert_valid_options!
36
+ SETTINGS.each { |k,v| assert_array_in(k, v[:valid_values]) if v[:valid_values] }
24
37
  end
25
38
 
26
39
  def reset!
27
- @configured = false
28
- apply_defaults
40
+ instance_variables.each { |ivar| remove_instance_variable(ivar) }
29
41
  end
30
42
 
31
43
  def configure(&block)
32
- apply_defaults unless configured?
44
+ @applied_defaults_for.to_a.each { |k| remove_instance_variable("@#{k}") }
33
45
  block.call(self) if block
46
+ apply_defaults
34
47
  assert_valid_options!
35
48
  @configured = true
36
49
 
@@ -41,21 +54,28 @@ module NoBrainer::Config
41
54
  !!@configured
42
55
  end
43
56
 
44
- def assert_valid_options!
45
- assert_array_in :durability, [:hard, :soft]
46
- assert_array_in :user_timezone, [:unchanged, :utc, :local]
47
- assert_array_in :db_timezone, [:unchanged, :utc, :local]
48
- end
49
-
50
57
  def assert_array_in(name, values)
51
58
  unless __send__(name).in?(values)
52
59
  raise ArgumentError.new("Unknown configuration for #{name}: #{__send__(name)}. Valid values are: #{values.inspect}")
53
60
  end
54
61
  end
55
62
 
63
+ def dev_mode?
64
+ self.environment.to_s.in? %w(development test)
65
+ end
66
+
67
+ def default_app_name
68
+ defined?(Rails) ? Rails.application.class.parent_name.underscore.presence : nil rescue nil
69
+ end
70
+
71
+ def default_environment
72
+ return Rails.env if defined?(Rails.env)
73
+ ENV['RUBY_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || :production
74
+ end
75
+
56
76
  def default_rethinkdb_url
57
77
  db = ENV['RETHINKDB_DB'] || ENV['RDB_DB']
58
- db ||= "#{Rails.application.class.parent_name.underscore}_#{Rails.env}" rescue nil
78
+ db ||= "#{self.app_name}_#{self.environment}" if self.app_name && self.environment
59
79
  host = ENV['RETHINKDB_HOST'] || ENV['RDB_HOST'] || 'localhost'
60
80
  port = ENV['RETHINKDB_PORT'] || ENV['RDB_PORT']
61
81
  auth = ENV['RETHINKDB_AUTH'] || ENV['RDB_AUTH']
@@ -69,7 +89,11 @@ module NoBrainer::Config
69
89
  end
70
90
 
71
91
  def default_durability
72
- (defined?(Rails.env) && (Rails.env.test? || Rails.env.development?)) ? :soft : :hard
92
+ dev_mode? ? :soft : :hard
93
+ end
94
+
95
+ def default_max_retries_on_connection_failure
96
+ dev_mode? ? 1 : 15
73
97
  end
74
98
  end
75
99
  end
@@ -52,6 +52,7 @@ class NoBrainer::Connection
52
52
  # database (drop)
53
53
  def purge!(options={})
54
54
  table_list.each do |table_name|
55
+ next if table_name =~ /^nobrainer_/
55
56
  NoBrainer.run { |r| r.table(table_name).delete }
56
57
  end
57
58
  true
@@ -43,7 +43,7 @@ module NoBrainer::ConnectionManager
43
43
  end
44
44
 
45
45
  def _disconnect
46
- self.current_connection.try(:disconnect, :noreply_wait => true) rescue nil
46
+ self.current_connection.try(:close, :noreply_wait => false) rescue nil
47
47
  self.current_connection = nil
48
48
  end
49
49
 
@@ -1,23 +1,15 @@
1
1
  module NoBrainer::Criteria::AfterFind
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :_after_find }
4
+ included { criteria_option :after_find, :merge_with => :append_array }
5
5
 
6
6
  def after_find(b=nil, &block)
7
- chain { |criteria| criteria._after_find = [b || block] }
8
- end
9
-
10
- def merge!(criteria, options={})
11
- super
12
- if criteria._after_find.present?
13
- self._after_find = (self._after_find || []) + criteria._after_find
14
- end
15
- self
7
+ chain(:after_find => [b, block].compact)
16
8
  end
17
9
 
18
10
  def _instantiate_doc(attrs)
19
11
  super.tap do |doc|
20
- self._after_find.to_a.each { |block| block.call(doc) }
12
+ @options[:after_find].to_a.each { |block| block.call(doc) }
21
13
  doc.run_callbacks(:find) if doc.is_a?(NoBrainer::Document)
22
14
  end
23
15
  end
@@ -2,24 +2,27 @@ module NoBrainer::Criteria::Aggregate
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def min(*a, &b)
5
- instantiate_doc NoBrainer.run { aggregate_rql(:min, *a, &b) }
5
+ instantiate_doc run { aggregate_rql(:min, *a, &b) }
6
6
  end
7
7
 
8
8
  def max(*a, &b)
9
- instantiate_doc NoBrainer.run { aggregate_rql(:max, *a, &b) }
9
+ instantiate_doc run { aggregate_rql(:max, *a, &b) }
10
10
  end
11
11
 
12
12
  def sum(*a, &b)
13
- NoBrainer.run { aggregate_rql(:sum, *a, &b) }
13
+ run { aggregate_rql(:sum, *a, &b) }
14
14
  end
15
15
 
16
16
  def avg(*a, &b)
17
- NoBrainer.run { aggregate_rql(:avg, *a, &b) }
17
+ run { aggregate_rql(:avg, *a, &b) }
18
18
  end
19
19
 
20
20
  private
21
21
 
22
22
  def aggregate_rql(type, *a, &b)
23
- without_ordering.without_plucking.to_rql.__send__(type, *klass.with_fields_aliased(a), &b)
23
+ rql = without_ordering.without_plucking.to_rql
24
+ rql = rql.__send__(type, *model.with_fields_aliased(a), &b)
25
+ rql = rql.default(nil) unless type == :sum
26
+ rql
24
27
  end
25
28
  end
@@ -1,14 +1,14 @@
1
1
  module NoBrainer::Criteria::Cache
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :_with_cache }
4
+ included { criteria_option :with_cache, :merge_with => :set_scalar }
5
5
 
6
6
  def with_cache
7
- chain { |criteria| criteria._with_cache = true }
7
+ chain(:with_cache => true)
8
8
  end
9
9
 
10
10
  def without_cache
11
- chain { |criteria| criteria._with_cache = false }
11
+ chain(:with_cache => false)
12
12
  end
13
13
 
14
14
  def inspect
@@ -18,14 +18,14 @@ module NoBrainer::Criteria::Cache
18
18
  end
19
19
 
20
20
  def merge!(criteria, options={})
21
+ if options[:copy_cache_from] && options[:copy_cache_from].cached?
22
+ @cache = options[:copy_cache_from].instance_variable_get(:@cache)
23
+ end
21
24
  super
22
- self._with_cache = criteria._with_cache unless criteria._with_cache.nil?
23
- self.reload unless options[:keep_cache]
24
- self
25
25
  end
26
26
 
27
27
  def with_cache?
28
- @_with_cache != false
28
+ finalized_criteria.options[:with_cache] != false
29
29
  end
30
30
 
31
31
  def reload
@@ -1,14 +1,26 @@
1
1
  module NoBrainer::Criteria::Core
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :init_options }
4
+ included do
5
+ singleton_class.send(:attr_accessor, :options_definitions)
6
+ self.options_definitions = {}
7
+ attr_accessor :options
8
+
9
+ criteria_option :model, :merge_with => :set_scalar
10
+ criteria_option :finalized, :merge_with => :set_scalar
11
+ end
5
12
 
6
13
  def initialize(options={})
7
- self.init_options = options
14
+ @options = options
15
+ end
16
+
17
+ def dup
18
+ # We don't keep any of the instance variables except options.
19
+ self.class.new(@options.dup)
8
20
  end
9
21
 
10
- def klass
11
- init_options[:klass]
22
+ def model
23
+ @options[:model]
12
24
  end
13
25
 
14
26
  def to_rql
@@ -16,16 +28,21 @@ module NoBrainer::Criteria::Core
16
28
  end
17
29
 
18
30
  def inspect
19
- # rescue super because sometimes klass is not set.
31
+ # rescue super because sometimes model is not set.
20
32
  to_rql.inspect rescue super
21
33
  end
22
34
 
23
- def run(rql=nil)
24
- NoBrainer.run(:criteria => self) { (rql || to_rql) }
35
+ def run(&block)
36
+ block ||= proc { to_rql }
37
+ NoBrainer.run(:criteria => self, &block)
25
38
  end
26
39
 
27
40
  def merge!(criteria, options={})
28
- self.init_options = self.init_options.merge(criteria.init_options)
41
+ criteria.options.each do |k,v|
42
+ merge_proc = self.class.options_definitions[k]
43
+ raise "Non declared option: #{k}" unless merge_proc
44
+ @options[k] = merge_proc.call(@options[k], v)
45
+ end
29
46
  self
30
47
  end
31
48
 
@@ -40,16 +57,14 @@ module NoBrainer::Criteria::Core
40
57
 
41
58
  private
42
59
 
43
- def chain(options={}, &block)
44
- tmp = self.class.new(self.init_options) # we might want to optimize that thing
45
- block.call(tmp)
46
- merge(tmp, options)
60
+ def chain(options={}, merge_options={}, &block)
61
+ merge(self.class.new(options), merge_options)
47
62
  end
48
63
 
49
64
  def compile_rql_pass1
50
65
  # This method is overriden by other modules.
51
- raise "Criteria not bound to a class" unless klass
52
- klass.rql_table
66
+ raise "Criteria not bound to a model" unless model
67
+ model.rql_table
53
68
  end
54
69
 
55
70
  def compile_rql_pass2
@@ -58,7 +73,7 @@ module NoBrainer::Criteria::Core
58
73
  end
59
74
 
60
75
  def finalized?
61
- !!init_options[:finalized]
76
+ !!@options[:finalized]
62
77
  end
63
78
 
64
79
  def finalized_criteria
@@ -66,8 +81,33 @@ module NoBrainer::Criteria::Core
66
81
  end
67
82
 
68
83
  module ClassMethods
84
+ def criteria_option(*names)
85
+ options = names.extract_options!
86
+
87
+ names.map(&:to_sym).each do |name|
88
+ merge_proc = options[:merge_with]
89
+ merge_proc = MergeStrategies.method(merge_proc) if merge_proc.is_a?(Symbol)
90
+ self.options_definitions[name] = merge_proc
91
+ end
92
+ end
93
+
69
94
  def _finalize_criteria(base)
70
- base.merge(base.class.new(:finalized => true))
95
+ base.__send__(:chain, :finalized => true)
96
+ end
97
+ end
98
+
99
+ module MergeStrategies
100
+ extend self
101
+ def set_scalar(a, b)
102
+ b
103
+ end
104
+
105
+ def merge_hash(a, b)
106
+ a ? a.merge(b) : b
107
+ end
108
+
109
+ def append_array(a, b)
110
+ a ? a+b : b
71
111
  end
72
112
  end
73
113
  end
@@ -2,7 +2,7 @@ module NoBrainer::Criteria::Count
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def count
5
- run(without_ordering.without_plucking.to_rql.count)
5
+ run { without_ordering.without_plucking.to_rql.count }
6
6
  end
7
7
 
8
8
  def empty?
@@ -2,7 +2,7 @@ module NoBrainer::Criteria::Delete
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def delete_all
5
- run(without_ordering.without_plucking.to_rql.delete)
5
+ run { without_ordering.without_plucking.to_rql.delete }
6
6
  end
7
7
 
8
8
  def destroy_all
@@ -0,0 +1,19 @@
1
+ module NoBrainer::Criteria::Extend
2
+ extend ActiveSupport::Concern
3
+
4
+ included { criteria_option :extend, :merge_with => :append_array }
5
+
6
+ def extend(*modules, &block)
7
+ options = modules.extract_options!
8
+ modules << Module.new(&block) if block
9
+
10
+ return super(*modules) if options[:original_behavior]
11
+ chain(:extend => [modules])
12
+ end
13
+
14
+ def merge!(criteria, options={})
15
+ super.tap do
16
+ @options[:extend].to_a.each { |modules| extend(*modules, :original_behavior => true) }
17
+ end
18
+ end
19
+ end
@@ -18,7 +18,7 @@ module NoBrainer::Criteria::First
18
18
  end
19
19
 
20
20
  def sample(n=nil)
21
- result = NoBrainer.run { self.without_ordering.to_rql.sample(n.nil? ? 1 : n) }
21
+ result = run { self.without_ordering.to_rql.sample(n.nil? ? 1 : n) }
22
22
  result = result.map(&method(:instantiate_doc))
23
23
  n.nil? ? result.first : result
24
24
  end
@@ -1,10 +1,10 @@
1
1
  module NoBrainer::Criteria::Index
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :with_index_name }
4
+ included { criteria_option :use_index, :merge_with => :set_scalar }
5
5
 
6
6
  def with_index(index_name=true)
7
- chain { |criteria| criteria.with_index_name = index_name }
7
+ chain(:use_index => index_name)
8
8
  end
9
9
 
10
10
  def without_index
@@ -12,25 +12,19 @@ module NoBrainer::Criteria::Index
12
12
  end
13
13
 
14
14
  def without_index?
15
- finalized_criteria.with_index_name == false
15
+ finalized_criteria.options[:use_index] == false
16
16
  end
17
17
 
18
18
  def used_index
19
- # only one of them will be active.
19
+ # Only one of them will be active.
20
20
  where_index_name || order_by_index_name
21
21
  end
22
22
 
23
- def merge!(criteria, options={})
24
- super
25
- self.with_index_name = criteria.with_index_name unless criteria.with_index_name.nil?
26
- self
27
- end
28
-
29
23
  def compile_rql_pass2
30
24
  super.tap do
31
- if with_index_name && (!used_index || order_by_index_name.to_s == klass.pk_name.to_s)
32
- # The implicit ordering on the indexed pk does not count.
33
- raise NoBrainer::Error::CannotUseIndex.new(with_index_name)
25
+ # The implicit ordering on the indexed pk does not count.
26
+ if @options[:use_index] && (!used_index || order_by_index_name.to_s == model.pk_name.to_s)
27
+ raise NoBrainer::Error::CannotUseIndex.new(@options[:use_index])
34
28
  end
35
29
  end
36
30
  end
@@ -1,31 +1,23 @@
1
1
  module NoBrainer::Criteria::Limit
2
- # TODO Test these guys
3
2
  extend ActiveSupport::Concern
4
3
 
5
- included { attr_accessor :_skip, :_limit }
4
+ included { criteria_option :skip, :limit, :merge_with => :set_scalar }
6
5
 
7
6
  def limit(value)
8
- chain { |criteria| criteria._limit = value }
7
+ chain(:limit => value)
9
8
  end
10
9
 
11
10
  def skip(value)
12
- chain { |criteria| criteria._skip = value }
11
+ chain(:skip => value)
13
12
  end
14
13
  alias_method :offset, :skip
15
14
 
16
- def merge!(criteria, options={})
17
- super
18
- self._skip = criteria._skip if criteria._skip
19
- self._limit = criteria._limit if criteria._limit
20
- self
21
- end
22
-
23
15
  private
24
16
 
25
17
  def compile_rql_pass2
26
18
  rql = super
27
- rql = rql.skip(_skip) if _skip
28
- rql = rql.limit(_limit) if _limit
19
+ rql = rql.skip(@options[:skip]) if @options[:skip]
20
+ rql = rql.limit(@options[:limit]) if @options[:limit]
29
21
  rql
30
22
  end
31
23
  end
@@ -1,16 +1,12 @@
1
1
  module NoBrainer::Criteria::OrderBy
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :order, :ordering_mode }
5
-
6
- def initialize(options={})
7
- super
8
- self.order = {}
9
- end
4
+ # The latest order_by() wins
5
+ included { criteria_option :order_by, :ordering_mode, :merge_with => :set_scalar }
10
6
 
11
7
  def order_by(*rules, &block)
12
8
  # Note: We are relying on the fact that Hashes are ordered (since 1.9)
13
- rules = [*rules, block].compact.map do |rule|
9
+ rules = [*rules, block].flatten.compact.map do |rule|
14
10
  case rule
15
11
  when Hash then
16
12
  bad_rule = rule.values.reject { |v| v.in? [:asc, :desc] }.first
@@ -21,34 +17,20 @@ module NoBrainer::Criteria::OrderBy
21
17
  end
22
18
  end.reduce({}, :merge)
23
19
 
24
- chain do |criteria|
25
- criteria.order = rules
26
- criteria.ordering_mode = :normal
27
- end
20
+ chain(:order_by => rules, :ordering_mode => :normal)
28
21
  end
29
22
 
30
23
  def without_ordering
31
- chain { |criteria| criteria.ordering_mode = :disabled }
32
- end
33
-
34
- def merge!(criteria, options={})
35
- super
36
- # The latest order_by() wins
37
- self.order = criteria.order if criteria.order.present?
38
- self.ordering_mode = criteria.ordering_mode unless criteria.ordering_mode.nil?
39
- self
24
+ chain(:ordering_mode => :disabled)
40
25
  end
41
26
 
42
27
  def reverse_order
43
- chain do |criteria|
44
- criteria.ordering_mode =
45
- case self.ordering_mode
46
- when nil then :reversed
47
- when :normal then :reversed
48
- when :reversed then :normal
49
- when :disabled then :disabled
50
- end
51
- end
28
+ chain(:ordering_mode => case @options[:ordering_mode]
29
+ when nil then :reversed
30
+ when :normal then :reversed
31
+ when :reversed then :normal
32
+ when :disabled then :disabled
33
+ end)
52
34
  end
53
35
 
54
36
  def order_by_indexed?
@@ -62,15 +44,15 @@ module NoBrainer::Criteria::OrderBy
62
44
  private
63
45
 
64
46
  def effective_order
65
- self.order.presence || (klass ? {klass.pk_name => :asc} : {})
47
+ @options[:order_by].presence || (model ? {model.pk_name => :asc} : {})
66
48
  end
67
49
 
68
50
  def reverse_order?
69
- self.ordering_mode == :reversed
51
+ @options[:ordering_mode] == :reversed
70
52
  end
71
53
 
72
54
  def should_order?
73
- self.ordering_mode != :disabled
55
+ @options[:ordering_mode] != :disabled
74
56
  end
75
57
 
76
58
  class IndexFinder < Struct.new(:criteria, :index_name, :rql_proc)
@@ -83,15 +65,15 @@ module NoBrainer::Criteria::OrderBy
83
65
  end
84
66
 
85
67
  def first_key_indexable?
86
- (first_key.is_a?(Symbol) || first_key.is_a?(String)) && criteria.klass.has_index?(first_key)
68
+ (first_key.is_a?(Symbol) || first_key.is_a?(String)) && criteria.model.has_index?(first_key)
87
69
  end
88
70
 
89
71
  def find_index
90
72
  return if criteria.without_index?
91
73
  return unless first_key_indexable?
92
74
 
93
- if criteria.with_index_name && criteria.with_index_name != true
94
- return unless first_key.to_s == criteria.with_index_name.to_s
75
+ if criteria.options[:use_index] && criteria.options[:use_index] != true
76
+ return unless first_key.to_s == criteria.options[:use_index].to_s
95
77
  end
96
78
 
97
79
  # We need make sure that the where index finder has been invoked, it has priority.
@@ -116,9 +98,9 @@ module NoBrainer::Criteria::OrderBy
116
98
 
117
99
  rql_rules = _effective_order.map do |k,v|
118
100
  if order_by_index_finder.index_name == k
119
- k = klass.lookup_index_alias(k)
101
+ k = model.lookup_index_alias(k)
120
102
  else
121
- k = klass.lookup_field_alias(k)
103
+ k = model.lookup_field_alias(k)
122
104
  end
123
105
 
124
106
  case v
@@ -127,14 +109,13 @@ module NoBrainer::Criteria::OrderBy
127
109
  end
128
110
  end
129
111
 
130
- # We can only apply an index order_by on a table() term.
131
- # We are going to try to go so and if we cannot, we'll simply apply
132
- # the ordering in pass2, which will happen after a potential filter().
112
+ # We can only apply an indexed order_by on a table() RQL term.
113
+ # If we can, great. Otherwise, the ordering is applied in pass2, which will
114
+ # happen after a potential filter(), which is better for perfs.
133
115
  if order_by_index_finder.could_find_index?
134
116
  options = { :index => rql_rules.shift }
135
117
  rql = rql.order_by(*rql_rules, options)
136
118
  else
137
- # Stashing @rql_rules for pass2
138
119
  @rql_rules_pass2 = rql_rules
139
120
  end
140
121