nobrainer 0.10.0 → 0.12.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +169 -0
  3. data/lib/no_brainer/criteria/after_find.rb +24 -0
  4. data/lib/no_brainer/criteria/{termination/cache.rb → cache.rb} +1 -1
  5. data/lib/no_brainer/criteria/{chainable/core.rb → core.rb} +1 -1
  6. data/lib/no_brainer/criteria/{termination/count.rb → count.rb} +1 -1
  7. data/lib/no_brainer/criteria/{termination/delete.rb → delete.rb} +1 -1
  8. data/lib/no_brainer/criteria/{termination/enumerable.rb → enumerable.rb} +1 -1
  9. data/lib/no_brainer/criteria/{termination/first.rb → first.rb} +1 -1
  10. data/lib/no_brainer/criteria/{termination/inc.rb → inc.rb} +1 -1
  11. data/lib/no_brainer/criteria/{chainable/limit.rb → limit.rb} +1 -1
  12. data/lib/no_brainer/criteria/{chainable/order_by.rb → order_by.rb} +1 -1
  13. data/lib/no_brainer/criteria/preload.rb +55 -0
  14. data/lib/no_brainer/criteria/raw.rb +25 -0
  15. data/lib/no_brainer/criteria/{chainable/scope.rb → scope.rb} +1 -1
  16. data/lib/no_brainer/criteria/{termination/update.rb → update.rb} +1 -1
  17. data/lib/no_brainer/criteria/{chainable/where.rb → where.rb} +61 -25
  18. data/lib/no_brainer/criteria.rb +4 -16
  19. data/lib/no_brainer/document/association/belongs_to.rb +9 -2
  20. data/lib/no_brainer/document/association/core.rb +2 -2
  21. data/lib/no_brainer/document/association/eager_loader.rb +7 -7
  22. data/lib/no_brainer/document/association/has_many.rb +1 -1
  23. data/lib/no_brainer/document/attributes.rb +9 -23
  24. data/lib/no_brainer/document/callbacks.rb +37 -0
  25. data/lib/no_brainer/document/core.rb +0 -2
  26. data/lib/no_brainer/document/criteria.rb +1 -1
  27. data/lib/no_brainer/document/dirty.rb +43 -46
  28. data/lib/no_brainer/document/dynamic_attributes.rb +11 -3
  29. data/lib/no_brainer/document/id.rb +1 -1
  30. data/lib/no_brainer/document/index.rb +0 -5
  31. data/lib/no_brainer/document/persistance.rb +39 -39
  32. data/lib/no_brainer/document/serialization.rb +0 -1
  33. data/lib/no_brainer/document/timestamps.rb +10 -9
  34. data/lib/no_brainer/document/types.rb +42 -39
  35. data/lib/no_brainer/document/validation.rb +5 -0
  36. data/lib/no_brainer/document.rb +3 -3
  37. data/lib/no_brainer/document_with_timestamps.rb +6 -0
  38. data/lib/no_brainer/error.rb +26 -9
  39. data/lib/no_brainer/query_runner/missing_index.rb +1 -1
  40. data/lib/no_brainer/railtie.rb +10 -0
  41. data/lib/nobrainer.rb +3 -3
  42. metadata +37 -34
  43. data/LICENSE.md +0 -7
  44. data/lib/no_brainer/criteria/chainable/raw.rb +0 -33
  45. data/lib/no_brainer/criteria/termination/eager_loading.rb +0 -50
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0fa1301301888d07f8c607cfd1102555aad40a92
4
- data.tar.gz: f05a189ae40c4e6b1f2d420dd84a4928a6f65203
3
+ metadata.gz: f9f0fb2d3cb21fba6fad94403706e6f5274f72f4
4
+ data.tar.gz: 74bfea6252ccc4eee6e67c003ed3facc7bbffe69
5
5
  SHA512:
