nobrainer 0.18.0 → 0.19.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/config.rb +35 -37
  3. data/lib/no_brainer/connection.rb +1 -1
  4. data/lib/no_brainer/criteria/after_find.rb +3 -11
  5. data/lib/no_brainer/criteria/cache.rb +7 -7
  6. data/lib/no_brainer/criteria/core.rb +49 -10
  7. data/lib/no_brainer/criteria/delete.rb +1 -1
  8. data/lib/no_brainer/criteria/extend.rb +4 -16
  9. data/lib/no_brainer/criteria/index.rb +7 -13
  10. data/lib/no_brainer/criteria/limit.rb +5 -12
  11. data/lib/no_brainer/criteria/order_by.rb +19 -35
  12. data/lib/no_brainer/criteria/pluck.rb +16 -22
  13. data/lib/no_brainer/criteria/preload.rb +9 -15
  14. data/lib/no_brainer/criteria/raw.rb +4 -10
  15. data/lib/no_brainer/criteria/scope.rb +4 -10
  16. data/lib/no_brainer/criteria/where.rb +172 -130
  17. data/lib/no_brainer/document/aliases.rb +10 -2
  18. data/lib/no_brainer/document/association/belongs_to.rb +3 -3
  19. data/lib/no_brainer/document/association/core.rb +1 -1
  20. data/lib/no_brainer/document/association/eager_loader.rb +4 -3
  21. data/lib/no_brainer/document/association/has_many.rb +2 -2
  22. data/lib/no_brainer/document/atomic_ops.rb +29 -30
  23. data/lib/no_brainer/document/attributes.rb +15 -19
  24. data/lib/no_brainer/document/callbacks.rb +1 -1
  25. data/lib/no_brainer/document/id.rb +7 -3
  26. data/lib/no_brainer/document/index.rb +20 -10
  27. data/lib/no_brainer/document/persistance.rb +11 -10
  28. data/lib/no_brainer/document/timestamps.rb +4 -2
  29. data/lib/no_brainer/document/types/binary.rb +0 -1
  30. data/lib/no_brainer/document/types/boolean.rb +0 -1
  31. data/lib/no_brainer/document/types.rb +0 -9
  32. data/lib/no_brainer/document/uniqueness.rb +0 -1
  33. data/lib/no_brainer/document/validation.rb +5 -5
  34. data/lib/no_brainer/error.rb +1 -0
  35. data/lib/no_brainer/query_runner/connection_lock.rb +1 -1
  36. data/lib/no_brainer/query_runner/reconnect.rb +9 -11
  37. data/lib/no_brainer/query_runner/table_on_demand.rb +1 -1
  38. data/lib/no_brainer/railtie/database.rake +2 -2
  39. data/lib/no_brainer/rql.rb +1 -1
  40. data/lib/nobrainer.rb +1 -6
  41. data/lib/rails/generators/nobrainer.rb +1 -1
  42. metadata +18 -5
  43. data/lib/no_brainer/decorated_symbol.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e146036464db0297a156c6987cb51011137f245
4
- data.tar.gz: 7f6815c09410489fbe26b1c0faebdc7aefe001e1
3
+ metadata.gz: 5dc33c00dc3aa418ae3bd36b0ccb2618a8c0e47f
4
+ data.tar.gz: 3a4c171e4c41341d14a3c4b6103b463be1b12701
5
5
  SHA512:
6
- metadata.gz: 00b32de4c7ccd965972369698d6619cfa873671d73c5805d03c37e026737eab42cfb5a3d2f94c00323ec05ce84c7baef0702c1572673ed90050f9f02dcf04458
7
- data.tar.gz: 661eca7f94fb210e481ecf064829a72b095cc59552de2197856a9226d2978b91c489c26a4045e919368f6d532433b6cbcd3aba8c459c9cd5d2e3bb7f115c6d92
6
+ metadata.gz: 70af56ece8a93103f310a729c338631e0bc56b52d4725ea2a304dfdb389e04fb530fda2b3d8039ec36e4f175ae9d510f3eb765fdf2d7a6f155ea37b822f0f464
7
+ data.tar.gz: 58371614f47e835c11b183fd2b004271b7c0031f27de8b17a9f8b0d3d2c4ee185a4e32efb5e495f5f6cf0b53fa9f64e4510150cfff3305d237431ec195733b13
@@ -1,30 +1,25 @@
1
1
  require 'logger'
2
2
 
3
3
  module NoBrainer::Config
4
- class << self
5
- mattr_accessor :app_name, :environment, :rethinkdb_url,
6
- :logger, :warn_on_active_record,
7
- :auto_create_databases, :auto_create_tables,
8
- :max_retries_on_connection_failure, :durability,
9
- :user_timezone, :db_timezone, :colorize_logger,
10
- :distributed_lock_class, :per_thread_connection
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
+ }
11
20
 
