nobrainer 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/no_brainer/config.rb +36 -8
- data/lib/no_brainer/connection.rb +16 -19
- data/lib/no_brainer/connection_manager.rb +10 -10
- data/lib/no_brainer/criteria.rb +1 -1
- data/lib/no_brainer/criteria/eager_load.rb +1 -1
- data/lib/no_brainer/criteria/find.rb +1 -1
- data/lib/no_brainer/criteria/first.rb +2 -2
- data/lib/no_brainer/criteria/first_or_create.rb +32 -19
- data/lib/no_brainer/criteria/join.rb +62 -0
- data/lib/no_brainer/criteria/where.rb +25 -14
- data/lib/no_brainer/document.rb +1 -1
- data/lib/no_brainer/document/association/belongs_to.rb +4 -3
- data/lib/no_brainer/document/association/eager_loader.rb +26 -25
- data/lib/no_brainer/document/association/has_many.rb +3 -2
- data/lib/no_brainer/document/association/has_many_through.rb +1 -2
- data/lib/no_brainer/document/association/has_one.rb +4 -0
- data/lib/no_brainer/document/atomic_ops.rb +31 -4
- data/lib/no_brainer/document/attributes.rb +12 -9
- data/lib/no_brainer/document/core.rb +18 -18
- data/lib/no_brainer/document/criteria.rb +3 -2
- data/lib/no_brainer/document/dirty.rb +3 -3
- data/lib/no_brainer/document/index.rb +3 -3
- data/lib/no_brainer/document/index/index.rb +5 -5
- data/lib/no_brainer/document/index/meta_store.rb +1 -1
- data/lib/no_brainer/document/index/synchronizer.rb +5 -17
- data/lib/no_brainer/document/missing_attributes.rb +7 -2
- data/lib/no_brainer/document/primary_key.rb +14 -8
- data/lib/no_brainer/document/table_config.rb +118 -0
- data/lib/no_brainer/document/table_config/synchronizer.rb +21 -0
- data/lib/no_brainer/document/timestamps.rb +4 -0
- data/lib/no_brainer/document/validation/core.rb +1 -1
- data/lib/no_brainer/document/validation/uniqueness.rb +1 -1
- data/lib/no_brainer/lock.rb +4 -4
- data/lib/no_brainer/profiler/logger.rb +1 -1
- data/lib/no_brainer/query_runner/database_on_demand.rb +1 -1
- data/lib/no_brainer/query_runner/reconnect.rb +37 -21
- data/lib/no_brainer/query_runner/table_on_demand.rb +12 -5
- data/lib/no_brainer/railtie/database.rake +14 -4
- data/lib/no_brainer/rql.rb +3 -2
- data/lib/no_brainer/symbol_decoration.rb +1 -1
- data/lib/no_brainer/system.rb +17 -0
- data/lib/no_brainer/system/cluster_config.rb +5 -0
- data/lib/no_brainer/system/db_config.rb +5 -0
- data/lib/no_brainer/system/document.rb +24 -0
- data/lib/no_brainer/system/issue.rb +10 -0
- data/lib/no_brainer/system/job.rb +10 -0
- data/lib/no_brainer/system/log.rb +11 -0
- data/lib/no_brainer/system/server_config.rb +7 -0
- data/lib/no_brainer/system/server_status.rb +9 -0
- data/lib/no_brainer/system/stat.rb +11 -0
- data/lib/no_brainer/system/table_config.rb +10 -0
- data/lib/no_brainer/system/table_status.rb +8 -0
- data/lib/nobrainer.rb +7 -6
- data/lib/rails/generators/templates/nobrainer.rb +11 -2
- metadata +17 -3
- data/lib/no_brainer/document/store_in.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8406e0c14de6dc8379b7047aa6d7254675e1b1fb
|
4
|
+
data.tar.gz: 0d9906ef55e4f74e5317a6d2377d4c1f19ac9799
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdcbf5021c15bad13546e7b346eb73fb7f6d0b62ab861578110b0ab81aafda8c364a4b6fe9b282d0f8f8c6d732ebd8dcc3f5be9aada980b79d34d93b0fed6fea
|
7
|
+
data.tar.gz: afa546845837919bb06b01c7ba4168626b74263244986d4d9d3a7428b9b68b24faa968572f110546fb5afb9c4b57dbb236d5c48556b9ae691d271deea62ff816
|
data/lib/no_brainer/config.rb
CHANGED
@@ -4,18 +4,21 @@ module NoBrainer::Config
|
|
4
4
|
SETTINGS = {
|
5
5
|
:app_name => { :default => ->{ default_app_name } },
|
6
6
|
:environment => { :default => ->{ default_environment } },
|
7
|
-
:
|
7
|
+
:rethinkdb_urls => { :default => ->{ [default_rethinkdb_url] } },
|
8
|
+
:ssl_options => { :default => ->{ nil } },
|
8
9
|
:logger => { :default => ->{ default_logger } },
|
9
10
|
:colorize_logger => { :default => ->{ true }, :valid_values => [true, false] },
|
10
11
|
:warn_on_active_record => { :default => ->{ true }, :valid_values => [true, false] },
|
11
12
|
:max_retries_on_connection_failure => { :default => ->{ default_max_retries_on_connection_failure } },
|
12
13
|
:durability => { :default => ->{ default_durability }, :valid_values => [:hard, :soft] },
|
13
|
-
:
|
14
|
+
:table_options => { :default => ->{ {:shards => 1, :replicas => 1, :write_acks => :majority} },
|
15
|
+
:valid_keys => [:shards, :replicas, :primary_replica_tag, :write_acks, :durability] },
|
16
|
+
:max_string_length => { :default => ->{ 255 } },
|
14
17
|
:user_timezone => { :default => ->{ :local }, :valid_values => [:unchanged, :utc, :local] },
|
15
18
|
:db_timezone => { :default => ->{ :utc }, :valid_values => [:unchanged, :utc, :local] },
|
16
19
|
:geo_options => { :default => ->{ {:geo_system => 'WGS84', :unit => 'm'} } },
|
17
20
|
:distributed_lock_class => { :default => ->{ "NoBrainer::Lock" } },
|
18
|
-
:lock_options => { :default => ->{ { :expire => 60, :timeout => 10 } } },
|
21
|
+
:lock_options => { :default => ->{ { :expire => 60, :timeout => 10 } }, :valid_keys => [:expire, :timeout] },
|
19
22
|
:per_thread_connection => { :default => ->{ false }, :valid_values => [true, false] },
|
20
23
|
:machine_id => { :default => ->{ default_machine_id } },
|
21
24
|
:criteria_cache_max_entries => { :default => -> { 10_000 } },
|
@@ -44,7 +47,12 @@ module NoBrainer::Config
|
|
44
47
|
end
|
45
48
|
|
46
49
|
def assert_valid_options
|
47
|
-
SETTINGS.each
|
50
|
+
SETTINGS.each do |k,v|
|
51
|
+
assert_value_in(k, v[:valid_values]) if v[:valid_values]
|
52
|
+
assert_hash_keys_in(k, v[:valid_keys]) if v[:valid_keys]
|
53
|
+
end
|
54
|
+
|
55
|
+
validate_urls
|
48
56
|
end
|
49
57
|
|
50
58
|
def reset!
|
@@ -52,7 +60,9 @@ module NoBrainer::Config
|
|
52
60
|
end
|
53
61
|
|
54
62
|
def configure(&block)
|
55
|
-
@applied_defaults_for.to_a.each
|
63
|
+
@applied_defaults_for.to_a.each do |k|
|
64
|
+
remove_instance_variable("@#{k}") if instance_variable_defined?("@#{k}")
|
65
|
+
end
|
56
66
|
block.call(self) if block
|
57
67
|
apply_defaults
|
58
68
|
assert_valid_options
|
@@ -65,9 +75,16 @@ module NoBrainer::Config
|
|
65
75
|
!!@configured
|
66
76
|
end
|
67
77
|
|
68
|
-
def
|
69
|
-
unless __send__(name).in?(
|
70
|
-
raise ArgumentError.new("
|
78
|
+
def assert_value_in(name, valid_values)
|
79
|
+
unless __send__(name).in?(valid_values)
|
80
|
+
raise ArgumentError.new("Invalid configuration for #{name}: #{__send__(name)}. Valid values are: #{valid_values.inspect}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def assert_hash_keys_in(name, valid_keys)
|
85
|
+
extra_keys = __send__(name).keys - valid_keys
|
86
|
+
unless extra_keys.empty?
|
87
|
+
raise ArgumentError.new("Invalid configuration for #{name}: #{__send__(name)}. Valid keys are: #{valid_keys.inspect}")
|
71
88
|
end
|
72
89
|
end
|
73
90
|
|
@@ -84,6 +101,10 @@ module NoBrainer::Config
|
|
84
101
|
ENV['RUBY_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || :production
|
85
102
|
end
|
86
103
|
|
104
|
+
def rethinkdb_url=(value)
|
105
|
+
self.rethinkdb_urls = [*value]
|
106
|
+
end
|
107
|
+
|
87
108
|
def default_rethinkdb_url
|
88
109
|
db = ENV['RETHINKDB_DB'] || ENV['RDB_DB']
|
89
110
|
db ||= "#{self.app_name}_#{self.environment}" if self.app_name && self.environment
|
@@ -95,6 +116,13 @@ module NoBrainer::Config
|
|
95
116
|
url
|
96
117
|
end
|
97
118
|
|
119
|
+
def validate_urls
|
120
|
+
# This is not connecting, just validating the format.
|
121
|
+
dbs = rethinkdb_urls.compact.map { |url| NoBrainer::Connection.new(url).parsed_uri[:db] }.uniq
|
122
|
+
raise "Please specify at least one rethinkdb_url" if dbs.size == 0
|
123
|
+
raise "All the rethinkdb_urls must specify the same db name (instead of #{dbs.inspect})" if dbs.size != 1
|
124
|
+
end
|
125
|
+
|
98
126
|
def default_logger
|
99
127
|
defined?(Rails.logger) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
|
100
128
|
end
|
@@ -1,17 +1,16 @@
|
|
1
1
|
require 'rethinkdb'
|
2
|
+
require 'uri'
|
2
3
|
|
3
4
|
class NoBrainer::Connection
|
4
|
-
attr_accessor :
|
5
|
+
attr_accessor :parsed_uri
|
5
6
|
|
6
7
|
def initialize(uri)
|
7
|
-
|
8
|
-
parsed_uri # just to raise an exception if there is a problem.
|
8
|
+
parse_uri(uri)
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
@parsed_uri
|
13
|
-
|
14
|
-
uri = URI.parse(self.uri)
|
11
|
+
def parse_uri(uri)
|
12
|
+
@parsed_uri = begin
|
13
|
+
uri = URI.parse(uri)
|
15
14
|
|
16
15
|
if uri.scheme != 'rethinkdb'
|
17
16
|
raise NoBrainer::Error::Connection,
|
@@ -26,22 +25,20 @@ class NoBrainer::Connection
|
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
28
|
+
def uri
|
29
|
+
"rethinkdb://#{'****@' if parsed_uri[:auth_key]}#{parsed_uri[:host]}:#{parsed_uri[:port]}/#{parsed_uri[:db]}"
|
30
|
+
end
|
31
|
+
|
29
32
|
def raw
|
30
|
-
|
33
|
+
options = parsed_uri
|
34
|
+
options = options.merge(:ssl => NoBrainer::Config.ssl_options) if NoBrainer::Config.ssl_options
|
35
|
+
@raw ||= RethinkDB::Connection.new(options).tap { NoBrainer.logger.info("Connected to #{uri}") }
|
31
36
|
end
|
32
37
|
|
33
38
|
delegate :reconnect, :close, :run, :to => :raw
|
34
39
|
alias_method :connect, :raw
|
35
40
|
alias_method :disconnect, :close
|
36
41
|
|
37
|
-
[:db_create, :db_drop, :db_list, :table_create, :table_drop, :table_list].each do |cmd|
|
38
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
39
|
-
def #{cmd}(*args)
|
40
|
-
NoBrainer.run { |r| r.#{cmd}(*args) }
|
41
|
-
end
|
42
|
-
RUBY
|
43
|
-
end
|
44
|
-
|
45
42
|
def default_db
|
46
43
|
parsed_uri[:db]
|
47
44
|
end
|
@@ -51,13 +48,13 @@ class NoBrainer::Connection
|
|
51
48
|
end
|
52
49
|
|
53
50
|
def drop!
|
54
|
-
db_drop(current_db)
|
51
|
+
NoBrainer.run { |r| r.db_drop(current_db) }
|
55
52
|
end
|
56
53
|
|
57
54
|
# Note that truncating each table (purge!) is much faster than dropping the database (drop!)
|
58
55
|
def purge!
|
59
|
-
table_list.each do |table_name|
|
60
|
-
# keeping the index meta store because indexes are not going away
|
56
|
+
NoBrainer.run { |r| r.table_list }.each do |table_name|
|
57
|
+
# keeping the index meta store because indexes are not going away when purging
|
61
58
|
next if table_name == NoBrainer::Document::Index::MetaStore.table_name
|
62
59
|
NoBrainer.run { |r| r.table(table_name).delete }
|
63
60
|
end
|
@@ -4,11 +4,7 @@ module NoBrainer::ConnectionManager
|
|
4
4
|
@lock = Mutex.new
|
5
5
|
|
6
6
|
def synchronize(&block)
|
7
|
-
|
8
|
-
block.call
|
9
|
-
else
|
10
|
-
@lock.synchronize { block.call }
|
11
|
-
end
|
7
|
+
@lock.synchronize { block.call }
|
12
8
|
end
|
13
9
|
|
14
10
|
def warn_for_other_orms
|
@@ -26,15 +22,12 @@ module NoBrainer::ConnectionManager
|
|
26
22
|
end
|
27
23
|
|
28
24
|
def get_new_connection
|
29
|
-
url = NoBrainer::Config.rethinkdb_url
|
30
|
-
raise "Please specify a database connection to RethinkDB" unless url
|
31
|
-
|
32
25
|
# We don't want to warn on "rails g nobrainer:install", but because it's
|
33
26
|
# hard to check when the generator is running because of spring as it wipes
|
34
27
|
# ARGV. So we check for other ORMs during the connection instantiation.
|
35
28
|
warn_for_other_orms
|
36
29
|
|
37
|
-
NoBrainer::Connection.new(
|
30
|
+
NoBrainer::Connection.new(get_next_url)
|
38
31
|
end
|
39
32
|
|
40
33
|
def current_connection
|
@@ -53,6 +46,12 @@ module NoBrainer::ConnectionManager
|
|
53
46
|
end
|
54
47
|
end
|
55
48
|
|
49
|
+
def get_next_url
|
50
|
+
@urls ||= NoBrainer::Config.rethinkdb_urls.shuffle
|
51
|
+
@cycle_index = (@cycle_index || 0) + 1
|
52
|
+
@urls[@cycle_index % @urls.size] # not using .cycle due to threading issues
|
53
|
+
end
|
54
|
+
|
56
55
|
def connection
|
57
56
|
c = self.current_connection
|
58
57
|
return c if c
|
@@ -73,8 +72,9 @@ module NoBrainer::ConnectionManager
|
|
73
72
|
|
74
73
|
def notify_url_change
|
75
74
|
synchronize do
|
75
|
+
@urls = nil
|
76
76
|
c = current_connection
|
77
|
-
_disconnect if c &&
|
77
|
+
_disconnect if c && !NoBrainer::Config.rethinkdb_urls.include?(c.uri)
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
data/lib/no_brainer/criteria.rb
CHANGED
@@ -5,5 +5,5 @@ class NoBrainer::Criteria
|
|
5
5
|
autoload_and_include :Core, :Run, :Raw, :Scope, :AfterFind, :Where, :OrderBy,
|
6
6
|
:Limit, :Pluck, :Count, :Delete, :Enumerable, :Find,
|
7
7
|
:First, :FirstOrCreate, :Changes, :Aggregate, :EagerLoad,
|
8
|
-
:Update, :Cache, :Index, :Extend
|
8
|
+
:Update, :Cache, :Index, :Extend, :Join
|
9
9
|
end
|
@@ -45,7 +45,7 @@ module NoBrainer::Criteria::EagerLoad
|
|
45
45
|
|
46
46
|
def perform_eager_load(docs)
|
47
47
|
if should_eager_load? && docs.present?
|
48
|
-
NoBrainer
|
48
|
+
NoBrainer.eager_load(docs, @options[:eager_load])
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
@@ -10,11 +10,11 @@ module NoBrainer::Criteria::First
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def first!
|
13
|
-
first
|
13
|
+
first || (raise NoBrainer::Error::DocumentNotFound)
|
14
14
|
end
|
15
15
|
|
16
16
|
def last!
|
17
|
-
last
|
17
|
+
last || (raise NoBrainer::Error::DocumentNotFound)
|
18
18
|
end
|
19
19
|
|
20
20
|
def sample(n=nil)
|
@@ -1,31 +1,42 @@
|
|
1
1
|
module NoBrainer::Criteria::FirstOrCreate
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
def first_or_create(create_params={}, &block)
|
5
|
-
_first_or_create(create_params, :save_method => :save
|
4
|
+
def first_or_create(create_params={}, save_options={}, &block)
|
5
|
+
_first_or_create(create_params, save_options.merge(:save_method => :save?), &block)
|
6
6
|
end
|
7
7
|
|
8
|
-
def first_or_create!(create_params={}, &block)
|
9
|
-
_first_or_create(create_params, :save_method => :save
|
8
|
+
def first_or_create!(create_params={}, save_options={}, &block)
|
9
|
+
_first_or_create(create_params, save_options.merge(:save_method => :save!), &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def upsert(attrs, save_options={})
|
13
|
+
_upsert(attrs, save_options.merge(:save_method => :save?))
|
14
|
+
end
|
15
|
+
|
16
|
+
def upsert!(attrs, save_options={})
|
17
|
+
_upsert(attrs, save_options.merge(:save_method => :save!))
|
10
18
|
end
|
11
19
|
|
12
20
|
private
|
13
21
|
|
14
|
-
def
|
22
|
+
def _upsert(attrs, save_options)
|
23
|
+
attrs = attrs.symbolize_keys
|
24
|
+
unique_keys = get_model_unique_fields.detect { |keys| keys & attrs.keys == keys }
|
25
|
+
raise "Could not find a uniqueness validator within `#{attrs.keys.inspect}'.\n" +
|
26
|
+
"Please add a corresponding uniqueness validator" unless unique_keys
|
27
|
+
where(attrs.slice(*unique_keys)).__send__(:_first_or_create, attrs, save_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def _first_or_create(create_params, save_options, &block)
|
15
31
|
raise "Cannot use .raw() with .first_or_create()" if raw?
|
16
32
|
raise "Use first_or_create() on the root class `#{model.root_class}'" unless model.is_root_class?
|
17
33
|
|
18
34
|
where_params = extract_where_params()
|
19
|
-
keys = where_params.keys
|
20
35
|
|
21
|
-
#
|
22
|
-
#
|
23
|
-
|
24
|
-
|
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
|
36
|
+
# Note that we are not matching a subset of the keys on the uniqueness
|
37
|
+
# validators; we need an exact match on the keys.
|
38
|
+
keys = where_params.keys
|
39
|
+
unless get_model_unique_fields.include?(keys.sort)
|
29
40
|
# We could do without a uniqueness validator, but it's much preferable to
|
30
41
|
# have it, so that we don't conflict with others create(), not just others
|
31
42
|
# first_or_create().
|
@@ -53,7 +64,7 @@ module NoBrainer::Criteria::FirstOrCreate
|
|
53
64
|
create_params = create_params.symbolize_keys
|
54
65
|
|
55
66
|
keys_in_conflict = create_params.keys & where_params.keys
|
56
|
-
keys_in_conflict = keys_in_conflict.
|
67
|
+
keys_in_conflict = keys_in_conflict.reject { |k| create_params[k] == where_params[k] }
|
57
68
|
unless keys_in_conflict.empty?
|
58
69
|
raise "where() and first_or_create() were given conflicting values " +
|
59
70
|
"on the following keys: #{keys_in_conflict.inspect}"
|
@@ -69,7 +80,8 @@ module NoBrainer::Criteria::FirstOrCreate
|
|
69
80
|
end
|
70
81
|
|
71
82
|
new_instance.assign_attributes(create_params)
|
72
|
-
|
83
|
+
save_method = save_options.delete(:save_method)
|
84
|
+
new_instance.__send__(save_method, save_options)
|
73
85
|
return new_instance
|
74
86
|
ensure
|
75
87
|
new_instance.try(:unlock_unique_fields)
|
@@ -93,9 +105,10 @@ module NoBrainer::Criteria::FirstOrCreate
|
|
93
105
|
end]
|
94
106
|
end
|
95
107
|
|
96
|
-
def
|
97
|
-
|
108
|
+
def get_model_unique_fields
|
109
|
+
[[model.pk_name]] +
|
110
|
+
model.unique_validators
|
98
111
|
.flat_map { |validator| validator.attributes.map { |attr| [attr, validator] } }
|
99
|
-
.map { |f, validator| [
|
112
|
+
.map { |f, validator| [f, *validator.scope].map(&:to_sym).sort }
|
100
113
|
end
|
101
114
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module NoBrainer::Criteria::Join
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included { criteria_option :join, :merge_with => :append_array }
|
5
|
+
|
6
|
+
def join(*values)
|
7
|
+
chain(:join => values)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def _compile_join_ast(value)
|
13
|
+
case value
|
14
|
+
when Hash then
|
15
|
+
value.reduce({}) do |h, (k,v)|
|
16
|
+
association = model.association_metadata[k.to_sym]
|
17
|
+
raise "`#{k}' must be an association on `#{model}'" unless association
|
18
|
+
raise "join() does not support through associations" if association.options[:through]
|
19
|
+
|
20
|
+
criteria = association.base_criteria
|
21
|
+
criteria = case v
|
22
|
+
when NoBrainer::Criteria then criteria.merge(v)
|
23
|
+
when true then criteria
|
24
|
+
else criteria.join(v)
|
25
|
+
end
|
26
|
+
h.merge(association => criteria)
|
27
|
+
end
|
28
|
+
when Array then value.map { |v| _compile_join_ast(v) }.reduce({}, :merge)
|
29
|
+
else _compile_join_ast(value => true)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def join_ast
|
34
|
+
@join_ast ||= _compile_join_ast(@options[:join])
|
35
|
+
end
|
36
|
+
|
37
|
+
def _instantiate_model(attrs, options={})
|
38
|
+
return super unless @options[:join] && !raw?
|
39
|
+
|
40
|
+
associated_instances = join_ast.map do |association, criteria|
|
41
|
+
[association, criteria.send(:_instantiate_model, attrs.delete(association.target_name.to_s))]
|
42
|
+
end
|
43
|
+
super(attrs, options).tap do |instance|
|
44
|
+
associated_instances.each do |association, assoc_instance|
|
45
|
+
instance.associations[association].preload([assoc_instance])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def compile_rql_pass2
|
51
|
+
return super unless @options[:join]
|
52
|
+
|
53
|
+
join_ast.reduce(super) do |rql, (association, criteria)|
|
54
|
+
rql.concat_map do |doc|
|
55
|
+
key = doc[association.eager_load_owner_key]
|
56
|
+
criteria.where(association.eager_load_target_key => key).to_rql.map do |assoc_doc|
|
57
|
+
doc.merge(association.target_name => assoc_doc)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -11,6 +11,10 @@ module NoBrainer::Criteria::Where
|
|
11
11
|
chain(:where_ast => parse_clause([*args, block].compact))
|
12
12
|
end
|
13
13
|
|
14
|
+
def _where(*args, &block)
|
15
|
+
chain(:where_ast => parse_clause([*args, block].compact, :unsafe => true))
|
16
|
+
end
|
17
|
+
|
14
18
|
def self.merge_where_ast(a, b)
|
15
19
|
(a ? MultiOperator.new(:and, [a, b]) : b).simplify
|
16
20
|
end
|
@@ -90,20 +94,19 @@ module NoBrainer::Criteria::Where
|
|
90
94
|
|
91
95
|
def simplify
|
92
96
|
new_key_path = cast_key_path(key_path)
|
93
|
-
new_op, new_value = case op
|
97
|
+
new_key_modifier, new_op, new_value = case op
|
94
98
|
when :in then
|
95
99
|
case value
|
96
|
-
when Range then [:between, (cast_value(value.min)..cast_value(value.max))]
|
97
|
-
when Array then [:in, value.map(&method(:cast_value)).uniq]
|
100
|
+
when Range then [key_modifier, :between, (cast_value(value.min)..cast_value(value.max))]
|
101
|
+
when Array then [key_modifier, :in, value.map(&method(:cast_value)).uniq]
|
98
102
|
else raise ArgumentError.new "`in' takes an array/range, not #{value}"
|
99
103
|
end
|
100
|
-
when :between then [op, (cast_value(value.min)..cast_value(value.max))]
|
101
|
-
when :
|
102
|
-
|
103
|
-
|
104
|
-
else [op, cast_value(value)]
|
104
|
+
when :between then [key_modifier, op, (cast_value(value.min)..cast_value(value.max))]
|
105
|
+
when :include then ensure_scalar_for(op); [:any, :eq, cast_value(value)]
|
106
|
+
when :defined, :undefined then ensure_scalar_for(op); [key_modifier, op, cast_value(value)]
|
107
|
+
else [key_modifier, op, cast_value(value)]
|
105
108
|
end
|
106
|
-
BinaryOperator.new(new_key_path,
|
109
|
+
BinaryOperator.new(new_key_path, new_key_modifier, new_op, new_value, model, true)
|
107
110
|
end
|
108
111
|
|
109
112
|
def to_rql(doc)
|
@@ -115,7 +118,8 @@ module NoBrainer::Criteria::Where
|
|
115
118
|
case key_modifier
|
116
119
|
when :scalar then
|
117
120
|
case op
|
118
|
-
when :defined
|
121
|
+
when :defined then value ? doc.has_fields(key) : doc.has_fields(key).not
|
122
|
+
when :undefined then !value ? doc.has_fields(key) : doc.has_fields(key).not
|
119
123
|
else to_rql_scalar(doc[key])
|
120
124
|
end
|
121
125
|
when :any then doc[key].map { |lvalue| to_rql_scalar(lvalue) }.contains(true)
|
@@ -142,6 +146,10 @@ module NoBrainer::Criteria::Where
|
|
142
146
|
|
143
147
|
private
|
144
148
|
|
149
|
+
def ensure_scalar_for(op)
|
150
|
+
raise "Incorrect use of `#{op}' and `#{key_modifier}'" if key_modifier != :scalar
|
151
|
+
end
|
152
|
+
|
145
153
|
def association
|
146
154
|
return nil if key_path.size > 1
|
147
155
|
@association ||= [model.association_metadata[key_path.first]]
|
@@ -161,7 +169,7 @@ module NoBrainer::Criteria::Where
|
|
161
169
|
value.pk_value
|
162
170
|
else
|
163
171
|
case op
|
164
|
-
when :defined then NoBrainer::Boolean.nobrainer_cast_user_to_model(value)
|
172
|
+
when :defined, :undefined then NoBrainer::Boolean.nobrainer_cast_user_to_model(value)
|
165
173
|
when :intersects
|
166
174
|
raise "Use a geo object with `intersects`" unless value.is_a?(NoBrainer::Geo::Base)
|
167
175
|
value
|
@@ -308,14 +316,17 @@ module NoBrainer::Criteria::Where
|
|
308
316
|
end if op == :eq
|
309
317
|
|
310
318
|
nested_prefix = options[:nested_prefix] || []
|
319
|
+
|
320
|
+
tail_args = [op, value, self.model, !!options[:unsafe]]
|
321
|
+
|
311
322
|
case key
|
312
323
|
when Symbol::Decoration
|
313
324
|
raise "Use only one .not, .all or .any modifiers in the query" if key.symbol.is_a?(Symbol::Decoration)
|
314
325
|
case key.decorator
|
315
|
-
when :any, :all then BinaryOperator.new(nested_prefix + [key.symbol], key.decorator,
|
316
|
-
when :not then UnaryOperator.new(:not, BinaryOperator.new(nested_prefix + [key.symbol], :scalar,
|
326
|
+
when :any, :all then BinaryOperator.new(nested_prefix + [key.symbol], key.decorator, *tail_args)
|
327
|
+
when :not then UnaryOperator.new(:not, BinaryOperator.new(nested_prefix + [key.symbol], :scalar, *tail_args))
|
317
328
|
end
|
318
|
-
else BinaryOperator.new(nested_prefix + [key], :scalar,
|
329
|
+
else BinaryOperator.new(nested_prefix + [key], :scalar, *tail_args)
|
319
330
|
end
|
320
331
|
end
|
321
332
|
|