6
- metadata.gz: 1bfa4718cb043f4847f972d46b2728c772475b8d07683a591d38d6ec039d06c09508d2df855791e935bbaa85f5d11780a367a383e9ebae0c19dce278dab7c83d
7
- data.tar.gz: 4584d38e6646c426672cea00c0f9aa58f05a6df4778586ae2d699662d927984b1f32ba3b87ebc3b787d4ad9181b19e9e4ecf3f4cfafdbc116f85920566534ed0
6
+ metadata.gz: 8da93f62774128bc6bfc485c356fd1e27b68ae0f3c5ccd0e9e81545210856a9282dccf0936369d3b92da90904e598d1c16d264a36b82e050e328ad02bda2758c
7
+ data.tar.gz: c8f6e9c112d89df7062d2ffde33952e688d6c7c47becde0d3391949abac41851149a3d3625025908f00429565152157366bf90d1147bf4d0e705e68e57b1c31a
data/LICENSE ADDED
@@ -0,0 +1,169 @@
1
+ Copyright (c) Nicolas Viennot
2
+
3
+ NoBrainer is licensed under the terms of the LGPLv3 license.
4
+
5
+ GNU LESSER GENERAL PUBLIC LICENSE
6
+ Version 3, 29 June 2007
7
+
8
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
9
+ Everyone is permitted to copy and distribute verbatim copies
10
+ of this license document, but changing it is not allowed.
11
+
12
+
13
+ This version of the GNU Lesser General Public License incorporates
14
+ the terms and conditions of version 3 of the GNU General Public
15
+ License, supplemented by the additional permissions listed below.
16
+
17
+ 0. Additional Definitions.
18
+
19
+ As used herein, "this License" refers to version 3 of the GNU Lesser
20
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
21
+ General Public License.
22
+
23
+ "The Library" refers to a covered work governed by this License,
24
+ other than an Application or a Combined Work as defined below.
25
+
26
+ An "Application" is any work that makes use of an interface provided
27
+ by the Library, but which is not otherwise based on the Library.
28
+ Defining a subclass of a class defined by the Library is deemed a mode
29
+ of using an interface provided by the Library.
30
+
31
+ A "Combined Work" is a work produced by combining or linking an
32
+ Application with the Library. The particular version of the Library
33
+ with which the Combined Work was made is also called the "Linked
34
+ Version".
35
+
36
+ The "Minimal Corresponding Source" for a Combined Work means the
37
+ Corresponding Source for the Combined Work, excluding any source code
38
+ for portions of the Combined Work that, considered in isolation, are
39
+ based on the Application, and not on the Linked Version.
40
+
41
+ The "Corresponding Application Code" for a Combined Work means the
42
+ object code and/or source code for the Application, including any data
43
+ and utility programs needed for reproducing the Combined Work from the
44
+ Application, but excluding the System Libraries of the Combined Work.
45
+
46
+ 1. Exception to Section 3 of the GNU GPL.
47
+
48
+ You may convey a covered work under sections 3 and 4 of this License
49
+ without being bound by section 3 of the GNU GPL.
50
+
51
+ 2. Conveying Modified Versions.
52
+
53
+ If you modify a copy of the Library, and, in your modifications, a
54
+ facility refers to a function or data to be supplied by an Application
55
+ that uses the facility (other than as an argument passed when the
56
+ facility is invoked), then you may convey a copy of the modified
57
+ version:
58
+
59
+ a) under this License, provided that you make a good faith effort to
60
+ ensure that, in the event an Application does not supply the
61
+ function or data, the facility still operates, and performs
62
+ whatever part of its purpose remains meaningful, or
63
+
64
+ b) under the GNU GPL, with none of the additional permissions of
65
+ this License applicable to that copy.
66
+
67
+ 3. Object Code Incorporating Material from Library Header Files.
68
+
69
+ The object code form of an Application may incorporate material from
70
+ a header file that is part of the Library. You may convey such object
71
+ code under terms of your choice, provided that, if the incorporated
72
+ material is not limited to numerical parameters, data structure
73
+ layouts and accessors, or small macros, inline functions and templates
74
+ (ten or fewer lines in length), you do both of the following:
75
+
76
+ a) Give prominent notice with each copy of the object code that the
77
+ Library is used in it and that the Library and its use are
78
+ covered by this License.
79
+
80
+ b) Accompany the object code with a copy of the GNU GPL and this license
81
+ document.
82
+
83
+ 4. Combined Works.
84
+
85
+ You may convey a Combined Work under terms of your choice that,
86
+ taken together, effectively do not restrict modification of the
87
+ portions of the Library contained in the Combined Work and reverse
88
+ engineering for debugging such modifications, if you also do each of
89
+ the following:
90
+
91
+ a) Give prominent notice with each copy of the Combined Work that
92
+ the Library is used in it and that the Library and its use are
93
+ covered by this License.
94
+
95
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
96
+ document.
97
+
98
+ c) For a Combined Work that displays copyright notices during
99
+ execution, include the copyright notice for the Library among
100
+ these notices, as well as a reference directing the user to the
101
+ copies of the GNU GPL and this license document.
102
+
103
+ d) Do one of the following:
104
+
105
+ 0) Convey the Minimal Corresponding Source under the terms of this
106
+ License, and the Corresponding Application Code in a form
107
+ suitable for, and under terms that permit, the user to
108
+ recombine or relink the Application with a modified version of
109
+ the Linked Version to produce a modified Combined Work, in the
110
+ manner specified by section 6 of the GNU GPL for conveying
111
+ Corresponding Source.
112
+
113
+ 1) Use a suitable shared library mechanism for linking with the
114
+ Library. A suitable mechanism is one that (a) uses at run time
115
+ a copy of the Library already present on the user's computer
116
+ system, and (b) will operate properly with a modified version
117
+ of the Library that is interface-compatible with the Linked
118
+ Version.
119
+
120
+ e) Provide Installation Information, but only if you would otherwise
121
+ be required to provide such information under section 6 of the
122
+ GNU GPL, and only to the extent that such information is
123
+ necessary to install and execute a modified version of the
124
+ Combined Work produced by recombining or relinking the
125
+ Application with a modified version of the Linked Version. (If
126
+ you use option 4d0, the Installation Information must accompany
127
+ the Minimal Corresponding Source and Corresponding Application
128
+ Code. If you use option 4d1, you must provide the Installation
129
+ Information in the manner specified by section 6 of the GNU GPL
130
+ for conveying Corresponding Source.)
131
+
132
+ 5. Combined Libraries.
133
+
134
+ You may place library facilities that are a work based on the
135
+ Library side by side in a single library together with other library
136
+ facilities that are not Applications and are not covered by this
137
+ License, and convey such a combined library under terms of your
138
+ choice, if you do both of the following:
139
+
140
+ a) Accompany the combined library with a copy of the same work based
141
+ on the Library, uncombined with any other library facilities,
142
+ conveyed under the terms of this License.
143
+
144
+ b) Give prominent notice with the combined library that part of it
145
+ is a work based on the Library, and explaining where to find the
146
+ accompanying uncombined form of the same work.
147
+
148
+ 6. Revised Versions of the GNU Lesser General Public License.
149
+
150
+ The Free Software Foundation may publish revised and/or new versions
151
+ of the GNU Lesser General Public License from time to time. Such new
152
+ versions will be similar in spirit to the present version, but may
153
+ differ in detail to address new problems or concerns.
154
+
155
+ Each version is given a distinguishing version number. If the
156
+ Library as you received it specifies that a certain numbered version
157
+ of the GNU Lesser General Public License "or any later version"
158
+ applies to it, you have the option of following the terms and
159
+ conditions either of that published version or of any later version
160
+ published by the Free Software Foundation. If the Library as you
161
+ received it does not specify a version number of the GNU Lesser
162
+ General Public License, you may choose any version of the GNU Lesser
163
+ General Public License ever published by the Free Software Foundation.
164
+
165
+ If the Library as you received it specifies that a proxy can decide
166
+ whether future versions of the GNU Lesser General Public License shall
167
+ apply, that proxy's public statement of acceptance of any version is
168
+ permanent authorization for you to choose that version for the
169
+ Library.
@@ -0,0 +1,24 @@
1
+ module NoBrainer::Criteria::AfterFind
2
+ extend ActiveSupport::Concern
3
+
4
+ included { attr_accessor :_after_find }
5
+
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
16
+ end
17
+
18
+ def instantiate_doc(attrs)
19
+ super.tap do |doc|
20
+ self._after_find.to_a.each { |block| block.call(doc) }
21
+ doc.run_callbacks(:find) if doc.is_a?(NoBrainer::Document)
22
+ end
23
+ end
24
+ end
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Termination::Cache
1
+ module NoBrainer::Criteria::Cache
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included { attr_accessor :_with_cache }
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Chainable::Core
1
+ module NoBrainer::Criteria::Core
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included { attr_accessor :init_options }
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Termination::Count
1
+ module NoBrainer::Criteria::Count
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def count
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Termination::Delete
1
+ module NoBrainer::Criteria::Delete
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def delete_all
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Termination::Enumerable
1
+ module NoBrainer::Criteria::Enumerable
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def each(options={}, &block)
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Termination::First
1
+ module NoBrainer::Criteria::First
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def first
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Termination::Inc
1
+ module NoBrainer::Criteria::Inc
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def inc_all(field, value=1)
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Chainable::Limit
1
+ module NoBrainer::Criteria::Limit
2
2
  # TODO Test these guys
