nobrainer 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/no_brainer/config.rb +9 -16
- data/lib/no_brainer/connection.rb +32 -26
- data/lib/no_brainer/criteria/chainable/core.rb +17 -20
- data/lib/no_brainer/criteria/chainable/limit.rb +2 -2
- data/lib/no_brainer/criteria/chainable/order_by.rb +24 -7
- data/lib/no_brainer/criteria/chainable/raw.rb +11 -3
- data/lib/no_brainer/criteria/chainable/scope.rb +11 -7
- data/lib/no_brainer/criteria/chainable/where.rb +32 -15
- data/lib/no_brainer/criteria/termination/cache.rb +13 -9
- data/lib/no_brainer/criteria/termination/eager_loading.rb +12 -26
- data/lib/no_brainer/document.rb +2 -6
- data/lib/no_brainer/document/association.rb +8 -7
- data/lib/no_brainer/document/association/belongs_to.rb +27 -22
- data/lib/no_brainer/document/association/core.rb +8 -8
- data/lib/no_brainer/document/association/eager_loader.rb +57 -0
- data/lib/no_brainer/document/association/has_many.rb +49 -26
- data/lib/no_brainer/document/association/has_many_through.rb +31 -0
- data/lib/no_brainer/document/association/has_one.rb +13 -0
- data/lib/no_brainer/document/association/has_one_through.rb +10 -0
- data/lib/no_brainer/document/attributes.rb +4 -3
- data/lib/no_brainer/document/core.rb +1 -1
- data/lib/no_brainer/document/criteria.rb +2 -3
- data/lib/no_brainer/document/index.rb +3 -3
- data/lib/no_brainer/document/polymorphic.rb +1 -1
- data/lib/no_brainer/document/serialization.rb +1 -1
- data/lib/no_brainer/document/store_in.rb +5 -3
- data/lib/no_brainer/error.rb +1 -0
- data/lib/no_brainer/query_runner.rb +2 -1
- data/lib/no_brainer/query_runner/driver.rb +1 -4
- data/lib/no_brainer/query_runner/missing_index.rb +11 -0
- data/lib/no_brainer/query_runner/run_options.rb +2 -3
- data/lib/no_brainer/railtie.rb +0 -1
- data/lib/no_brainer/version.rb +1 -1
- data/lib/nobrainer.rb +5 -5
- metadata +28 -24
- data/lib/no_brainer/database.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea32475119089388850bb55326aab70d4a3dbc16
|
4
|
+
data.tar.gz: dfbef7c1a239b75cb3b45d32b593026ef4d8df93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d83f20ced889fba1ba1048e6ad41f4c7a6a1ae02562c8bf230580a66e64306d9a064eb179a500b61b230f026bbc93a8bf8876f4b4edbf2e21c1a2140decb743d
|
7
|
+
data.tar.gz: a9a671db853f43d369550c7b1031a3a9ad17b9d5307aee6aea659e3808364c6d3d77b0ecfe9d2354a08efb99df7f44f085566d36d2f787a3e1d7469ec915ba52
|
data/lib/no_brainer/config.rb
CHANGED
@@ -2,9 +2,7 @@ module NoBrainer::Config
|
|
2
2
|
class << self
|
3
3
|
mattr_accessor :rethinkdb_url, :logger, :warn_on_active_record,
|
4
4
|
:auto_create_databases, :auto_create_tables,
|
5
|
-
:
|
6
|
-
:max_reconnection_tries, :include_root_in_json,
|
7
|
-
:durability, :colorize_logger
|
5
|
+
:max_reconnection_tries, :durability, :colorize_logger
|
8
6
|
|
9
7
|
def apply_defaults
|
10
8
|
self.rethinkdb_url = default_rethinkdb_url
|
@@ -12,10 +10,7 @@ module NoBrainer::Config
|
|
12
10
|
self.warn_on_active_record = true
|
13
11
|
self.auto_create_databases = true
|
14
12
|
self.auto_create_tables = true
|
15
|
-
self.cache_documents = true
|
16
|
-
self.auto_include_timestamps = true
|
17
13
|
self.max_reconnection_tries = 10
|
18
|
-
self.include_root_in_json = false
|
19
14
|
self.durability = default_durability
|
20
15
|
self.colorize_logger = true
|
21
16
|
end
|
@@ -38,16 +33,14 @@ module NoBrainer::Config
|
|
38
33
|
end
|
39
34
|
|
40
35
|
def default_rethinkdb_url
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
"rethinkdb://#{":#{auth}@" if auth}#{host}#{":#{port}" if port}/#{db_name}"
|
50
|
-
end
|
36
|
+
db = ENV['RETHINKDB_DB'] || ENV['RDB_DB']
|
37
|
+
db ||= "#{Rails.application.class.parent_name.underscore}_#{Rails.env}" if defined?(Rails)
|
38
|
+
host = ENV['RETHINKDB_HOST'] || ENV['RDB_HOST'] || 'localhost'
|
39
|
+
port = ENV['RETHINKDB_PORT'] || ENV['RDB_PORT']
|
40
|
+
auth = ENV['RETHINKDB_AUTH'] || ENV['RDB_AUTH']
|
41
|
+
url = ENV['RETHINKDB_URL'] || ENV['RDB_URL']
|
42
|
+
url ||= "rethinkdb://#{":#{auth}@" if auth}#{host}#{":#{port}" if port}/#{db}" if db
|
43
|
+
url
|
51
44
|
end
|
52
45
|
|
53
46
|
def default_logger
|
@@ -1,24 +1,38 @@
|
|
1
1
|
require 'rethinkdb'
|
2
2
|
|
3
3
|
class NoBrainer::Connection
|
4
|
-
|
5
|
-
|
6
|
-
attr_accessor :uri, :host, :port, :database_name, :auth_key
|
4
|
+
attr_accessor :uri
|
7
5
|
|
8
6
|
def initialize(uri)
|
9
7
|
self.uri = uri
|
10
|
-
|
8
|
+
parsed_uri # just to raise an exception if there is a problem.
|
9
|
+
end
|
10
|
+
|
11
|
+
def parsed_uri
|
12
|
+
require 'uri'
|
13
|
+
uri = URI.parse(self.uri)
|
14
|
+
|
15
|
+
if uri.scheme != 'rethinkdb'
|
16
|
+
raise NoBrainer::Error::Connection,
|
17
|
+
"Invalid URI. Expecting something like rethinkdb://host:port/database. Got #{uri}"
|
18
|
+
end
|
19
|
+
|
20
|
+
{ :auth_key => uri.password,
|
21
|
+
:host => uri.host,
|
22
|
+
:port => uri.port || 28015,
|
23
|
+
:db => uri.path.gsub(/^\//, ''),
|
24
|
+
}.tap { |result| raise "No database specified in #{uri}" unless result[:db].present? }
|
11
25
|
end
|
12
26
|
|
13
27
|
def raw
|
14
|
-
@raw ||= RethinkDB::Connection.new(
|
28
|
+
@raw ||= RethinkDB::Connection.new(parsed_uri)
|
15
29
|
end
|
16
30
|
|
17
31
|
delegate :reconnect, :close, :run, :to => :raw
|
18
32
|
alias_method :connect, :raw
|
19
33
|
alias_method :disconnect, :close
|
20
34
|
|
21
|
-
[:db_create, :db_drop, :db_list].each do |cmd|
|
35
|
+
[:db_create, :db_drop, :db_list, :table_create, :table_drop, :table_list].each do |cmd|
|
22
36
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
23
37
|
def #{cmd}(*args)
|
24
38
|
NoBrainer.run { |r| r.#{cmd}(*args) }
|
@@ -26,28 +40,20 @@ class NoBrainer::Connection
|
|
26
40
|
RUBY
|
27
41
|
end
|
28
42
|
|
29
|
-
def
|
30
|
-
|
43
|
+
def drop!
|
44
|
+
# XXX Thread.current[:nobrainer_options] is set by NoBrainer::QueryRunner::RunOptions
|
45
|
+
db = (Thread.current[:nobrainer_options] || parsed_uri)[:db]
|
46
|
+
db_drop(db)['dropped'] == 1
|
31
47
|
end
|
32
48
|
|
33
|
-
|
34
|
-
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
if parsed_uri.scheme != 'rethinkdb'
|
40
|
-
raise NoBrainer::Error::Connection,
|
41
|
-
"Invalid URI. Expecting something like rethinkdb://host:port/database. Got #{uri}"
|
49
|
+
# Note that truncating each table (purge) is much faster than dropping the
|
50
|
+
# database (drop)
|
51
|
+
def purge!(options={})
|
52
|
+
table_list.each do |table_name|
|
53
|
+
NoBrainer.run { |r| r.table(table_name).delete }
|
42
54
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
def apply_connection_settings!(uri)
|
48
|
-
self.host = uri.host
|
49
|
-
self.port = uri.port || 28015
|
50
|
-
self.database_name = uri.path.gsub(/^\//, '')
|
51
|
-
self.auth_key = uri.password
|
55
|
+
true
|
56
|
+
rescue RuntimeError => e
|
57
|
+
raise e unless e.message =~ /No entry with that name/
|
52
58
|
end
|
53
59
|
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
module NoBrainer::Criteria::Chainable::Core
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
included { attr_accessor :
|
4
|
+
included { attr_accessor :init_options }
|
5
5
|
|
6
6
|
def initialize(options={})
|
7
|
-
self.
|
7
|
+
self.init_options = options
|
8
8
|
end
|
9
9
|
|
10
10
|
def klass
|
11
|
-
|
11
|
+
init_options[:klass]
|
12
12
|
end
|
13
13
|
|
14
14
|
def to_rql
|
15
|
-
|
15
|
+
with_default_scope_applied.__send__(:compile_rql_pass2)
|
16
16
|
end
|
17
17
|
|
18
18
|
def inspect
|
@@ -29,39 +29,36 @@ module NoBrainer::Criteria::Chainable::Core
|
|
29
29
|
NoBrainer.run(rql || to_rql)
|
30
30
|
end
|
31
31
|
|
32
|
-
def merge!(criteria)
|
33
|
-
self.
|
32
|
+
def merge!(criteria, options={})
|
33
|
+
self.init_options = self.init_options.merge(criteria.init_options)
|
34
34
|
self
|
35
35
|
end
|
36
36
|
|
37
|
-
def merge(criteria)
|
38
|
-
dup.tap { |new_criteria| new_criteria.merge!(criteria) }
|
37
|
+
def merge(criteria, options={})
|
38
|
+
dup.tap { |new_criteria| new_criteria.merge!(criteria, options) }
|
39
39
|
end
|
40
40
|
|
41
41
|
def ==(other)
|
42
|
-
return
|
43
|
-
return to_a == other if other.is_a?(Enumerable) && other.first.is_a?(NoBrainer::Document)
|
42
|
+
return to_a == other if other.is_a?(Array)
|
44
43
|
super
|
45
44
|
end
|
46
45
|
|
47
46
|
private
|
48
47
|
|
49
|
-
def chain(&block)
|
50
|
-
tmp = self.class.new(
|
48
|
+
def chain(options={}, &block)
|
49
|
+
tmp = self.class.new(self.init_options) # we might want to optimize that thing
|
51
50
|
block.call(tmp)
|
52
|
-
merge(tmp)
|
51
|
+
merge(tmp, options)
|
53
52
|
end
|
54
53
|
|
55
|
-
def
|
54
|
+
def compile_rql_pass1
|
56
55
|
# This method is overriden by other modules.
|
57
|
-
|
58
|
-
|
59
|
-
self
|
56
|
+
raise "Criteria not bound to a class" unless klass
|
57
|
+
klass.rql_table
|
60
58
|
end
|
61
59
|
|
62
|
-
def
|
60
|
+
def compile_rql_pass2
|
63
61
|
# This method is overriden by other modules.
|
64
|
-
|
65
|
-
klass.rql_table
|
62
|
+
compile_rql_pass1
|
66
63
|
end
|
67
64
|
end
|
@@ -13,7 +13,7 @@ module NoBrainer::Criteria::Chainable::Limit
|
|
13
13
|
end
|
14
14
|
alias_method :offset, :skip
|
15
15
|
|
16
|
-
def merge!(criteria)
|
16
|
+
def merge!(criteria, options={})
|
17
17
|
super
|
18
18
|
self._skip = criteria._skip if criteria._skip
|
19
19
|
self._limit = criteria._limit if criteria._limit
|
@@ -22,7 +22,7 @@ module NoBrainer::Criteria::Chainable::Limit
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
def
|
25
|
+
def compile_rql_pass2
|
26
26
|
rql = super
|
27
27
|
rql = rql.skip(_skip) if _skip
|
28
28
|
rql = rql.limit(_limit) if _limit
|
@@ -28,7 +28,7 @@ module NoBrainer::Criteria::Chainable::OrderBy
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def merge!(criteria)
|
31
|
+
def merge!(criteria, options={})
|
32
32
|
super
|
33
33
|
# The latest order_by() wins
|
34
34
|
self.order = criteria.order if criteria.order.present?
|
@@ -43,14 +43,16 @@ module NoBrainer::Criteria::Chainable::OrderBy
|
|
43
43
|
private
|
44
44
|
|
45
45
|
def effective_order
|
46
|
-
self.order.
|
46
|
+
self.order.presence || {:id => :asc}
|
47
47
|
end
|
48
48
|
|
49
49
|
def reverse_order?
|
50
50
|
!!self._reverse_order
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
53
|
+
def compile_rql_pass1
|
54
|
+
rql = super
|
55
|
+
|
54
56
|
rql_rules = effective_order.map do |k,v|
|
55
57
|
case v
|
56
58
|
when :asc then reverse_order? ? RethinkDB::RQL.new.desc(k) : RethinkDB::RQL.new.asc(k)
|
@@ -58,16 +60,31 @@ module NoBrainer::Criteria::Chainable::OrderBy
|
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
+
# We can only apply an index order_by on a table() term.
|
64
|
+
# We are going to try to go so and if we cannot, we'll simply apply
|
65
|
+
# the ordering in pass2, which will happen after a potential filter().
|
66
|
+
|
67
|
+
if rql.body.type == Term::TermType::TABLE && !without_index?
|
68
|
+
options = {}
|
63
69
|
first_key = effective_order.first[0]
|
64
|
-
first_key = nil if first_key == :id # FIXME For some reason, using the id index doesn't work.
|
65
70
|
if (first_key.is_a?(Symbol) || first_key.is_a?(String)) && klass.has_index?(first_key)
|
66
71
|
options[:index] = rql_rules.shift
|
67
72
|
end
|
73
|
+
|
74
|
+
rql = rql.order_by(*rql_rules, options)
|
75
|
+
else
|
76
|
+
# Stashing @rql_rules for pass2, which is a pretty gross hack.
|
77
|
+
# We should really use more of a middleware pattern to build the RQL.
|
78
|
+
@rql_rules_pass2 = rql_rules
|
68
79
|
end
|
69
80
|
|
70
|
-
|
81
|
+
rql
|
82
|
+
end
|
83
|
+
|
84
|
+
def compile_rql_pass2
|
85
|
+
rql = super
|
86
|
+
rql = rql.order_by(*@rql_rules_pass2) if @rql_rules_pass2
|
87
|
+
rql
|
71
88
|
end
|
72
89
|
|
73
90
|
def raise_bad_rule(rule)
|
@@ -1,15 +1,22 @@
|
|
1
1
|
module NoBrainer::Criteria::Chainable::Raw
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
included { attr_accessor :_raw }
|
4
|
+
included { attr_accessor :_raw, :__after_instantiate }
|
5
5
|
|
6
6
|
def raw
|
7
7
|
chain { |criteria| criteria._raw = true }
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
10
|
+
def _after_instantiate(block)
|
11
|
+
# Just some helper for the has_many association to set the inverses on the
|
12
|
+
# related belongs_to. A bit hackish.
|
13
|
+
chain { |criteria| criteria.__after_instantiate = block }
|
14
|
+
end
|
15
|
+
|
16
|
+
def merge!(criteria, options={})
|
11
17
|
super
|
12
18
|
self._raw = criteria._raw unless criteria._raw.nil?
|
19
|
+
self.__after_instantiate ||= criteria.__after_instantiate
|
13
20
|
self
|
14
21
|
end
|
15
22
|
|
@@ -20,6 +27,7 @@ module NoBrainer::Criteria::Chainable::Raw
|
|
20
27
|
end
|
21
28
|
|
22
29
|
def instantiate_doc(attrs)
|
23
|
-
raw? ? attrs : klass.new_from_db(attrs)
|
30
|
+
(raw? ? attrs : klass.new_from_db(attrs))
|
31
|
+
.tap { |doc| __after_instantiate.call(doc) if __after_instantiate }
|
24
32
|
end
|
25
33
|
end
|
@@ -11,7 +11,7 @@ module NoBrainer::Criteria::Chainable::Scope
|
|
11
11
|
chain { |criteria| criteria.use_default_scope = false }
|
12
12
|
end
|
13
13
|
|
14
|
-
def merge!(criteria)
|
14
|
+
def merge!(criteria, options={})
|
15
15
|
super
|
16
16
|
self.use_default_scope = criteria.use_default_scope unless criteria.use_default_scope.nil?
|
17
17
|
self
|
@@ -30,12 +30,16 @@ module NoBrainer::Criteria::Chainable::Scope
|
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
def should_apply_default_scope?
|
34
|
+
klass.default_scope_proc && use_default_scope != false
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_default_scope_applied
|
38
|
+
if should_apply_default_scope?
|
39
|
+
# XXX If default_scope.class != self.class, oops
|
40
|
+
klass.default_scope_proc.call.merge(self).unscoped
|
41
|
+
else
|
42
|
+
self
|
38
43
|
end
|
39
|
-
criteria
|
40
44
|
end
|
41
45
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module NoBrainer::Criteria::Chainable::Where
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
RESERVED_FIELDS = [:index, :default, :and, :or] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
|
5
|
-
|
6
4
|
included { attr_accessor :where_ast, :with_index_name }
|
7
5
|
|
8
6
|
def initialize(options={})
|
@@ -23,14 +21,14 @@ module NoBrainer::Criteria::Chainable::Where
|
|
23
21
|
end
|
24
22
|
|
25
23
|
def used_index
|
26
|
-
|
24
|
+
index_finder.index_name
|
27
25
|
end
|
28
26
|
|
29
27
|
def indexed?
|
30
|
-
|
28
|
+
index_finder.could_find_index?
|
31
29
|
end
|
32
30
|
|
33
|
-
def merge!(criteria)
|
31
|
+
def merge!(criteria, options={})
|
34
32
|
super
|
35
33
|
clauses = self.where_ast.clauses + criteria.where_ast.clauses
|
36
34
|
self.where_ast = MultiOperator.new(:and, clauses).simplify
|
@@ -58,6 +56,7 @@ module NoBrainer::Criteria::Chainable::Where
|
|
58
56
|
|
59
57
|
class BinaryOperator < Struct.new(:key, :op, :value)
|
60
58
|
def simplify
|
59
|
+
# TODO Simplify the in uniq
|
61
60
|
self
|
62
61
|
end
|
63
62
|
|
@@ -132,6 +131,17 @@ module NoBrainer::Criteria::Chainable::Where
|
|
132
131
|
end
|
133
132
|
|
134
133
|
class IndexFinder < Struct.new(:criteria, :index_name, :indexed_values, :ast)
|
134
|
+
def initialize(*args)
|
135
|
+
super
|
136
|
+
find_index
|
137
|
+
end
|
138
|
+
|
139
|
+
def could_find_index?
|
140
|
+
!!self.index_name
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
135
145
|
def get_candidate_clauses(*types)
|
136
146
|
Hash[criteria.where_ast.clauses
|
137
147
|
.select { |c| c.is_a?(BinaryOperator) && types.include?(c.op) }
|
@@ -175,23 +185,30 @@ module NoBrainer::Criteria::Chainable::Where
|
|
175
185
|
end
|
176
186
|
|
177
187
|
def find_index
|
178
|
-
return
|
179
|
-
|
180
|
-
if criteria.with_index_name && !could_find_index
|
188
|
+
return if criteria.__send__(:without_index?)
|
189
|
+
find_index_canonical || find_index_compound
|
190
|
+
if criteria.with_index_name && !could_find_index?
|
181
191
|
raise NoBrainer::Error::CannotUseIndex.new("Cannot use index #{criteria.with_index_name}")
|
182
192
|
end
|
183
|
-
!!could_find_index
|
184
193
|
end
|
185
194
|
end
|
186
195
|
|
187
|
-
def
|
196
|
+
def index_finder
|
197
|
+
return with_default_scope_applied.__send__(:index_finder) if should_apply_default_scope?
|
198
|
+
@index_finder ||= IndexFinder.new(self)
|
199
|
+
end
|
200
|
+
|
201
|
+
def compile_rql_pass1
|
188
202
|
rql = super
|
189
|
-
|
190
|
-
|
191
|
-
if finder.find_index
|
192
|
-
ast = finder.ast
|
193
|
-
rql = rql.get_all(*finder.indexed_values, :index => finder.index_name)
|
203
|
+
if index_finder.could_find_index?
|
204
|
+
rql = rql.get_all(*index_finder.indexed_values, :index => index_finder.index_name)
|
194
205
|
end
|
206
|
+
rql
|
207
|
+
end
|
208
|
+
|
209
|
+
def compile_rql_pass2
|
210
|
+
rql = super
|
211
|
+
ast = index_finder.could_find_index? ? index_finder.ast : self.where_ast
|
195
212
|
rql = rql.filter { |doc| ast.to_rql(doc) } if ast.clauses.present?
|
196
213
|
rql
|
197
214
|
end
|