12
- def apply_defaults
13
- self.app_name = default_app_name
14
- self.environment = default_environment
15
- self.rethinkdb_url = default_rethinkdb_url
16
- self.logger = default_logger
17
- self.warn_on_active_record = true
18
- self.auto_create_databases = true
19
- self.auto_create_tables = true
20
- self.max_retries_on_connection_failure = default_max_retries_on_connection_failure
21
- self.durability = default_durability
22
- self.user_timezone = :local
23
- self.db_timezone = :utc
24
- self.colorize_logger = true
25
- self.distributed_lock_class = nil
26
- self.per_thread_connection = false
27
- end
21
+ class << self
22
+ attr_accessor(*SETTINGS.keys)
28
23
 
29
24
  def max_reconnection_tries=(value)
30
25
  STDERR.puts "[NoBrainer] config.max_reconnection_tries is deprecated and will be removed"
@@ -32,14 +27,23 @@ module NoBrainer::Config
32
27
  self.max_retries_on_connection_failure = value
33
28
  end
34
29
 
30
+ def apply_defaults
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] }
37
+ end
38
+
35
39
  def reset!
36
- @configured = false
37
- apply_defaults
40
+ instance_variables.each { |ivar| remove_instance_variable(ivar) }
38
41
  end
39
42
 
40
43
  def configure(&block)
41
- apply_defaults unless configured?
44
+ @applied_defaults_for.to_a.each { |k| remove_instance_variable("@#{k}") }
42
45
  block.call(self) if block
46
+ apply_defaults
43
47
  assert_valid_options!
44
48
  @configured = true
45
49
 
@@ -50,20 +54,18 @@ module NoBrainer::Config
50
54
  !!@configured
51
55
  end
52
56
 
53
- def assert_valid_options!
54
- assert_array_in :durability, [:hard, :soft]
55
- assert_array_in :user_timezone, [:unchanged, :utc, :local]
56
- assert_array_in :db_timezone, [:unchanged, :utc, :local]
57
- end
58
-
59
57
  def assert_array_in(name, values)
60
58
  unless __send__(name).in?(values)
61
59
  raise ArgumentError.new("Unknown configuration for #{name}: #{__send__(name)}. Valid values are: #{values.inspect}")
62
60
  end
63
61
  end
64
62
 
63
+ def dev_mode?
64
+ self.environment.to_s.in? %w(development test)
65
+ end
66
+
65
67
  def default_app_name
66
- defined?(Rails) ? Rails.application.class.parent_name.underscore : nil
68
+ defined?(Rails) ? Rails.application.class.parent_name.underscore.presence : nil rescue nil
67
69
  end
68
70
 
69
71
  def default_environment
@@ -93,9 +95,5 @@ module NoBrainer::Config
93
95
  def default_max_retries_on_connection_failure
94
96
  dev_mode? ? 1 : 15
95
97
  end
96
-
97
- def dev_mode?
98
- self.environment.to_sym.in?([:development, :test])
99
- end
100
98
  end
101
99
  end
@@ -50,7 +50,7 @@ class NoBrainer::Connection
50
50
 
51
51
  # Note that truncating each table (purge) is much faster than dropping the
52
52
  # database (drop)
53
- def purge!(options={})
53
+ def purge!
54
54
  table_list.each do |table_name|
55
55
  next if table_name =~ /^nobrainer_/
56
56
  NoBrainer.run { |r| r.table(table_name).delete }
@@ -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
@@ -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
22
  def model
11
- init_options[:model]
23
+ @options[:model]
12
24
  end
13
25
 
14
26
  def to_rql
@@ -26,7 +38,11 @@ module NoBrainer::Criteria::Core
26
38
  end
27
39
 
28
40
  def merge!(criteria, options={})
29
- 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
30
46
  self
31
47
  end
32
48
 
@@ -41,10 +57,8 @@ module NoBrainer::Criteria::Core
41
57
 
42
58
  private
43
59
 
44
- def chain(options={}, &block)
45
- tmp = self.class.new(self.init_options) # we might want to optimize that thing
46
- block.call(tmp)
47
- merge(tmp, options)
60
+ def chain(options={}, merge_options={})
61
+ merge(self.class.new(options), merge_options)
48
62
  end
49
63
 
50
64
  def compile_rql_pass1
@@ -59,7 +73,7 @@ module NoBrainer::Criteria::Core
59
73
  end
60
74
 
61
75
  def finalized?
62
- !!init_options[:finalized]
76
+ !!@options[:finalized]
63
77
  end
64
78
 
65
79
  def finalized_criteria