3
3
  extend ActiveSupport::Concern
4
4
 
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Chainable::OrderBy
1
+ module NoBrainer::Criteria::OrderBy
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included { attr_accessor :order, :_reverse_order }
@@ -0,0 +1,55 @@
1
+ module NoBrainer::Criteria::Preload
2
+ extend ActiveSupport::Concern
3
+
4
+ included { attr_accessor :_preloads }
5
+
6
+ def initialize(options={})
7
+ super
8
+ self._preloads = []
9
+ end
10
+
11
+ def preload(*values)
12
+ chain(:keep_cache => true) { |criteria| criteria._preloads = values }
13
+ end
14
+
15
+ def includes(*values)
16
+ NoBrainer.logger.warn "[NoBrainer] includes() is deprecated and will be removed, use preload() instead."
17
+ preload(*values)
18
+ end
19
+
20
+ def merge!(criteria, options={})
21
+ super
22
+ self._preloads = self._preloads + criteria._preloads
23
+
24
+ # XXX Not pretty hack
25
+ if criteria._preloads.present? && cached?
26
+ perform_preloads(@cache)
27
+ end
28
+ end
29
+
30
+ def each(options={}, &block)
31
+ return super unless should_preloads? && !options[:no_preloading] && block
32
+
33
+ docs = []
34
+ super(options.merge(:no_preloading => true)) { |doc| docs << doc }
35
+ perform_preloads(docs)
36
+ docs.each(&block)
37
+ self
38
+ end
39
+
40
+ private
41
+
42
+ def should_preloads?
43
+ self._preloads.present? && !raw?
44
+ end
45
+
46
+ def get_one(criteria)
47
+ super.tap { |doc| perform_preloads([doc]) }
48
+ end
49
+
50
+ def perform_preloads(docs)
51
+ if should_preloads? && docs.present?
52
+ NoBrainer::Document::Association::EagerLoader.new.eager_load(docs, self._preloads)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,25 @@
1
+ module NoBrainer::Criteria::Raw
2
+ extend ActiveSupport::Concern
3
+
4
+ included { attr_accessor :_raw }
5
+
6
+ def raw
7
+ chain { |criteria| criteria._raw = true }
8
+ end
9
+
10
+ def merge!(criteria, options={})
11
+ super
12
+ self._raw = criteria._raw unless criteria._raw.nil?
13
+ self
14
+ end
15
+
16
+ private
17
+
18
+ def raw?
19
+ !!_raw
20
+ end
21
+
22
+ def instantiate_doc(attrs)
23
+ raw? ? attrs : klass.new_from_db(attrs)
24
+ end
25
+ end
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Chainable::Scope
1
+ module NoBrainer::Criteria::Scope
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included { attr_accessor :use_default_scope }
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Termination::Update
1
+ module NoBrainer::Criteria::Update
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def update_all(attrs={}, &block)
@@ -1,4 +1,4 @@
1
- module NoBrainer::Criteria::Chainable::Where
1
+ module NoBrainer::Criteria::Where
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included { attr_accessor :where_ast, :with_index_name }
@@ -54,19 +54,32 @@ module NoBrainer::Criteria::Chainable::Where
54
54
  end
