nobrainer 0.8.0 → 0.9.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 +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
|