@@ -67,8 +81,33 @@ module NoBrainer::Criteria::Core
67
81
  end
68
82
 
69
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
+
70
94
  def _finalize_criteria(base)
71
- 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
72
111
  end
73
112
  end
74
113
  end
@@ -6,6 +6,6 @@ module NoBrainer::Criteria::Delete
6
6
  end
7
7
 
8
8
  def destroy_all
9
- to_a.each { |doc| doc.destroy }
9
+ to_a.each(&:destroy)
10
10
  end
11
11
  end
@@ -1,31 +1,19 @@
1
1
  module NoBrainer::Criteria::Extend
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :extend_modules }
4
+ included { criteria_option :extend, :merge_with => :append_array }
5
5
 
6
6
  def extend(*modules, &block)
7
7
  options = modules.extract_options!
8
8
  modules << Module.new(&block) if block
9
9
 
10
10
  return super(*modules) if options[:original_behavior]
11
-
12
- chain do |criteria|
13
- criteria.extend_modules ||= []
14
- criteria.extend_modules += [modules]
15
- end
11
+ chain(:extend => [modules])
16
12
  end
17
13
 
18
14
  def merge!(criteria, options={})
19
- super
20
-
21
- if criteria.extend_modules.present?
22
- self.extend_modules = self.extend_modules.to_a + criteria.extend_modules
15
+ super.tap do
16
+ @options[:extend].to_a.each { |modules| extend(*modules, :original_behavior => true) }
23
17
  end
24
-
25
- if self.extend_modules.present?
26
- self.extend_modules.each { |modules| extend(*modules, :original_behavior => true) }
27
- end
28
-
29
- self
30
18
  end
31
19
  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 == model.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,30 +1,23 @@
1
1
  module NoBrainer::Criteria::Limit
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :_skip, :_limit }
4
+ included { criteria_option :skip, :limit, :merge_with => :set_scalar }
5
5
 
6
6
  def limit(value)
7
- chain { |criteria| criteria._limit = value }
7
+ chain(:limit => value)
8
8
  end
9
9
 
10
10
  def skip(value)
11
- chain { |criteria| criteria._skip = value }
11
+ chain(:skip => value)
12
12
  end
13
13
  alias_method :offset, :skip
14
14
 
15
- def merge!(criteria, options={})
16
- super
17
- self._skip = criteria._skip if criteria._skip
18
- self._limit = criteria._limit if criteria._limit
19
- self
20
- end
21
-
22
15
  private
23
16
 
24
17
  def compile_rql_pass2
25
18
  rql = super
26
- rql = rql.skip(_skip) if _skip
27
- rql = rql.limit(_limit) if _limit
19
+ rql = rql.skip(@options[:skip]) if @options[:skip]
20
+ rql = rql.limit(@options[:limit]) if @options[:limit]
28
21
  rql
29
22
  end
30
23
  end