55
55
  end
56
56
 
57
- class BinaryOperator < Struct.new(:key, :op, :value)
57
+ class BinaryOperator < Struct.new(:key, :op, :value, :criteria)
58
58
  def simplify
59
- # TODO Simplify the in uniq
60
- self
59
+ case op
60
+ when :in then
61
+ case value
62
+ when Range then BinaryOperator.new(key, :between, (cast(value.min)..cast(value.max)), criteria)
63
+ when Array then BinaryOperator.new(key, :in, value.map(&method(:cast)).uniq, criteria)
64
+ else raise ArgumentError.new ":in takes an array/range, not #{value}"
65
+ end
66
+ else BinaryOperator.new(key, op, cast(value), criteria)
67
+ end
61
68
  end
62
69
 
63
70
  def to_rql(doc)
64
71
  case op
65
72
  when :between then (doc[key] >= value.min) & (doc[key] <= value.max)
66
- when :in then value.map { |v| doc[key].eq(v) }.reduce { |a,b| a | b }
73
+ when :in then RethinkDB::RQL.new.expr(value).contains(doc[key])
67
74
  else doc[key].__send__(op, value)
68
75
  end
69
76
  end
77
+
78
+ private
79
+
80
+ def cast(value)
81
+ criteria.klass.cast_value_for(key, value)
82
+ end
70
83
  end
71
84
 
72
85
  class UnaryOperator < Struct.new(:op, :value)
@@ -110,27 +123,30 @@ module NoBrainer::Criteria::Chainable::Where
110
123
  when :and then MultiOperator.new(:and, value.map { |v| parse_clause(v) })
111
124
  when :or then MultiOperator.new(:or, value.map { |v| parse_clause(v) })
112
125
  when :not then UnaryOperator.new(:not, parse_clause(value))
113
- when String, Symbol then parse_clause_stub(key.to_sym.eq, value)
126
+ when String, Symbol then parse_clause_stub_eq(key, value)
114
127
  when NoBrainer::DecoratedSymbol then
115
128
  case key.modifier
116
129
  when :ne then parse_clause(:not => { key.symbol => value })
117
- when :eq then
118
- case value
119
- when Range then BinaryOperator.new(key.symbol, :between, value)
120
- when Regexp then BinaryOperator.new(key.symbol, :match, value.inspect[1..-2])
121
- else BinaryOperator.new(key.symbol, key.modifier, value)
122
- end
123
- else BinaryOperator.new(key.symbol, key.modifier, value)
130
+ when :eq then parse_clause_stub_eq(key.symbol, value)
131
+ else BinaryOperator.new(key.symbol, key.modifier, value, self)
124
132
  end
125
133
  else raise "Invalid key: #{key}"
126
134
  end
127
135
  end
128
136
 
137
+ def parse_clause_stub_eq(key, value)
138
+ case value
139
+ when Range then BinaryOperator.new(key, :between, value, self)
140
+ when Regexp then BinaryOperator.new(key, :match, value.inspect[1..-2], self)
141
+ else BinaryOperator.new(key, :eq, value, self)
142
+ end
143
+ end
144
+
129
145
  def without_index?
130
146
  self.with_index_name == false
131
147
  end
132
148
 
133
- class IndexFinder < Struct.new(:criteria, :index_name, :indexed_values, :ast)
149
+ class IndexFinder < Struct.new(:criteria, :index_name, :rql_proc, :ast)
134
150
  def initialize(*args)
135
151
  super
136
152
  find_index
@@ -143,9 +159,7 @@ module NoBrainer::Criteria::Chainable::Where
143
159
  private
144
160
 
145
161
  def get_candidate_clauses(*types)
146
- Hash[criteria.where_ast.clauses
147
- .select { |c| c.is_a?(BinaryOperator) && types.include?(c.op) }
148
- .map { |c| [c.key, c] }]
162
+ criteria.where_ast.clauses.select { |c| c.is_a?(BinaryOperator) && types.include?(c.op) }
149
163
  end
150
164
 
151
165
  def get_usable_indexes(*types)