@@ -1,12 +1,8 @@
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)
@@ -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 || (model ? {model.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,17 @@ 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.model.has_index?(first_key)
68
+ return false unless first_key.is_a?(Symbol) || first_key.is_a?(String)
69
+ return false unless index = criteria.model.indexes[first_key.to_sym]
70
+ return !index.multi && !index.geo
87
71
  end
88
72
 
89
73
  def find_index
90
74
  return if criteria.without_index?
91
75
  return unless first_key_indexable?
92
76
 
93
- if criteria.with_index_name && criteria.with_index_name != true
94
- return unless first_key.to_s == criteria.with_index_name.to_s
77
+ if criteria.options[:use_index] && criteria.options[:use_index] != true
78
+ return unless first_key.to_s == criteria.options[:use_index].to_s
95
79
  end
96
80
 
97
81
  # We need make sure that the where index finder has been invoked, it has priority.
@@ -105,7 +89,7 @@ module NoBrainer::Criteria::OrderBy
105
89
 
106
90
  def order_by_index_finder
107
91
  return finalized_criteria.__send__(:order_by_index_finder) unless finalized?
108
- @order_by_index_finder ||= IndexFinder.new(self).tap { |index_finder| index_finder.find_index }
92
+ @order_by_index_finder ||= IndexFinder.new(self).tap(&:find_index)
109
93
  end
110
94
 
111
95
  def compile_rql_pass1
@@ -1,7 +1,8 @@
1
1
  module NoBrainer::Criteria::Pluck
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :missing_attributes }
4
+ included { criteria_option :missing_attributes, :merge_with =>
5
+ NoBrainer::Criteria::Pluck.method(:merge_missing_attributes) }
5
6
 
6
7
  def pluck(*attrs)
7
8
  _missing_attributes_criteria(:pluck, attrs)
@@ -16,24 +17,19 @@ module NoBrainer::Criteria::Pluck
16
17
  end
17
18
 
18
19
  def without_plucking
19
- chain { |criteria| criteria.missing_attributes = :remove }
20
+ chain(:missing_attributes => :remove)
20
21
  end
21
22
 
22
- def merge!(criteria, options={})
23
- return super unless criteria.missing_attributes
23
+ def self.merge_missing_attributes(a, b)
24
+ return nil if b.nil? || b == :remove
24
25
 
25
- if criteria.missing_attributes == :remove
26
- self.missing_attributes = nil
27
- else
28
- self.missing_attributes ||= {}
29
- criteria.missing_attributes.each do |type, attrs|
30
- old_attrs = self.missing_attributes[type] || {}.with_indifferent_access
31
- new_attrs = old_attrs.deep_merge(attrs)
32
- self.missing_attributes[type] = new_attrs
33
- end
26
+ a = a ? a.dup : {}
27
+ b.each do |type, attrs|
28
+ old_attrs = a[type] || {}.with_indifferent_access
29
+ new_attrs = old_attrs.deep_merge(attrs)
30
+ a[type] = new_attrs
34
31
  end
35
-
36
- self
32
+ a
37
33
  end
38
34
 
39
35
  private
@@ -41,16 +37,14 @@ module NoBrainer::Criteria::Pluck
41
37
  def _missing_attributes_criteria(type, args)
42
38
  raise ArgumentError if args.size.zero?
43
39
  args = [Hash[args.flatten.map { |k| [k, true] }]] unless args.size == 1 && args.first.is_a?(Hash)
44
- chain { |criteria| criteria.missing_attributes = {type => args.first} }
45
-
40
+ chain(:missing_attributes => {type => args.first})
46
41
  end
47
42
 
48
43
  def effective_missing_attributes
49
- return nil if self.missing_attributes.nil?
44
+ return nil if @options[:missing_attributes].nil?
50
45
  @effective_missing_attributes ||= begin
51
46
  # pluck gets priority
52
-
53
- missing_attributes = Hash[self.missing_attributes.map do |type, attrs|
47
+ missing_attributes = Hash[@options[:missing_attributes].map do |type, attrs|
54
48
  attrs = attrs.select { |k,v| v } # TODO recursive
55
49
  attrs.empty? ? nil : [type, attrs]
56
50
  end.compact]
@@ -65,9 +59,9 @@ module NoBrainer::Criteria::Pluck
65
59
  end
66
60
 
67
61
  def _instantiate_model(attrs, options={})
68
- return super if missing_attributes.nil?
62
+ return super if @options[:missing_attributes].nil?
69
63
  super(attrs, options.merge(:missing_attributes => effective_missing_attributes,
70
- :lazy_fetch => missing_attributes[:lazy_fetch]))
64
+ :lazy_fetch => @options[:missing_attributes][:lazy_fetch]))
71
65
  end
72
66
 
73
67
  def compile_rql_pass2
@@ -1,24 +1,18 @@
1
1
  module NoBrainer::Criteria::Preload
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :_preloads }
5
-
6
- def initialize(options={})
7
- super
8
- self._preloads = []
9
- end
4
+ included { criteria_option :preload, :merge_with => :append_array }
10
5
 
11
6
  def preload(*values)
12
- chain(:keep_cache => true) { |criteria| criteria._preloads = values }
7
+ chain({:preload => values}, :copy_cache_from => self)
13
8
  end
14
9
 
15
10
  def merge!(criteria, options={})
16
- super
17
- self._preloads = self._preloads + criteria._preloads
18
-
19
- # XXX Not pretty hack
20
- if criteria._preloads.present? && cached?
21
- perform_preloads(@cache)
11
+ super.tap do
12
+ # XXX Not pretty hack
13
+ if criteria.options[:preload].present? && criteria.cached?
14
+ perform_preloads(@cache)
15
+ end
22
16
  end
23
17
  end
24
18
 
@@ -35,7 +29,7 @@ module NoBrainer::Criteria::Preload
35
29
  private
36
30
 
37
31
  def should_preloads?
38
- self._preloads.present? && !raw?
32
+ @options[:preload].present? && !raw?
39
33
  end
40
34
 
41
35
  def get_one(criteria)
@@ -44,7 +38,7 @@ module NoBrainer::Criteria::Preload
44
38
 
45
39
  def perform_preloads(docs)
46
40
  if should_preloads? && docs.present?
47
- NoBrainer::Document::Association::EagerLoader.new.eager_load(docs, self._preloads)
41
+ NoBrainer::Document::Association::EagerLoader.new.eager_load(docs, @options[:preload])
48
42
  end
49
43
  end
50
44
  end