@@ -156,19 +170,24 @@ module NoBrainer::Criteria::Chainable::Where
156
170
  end
157
171
 
158
172
  def find_index_canonical
159
- clauses = get_candidate_clauses(:eq, :in)
173
+ clauses = Hash[get_candidate_clauses(:eq, :in, :between).map { |c| [c.key, c] }]
160
174
  return unless clauses.present?
161
175
 
162
176
  if index_name = (get_usable_indexes.keys & clauses.keys).first
163
177
  clause = clauses[index_name]
164
178
  self.index_name = index_name
165
- self.indexed_values = clause.op == :in ? clause.value : [clause.value]
166
179
  self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - [clause])
180
+ self.rql_proc = case clause.op
181
+ when :eq then ->(rql){ rql.get_all(clause.value, :index => index_name) }
182
+ when :in then ->(rql){ rql.get_all(*clause.value, :index => index_name) }
183
+ when :between then ->(rql){ rql.between(clause.value.min, clause.value.max, :index => index_name,
184
+ :left_bound => :closed, :right_bound => :closed) }
185
+ end
167
186
  end
168
187
  end
169
188
 
170
189
  def find_index_compound
171
- clauses = get_candidate_clauses(:eq)
190
+ clauses = Hash[get_candidate_clauses(:eq).map { |c| [c.key, c] }]
172
191
  return unless clauses.present?
173
192
 
174
193
  index_name, index_values = get_usable_indexes(:compound)
@@ -179,14 +198,33 @@ module NoBrainer::Criteria::Chainable::Where
179
198
  if index_name
180
199
  indexed_clauses = index_values.map { |field| clauses[field] }
181
200
  self.index_name = index_name
182
- self.indexed_values = [indexed_clauses.map { |c| c.value }]
183
201
  self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - indexed_clauses)
202
+ self.rql_proc = ->(rql){ rql.get_all(indexed_clauses.map { |c| c.value }, :index => index_name) }
203
+ end
204
+ end
205
+
206
+ def find_index_hidden_between
207
+ clauses = get_candidate_clauses(:gt, :ge, :lt, :le).group_by(&:key)
208
+ return unless clauses.present?
209
+
210
+ if index_name = (get_usable_indexes.keys & clauses.keys).first
211
+ op_clauses = Hash[clauses[index_name].map { |c| [c.op, c] }]
212
+ left_bound = op_clauses[:gt] || op_clauses[:ge]
213
+ right_bound = op_clauses[:lt] || op_clauses[:le]
214
+
215
+ self.index_name = index_name
216
+ self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - [left_bound, right_bound].compact)
217
+
218
+ options = {:index => index_name}
219
+ options[:left_bound] = {:gt => :open, :ge => :closed}[left_bound.op] if left_bound
220
+ options[:right_bound] = {:lt => :open, :le => :closed}[right_bound.op] if right_bound
221
+ self.rql_proc = ->(rql){ rql.between(left_bound.try(:value), right_bound.try(:value), options) }
184
222
  end
185
223
  end
186
224
 
187
225
  def find_index
188
226
  return if criteria.__send__(:without_index?)
189
- find_index_canonical || find_index_compound
227
+ find_index_canonical || find_index_compound || find_index_hidden_between
190
228
  if criteria.with_index_name && !could_find_index?
191
229
  raise NoBrainer::Error::CannotUseIndex.new("Cannot use index #{criteria.with_index_name}")
192
230
  end
@@ -200,9 +238,7 @@ module NoBrainer::Criteria::Chainable::Where
200
238
 
201
239
  def compile_rql_pass1
202
240
  rql = super
203
- if index_finder.could_find_index?
204
- rql = rql.get_all(*index_finder.indexed_values, :index => index_finder.index_name)
205
- end
241
+ rql = index_finder.rql_proc.call(rql) if index_finder.could_find_index?
206
242
  rql
207
243
  end
208
244
 
@@ -1,20 +1,8 @@
1
1
  require 'rethinkdb'
2
2
 
3
3
  class NoBrainer::Criteria
4
- # The disctinction between Chainable and Termination is purely cosmetic.
5
- module Chainable
6
- extend NoBrainer::Autoload
7
- extend ActiveSupport::Concern
8
- autoload_and_include :Core, :Scope, :Raw, :Where, :OrderBy, :Limit
9
- end
10
-
11
- module Termination
12
- extend NoBrainer::Autoload
13
- extend ActiveSupport::Concern
14
- autoload_and_include :Count, :Delete, :Enumerable, :First, :EagerLoading,
15
- :Inc, :Update, :Cache
16
- end
17
-
18
- include Chainable
19
- include Termination
4
+ extend NoBrainer::Autoload
5
+ autoload_and_include :Core, :Scope, :Raw, :AfterFind, :Where, :OrderBy, :Limit,
6
+ :Count, :Delete, :Enumerable, :First, :Preload, :Inc,
7
+ :Update, :Cache
20
8
  end
@@ -2,7 +2,7 @@ class NoBrainer::Document::Association::BelongsTo
2
2
  include NoBrainer::Document::Association::Core
3
3
 
4
4
  class Metadata
5
- VALID_OPTIONS = [:foreign_key, :class_name, :index]
5
+ VALID_OPTIONS = [:foreign_key, :class_name, :index, :validates]
6
6
  include NoBrainer::Document::Association::Core::Metadata
7
7
  extend NoBrainer::Document::Association::EagerLoader::Generic
8
8
 
@@ -19,7 +19,14 @@ class NoBrainer::Document::Association::BelongsTo
19
19
  def hook
20
20
  super
21
21
 
22
- owner_klass.field foreign_key, :index => options[:index]
22
+ # TODO It would be good to set the type we want to work with, but because
23
+ # the target class is eager loaded, we are not doing it.
24
+ # This would have the effect of loading all the models because they
25
+ # are likely to be related to each other. So we don't know the type
26
+ # of the primary key of the target.
27
+ owner_klass.field(foreign_key, :index => options[:index])
28
+ owner_klass.validates(target_name, options[:validates]) if options[:validates]
29
+
23
30
  delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
24
31
  add_callback_for(:after_validation)
25
32
  # TODO test if we are not overstepping on another foreign_key
@@ -57,8 +57,8 @@ module NoBrainer::Document::Association::Core
57
57
 
58
58
  def assert_target_type(value)
59
59
  unless value.is_a?(target_klass) || value.nil?
60
- msg = "Trying to use a #{value.class} as a #{target_name}"
61
- raise NoBrainer::Error::InvalidType.new(msg)
60
+ options = { :attr_name => target_name, :value => value, :type => target_klass }
61
+ raise NoBrainer::Error::InvalidType.new(options)
62
62
  end
63
63
  end
64
64
  end
@@ -40,19 +40,19 @@ class NoBrainer::Document::Association::EagerLoader
40
40
  association.eager_load(docs, criteria)
41
41
  end
42
42
 
43
- def eager_load(docs, includes)
44
- case includes
45
- when Hash then includes.each do |k,v|
43
+ def eager_load(docs, preloads)
44
+ case preloads
45
+ when Hash then preloads.each do |k,v|
46
46
  if v.is_a?(NoBrainer::Criteria)
47
47
  v = v.dup
48
- nested_includes, v._includes = v._includes, []
49
- eager_load(eager_load_association(docs, k, v), nested_includes)
48
+ nested_preloads, v._preloads = v._preloads, []
49
+ eager_load(eager_load_association(docs, k, v), nested_preloads)
50
50
  else
51
51
  eager_load(eager_load_association(docs, k), v)
52
52
  end
53
53
  end
54
- when Array then includes.each { |v| eager_load(docs, v) }
55
- else eager_load_association(docs, includes)
54
+ when Array then preloads.each { |v| eager_load(docs, v) }
55
+ else eager_load_association(docs, preloads)
56
56
  end
57
57
  true
58
58
  end
@@ -39,7 +39,7 @@ class NoBrainer::Document::Association::HasMany
39
39
 
40
40
  def target_criteria
41
41
  @target_criteria ||= target_klass.where(foreign_key => owner.id)
42
- ._after_instantiate(set_inverse_proc)
42
+ .after_find(set_inverse_proc)
43
43
  end
44
44
 
45
45
  def read