nobrainer 0.10.0 → 0.11.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/criteria.rb +1 -1
- data/lib/no_brainer/criteria/chainable/where.rb +42 -15
- data/lib/no_brainer/criteria/termination/preload.rb +55 -0
- data/lib/no_brainer/document.rb +2 -2
- data/lib/no_brainer/document/association/belongs_to.rb +4 -2
- data/lib/no_brainer/document/association/eager_loader.rb +7 -7
- data/lib/no_brainer/document/attributes.rb +9 -10
- data/lib/no_brainer/document/callbacks.rb +32 -0
- data/lib/no_brainer/document/core.rb +0 -2
- data/lib/no_brainer/document/criteria.rb +1 -1
- data/lib/no_brainer/document/dirty.rb +51 -36
- data/lib/no_brainer/document/dynamic_attributes.rb +11 -3
- data/lib/no_brainer/document/persistance.rb +24 -37
- data/lib/no_brainer/document/timestamps.rb +5 -2
- data/lib/no_brainer/document/types.rb +26 -18
- data/lib/no_brainer/document/validation.rb +5 -0
- data/lib/nobrainer.rb +1 -1
- metadata +14 -13
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56f5bfdef46fb94e140efe80af84031e02238d07
|
4
|
+
data.tar.gz: aa5965b806a43a8727be98ef2a7a9c370fe94cfc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0285cc6834d21a4e973fe293adf54ada0bcce15424a605719a32db2445a909793ebab766549a9d8b31b42ced44e6d3ef5f1cfecd980b33b4c4c1792af94a74c4
|
7
|
+
data.tar.gz: 3ed81e4508bc4528142b7049db0e1464927f7dfcad9c459853593c70a9ebbed95c47b3bd634d03f0bc2e9ebcc358a2d72cbcd7b7c73b4c46ca37a518a23c7c56
|
data/lib/no_brainer/criteria.rb
CHANGED
@@ -11,7 +11,7 @@ class NoBrainer::Criteria
|
|
11
11
|
module Termination
|
12
12
|
extend NoBrainer::Autoload
|
13
13
|
extend ActiveSupport::Concern
|
14
|
-
autoload_and_include :Count, :Delete, :Enumerable, :First, :
|
14
|
+
autoload_and_include :Count, :Delete, :Enumerable, :First, :Preload,
|
15
15
|
:Inc, :Update, :Cache
|
16
16
|
end
|
17
17
|
|
@@ -56,14 +56,21 @@ module NoBrainer::Criteria::Chainable::Where
|
|
56
56
|
|
57
57
|
class BinaryOperator < Struct.new(:key, :op, :value)
|
58
58
|
def simplify
|
59
|
-
|
60
|
-
|
59
|
+
case op
|
60
|
+
when :in then
|
61
|
+
case value
|
62
|
+
when Range then BinaryOperator.new(key, :between, value)
|
63
|
+
when Array then BinaryOperator.new(key, :in, value.uniq)
|
64
|
+
else raise ArgumentError.new ":in takes an array/range, not #{value}"
|
65
|
+
end
|
66
|
+
else self
|
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.
|
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
|
@@ -130,7 +137,7 @@ module NoBrainer::Criteria::Chainable::Where
|
|
130
137
|
self.with_index_name == false
|
131
138
|
end
|
132
139
|
|
133
|
-
class IndexFinder < Struct.new(:criteria, :index_name, :
|
140
|
+
class IndexFinder < Struct.new(:criteria, :index_name, :rql_proc, :ast)
|
134
141
|
def initialize(*args)
|
135
142
|
super
|
136
143
|
find_index
|
@@ -143,9 +150,7 @@ module NoBrainer::Criteria::Chainable::Where
|
|
143
150
|
private
|
144
151
|
|
145
152
|
def get_candidate_clauses(*types)
|
146
|
-
|
147
|
-
.select { |c| c.is_a?(BinaryOperator) && types.include?(c.op) }
|
148
|
-
.map { |c| [c.key, c] }]
|
153
|
+
criteria.where_ast.clauses.select { |c| c.is_a?(BinaryOperator) && types.include?(c.op) }
|
149
154
|
end
|
150
155
|
|
151
156
|
def get_usable_indexes(*types)
|
@@ -156,19 +161,24 @@ module NoBrainer::Criteria::Chainable::Where
|
|
156
161
|
end
|
157
162
|
|
158
163
|
def find_index_canonical
|
159
|
-
clauses = get_candidate_clauses(:eq, :in)
|
164
|
+
clauses = Hash[get_candidate_clauses(:eq, :in, :between).map { |c| [c.key, c] }]
|
160
165
|
return unless clauses.present?
|
161
166
|
|
162
167
|
if index_name = (get_usable_indexes.keys & clauses.keys).first
|
163
168
|
clause = clauses[index_name]
|
164
169
|
self.index_name = index_name
|
165
|
-
self.indexed_values = clause.op == :in ? clause.value : [clause.value]
|
166
170
|
self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - [clause])
|
171
|
+
self.rql_proc = case clause.op
|
172
|
+
when :eq then ->(rql){ rql.get_all(clause.value, :index => index_name) }
|
173
|
+
when :in then ->(rql){ rql.get_all(*clause.value, :index => index_name) }
|
174
|
+
when :between then ->(rql){ rql.between(clause.value.min, clause.value.max, :index => index_name,
|
175
|
+
:left_bound => :closed, :right_bound => :closed) }
|
176
|
+
end
|
167
177
|
end
|
168
178
|
end
|
169
179
|
|
170
180
|
def find_index_compound
|
171
|
-
clauses = get_candidate_clauses(:eq)
|
181
|
+
clauses = Hash[get_candidate_clauses(:eq).map { |c| [c.key, c] }]
|
172
182
|
return unless clauses.present?
|
173
183
|
|
174
184
|
index_name, index_values = get_usable_indexes(:compound)
|
@@ -179,14 +189,33 @@ module NoBrainer::Criteria::Chainable::Where
|
|
179
189
|
if index_name
|
180
190
|
indexed_clauses = index_values.map { |field| clauses[field] }
|
181
191
|
self.index_name = index_name
|
182
|
-
self.indexed_values = [indexed_clauses.map { |c| c.value }]
|
183
192
|
self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - indexed_clauses)
|
193
|
+
self.rql_proc = ->(rql){ rql.get_all(indexed_clauses.map { |c| c.value }, :index => index_name) }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def find_index_hidden_between
|
198
|
+
clauses = get_candidate_clauses(:gt, :ge, :lt, :le).group_by(&:key)
|
199
|
+
return unless clauses.present?
|
200
|
+
|
201
|
+
if index_name = (get_usable_indexes.keys & clauses.keys).first
|
202
|
+
op_clauses = Hash[clauses[index_name].map { |c| [c.op, c] }]
|
203
|
+
left_bound = op_clauses[:gt] || op_clauses[:ge]
|
204
|
+
right_bound = op_clauses[:lt] || op_clauses[:le]
|
205
|
+
|
206
|
+
self.index_name = index_name
|
207
|
+
self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - [left_bound, right_bound].compact)
|
208
|
+
|
209
|
+
options = {:index => index_name}
|
210
|
+
options[:left_bound] = {:gt => :open, :ge => :closed}[left_bound.op] if left_bound
|
211
|
+
options[:right_bound] = {:lt => :open, :le => :closed}[right_bound.op] if right_bound
|
212
|
+
self.rql_proc = ->(rql){ rql.between(left_bound.try(:value), right_bound.try(:value), options) }
|
184
213
|
end
|
185
214
|
end
|
186
215
|
|
187
216
|
def find_index
|
188
217
|
return if criteria.__send__(:without_index?)
|
189
|
-
find_index_canonical || find_index_compound
|
218
|
+
find_index_canonical || find_index_compound || find_index_hidden_between
|
190
219
|
if criteria.with_index_name && !could_find_index?
|
191
220
|
raise NoBrainer::Error::CannotUseIndex.new("Cannot use index #{criteria.with_index_name}")
|
192
221
|
end
|
@@ -200,9 +229,7 @@ module NoBrainer::Criteria::Chainable::Where
|
|
200
229
|
|
201
230
|
def compile_rql_pass1
|
202
231
|
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
|
232
|
+
rql = index_finder.rql_proc.call(rql) if index_finder.could_find_index?
|
206
233
|
rql
|
207
234
|
end
|
208
235
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module NoBrainer::Criteria::Termination::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_preloadsing] && block
|
32
|
+
|
33
|
+
docs = []
|
34
|
+
super(options.merge(:no_preloadsing => 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
|
data/lib/no_brainer/document.rb
CHANGED
@@ -5,8 +5,8 @@ module NoBrainer::Document
|
|
5
5
|
extend NoBrainer::Autoload
|
6
6
|
|
7
7
|
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Validation, :Types,
|
8
|
-
:Persistance, :Dirty, :Id, :Association, :Serialization,
|
9
|
-
:Polymorphic, :Index, :Timestamps
|
8
|
+
:Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
|
9
|
+
:Criteria, :Polymorphic, :Index, :Timestamps
|
10
10
|
|
11
11
|
autoload :DynamicAttributes
|
12
12
|
|
@@ -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,9 @@ class NoBrainer::Document::Association::BelongsTo
|
|
19
19
|
def hook
|
20
20
|
super
|
21
21
|
|
22
|
-
owner_klass.field
|
22
|
+
owner_klass.field(foreign_key, :index => options[:index])
|
23
|
+
owner_klass.validates(target_name, options[:validates]) if options[:validates]
|
24
|
+
|
23
25
|
delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
|
24
26
|
add_callback_for(:after_validation)
|
25
27
|
# TODO test if we are not overstepping on another foreign_key
|
@@ -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,
|
44
|
-
case
|
45
|
-
when Hash then
|
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
|
-
|
49
|
-
eager_load(eager_load_association(docs, k, v),
|
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
|
55
|
-
else eager_load_association(docs,
|
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
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module NoBrainer::Document::Attributes
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type]
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :validates]
|
3
3
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
@@ -10,14 +10,13 @@ module NoBrainer::Document::Attributes
|
|
10
10
|
self.fields = {}
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
|
15
|
-
@attributes = {}
|
13
|
+
def _initialize(attrs={}, options={})
|
14
|
+
@_attributes = {}.with_indifferent_access
|
16
15
|
assign_attributes(attrs, options.reverse_merge(:pristine => true))
|
17
16
|
end
|
18
17
|
|
19
18
|
def attributes
|
20
|
-
@
|
19
|
+
@_attributes.dup.freeze
|
21
20
|
end
|
22
21
|
|
23
22
|
def read_attribute(name)
|
@@ -32,7 +31,7 @@ module NoBrainer::Document::Attributes
|
|
32
31
|
|
33
32
|
def assign_defaults
|
34
33
|
self.class.fields.each do |name, field_options|
|
35
|
-
if field_options.has_key?(:default) && !@
|
34
|
+
if field_options.has_key?(:default) && !@_attributes.has_key?(name)
|
36
35
|
default_value = field_options[:default]
|
37
36
|
default_value = default_value.call if default_value.is_a?(Proc)
|
38
37
|
self.write_attribute(name, default_value)
|
@@ -45,7 +44,7 @@ module NoBrainer::Document::Attributes
|
|
45
44
|
end
|
46
45
|
|
47
46
|
def assign_attributes(attrs, options={})
|
48
|
-
@
|
47
|
+
@_attributes.clear if options[:pristine]
|
49
48
|
_assign_attributes(attrs, options)
|
50
49
|
assign_defaults if options[:pristine]
|
51
50
|
self
|
@@ -54,7 +53,7 @@ module NoBrainer::Document::Attributes
|
|
54
53
|
|
55
54
|
def inspectable_attributes
|
56
55
|
# TODO test that thing
|
57
|
-
Hash[@
|
56
|
+
Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
|
58
57
|
end
|
59
58
|
|
60
59
|
def inspect
|
@@ -88,11 +87,11 @@ module NoBrainer::Document::Attributes
|
|
88
87
|
# Using a layer so the user can use super when overriding these methods
|
89
88
|
inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
|
90
89
|
def #{name}=(value)
|
91
|
-
@
|
90
|
+
@_attributes['#{name}'] = value
|
92
91
|
end
|
93
92
|
|
94
93
|
def #{name}
|
95
|
-
@
|
94
|
+
@_attributes['#{name}']
|
96
95
|
end
|
97
96
|
RUBY
|
98
97
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module NoBrainer::Document::Callbacks
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
extend ActiveModel::Callbacks
|
6
|
+
define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => 'false'
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
run_callbacks(:initialize) { _initialize(*args); true }
|
11
|
+
end
|
12
|
+
|
13
|
+
def _create(*args)
|
14
|
+
run_callbacks(:create) { super }
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(*args, &block)
|
18
|
+
run_callbacks(:update) { super }
|
19
|
+
end
|
20
|
+
|
21
|
+
def replace(*args, &block)
|
22
|
+
run_callbacks(:update) { super }
|
23
|
+
end
|
24
|
+
|
25
|
+
def save(*args)
|
26
|
+
run_callbacks(:save) { super }
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy(*args)
|
30
|
+
run_callbacks(:destroy) { super }
|
31
|
+
end
|
32
|
+
end
|
@@ -16,7 +16,7 @@ module NoBrainer::Document::Criteria
|
|
16
16
|
:with_cache, :without_cache, # Cache
|
17
17
|
:count, :empty?, :any?, # Count
|
18
18
|
:delete_all, :destroy_all, # Delete
|
19
|
-
:includes,
|
19
|
+
:includes, :preload, # Preload
|
20
20
|
:each, :to_a, # Enumerable
|
21
21
|
:first, :last, :first!, :last!, # First
|
22
22
|
:inc_all, :dec_all, # Inc
|
@@ -5,73 +5,87 @@ module NoBrainer::Document::Dirty
|
|
5
5
|
# ActiveModel::AttributeMethods which gives pretty violent method_missing()
|
6
6
|
# capabilities, such as giving a getter/setter method for any keys within the
|
7
7
|
# attributes keys. We don't want that.
|
8
|
+
# Also it doesn't work properly with array and hashes
|
8
9
|
|
9
|
-
included
|
10
|
-
|
11
|
-
|
10
|
+
included { after_save { clear_dirtiness } }
|
11
|
+
|
12
|
+
def old_attributes_values
|
13
|
+
@old_attributes_values ||= {}.with_indifferent_access
|
12
14
|
end
|
13
15
|
|
14
|
-
def
|
15
|
-
@
|
16
|
+
def clear_dirtiness
|
17
|
+
@old_attributes_values.try(:clear)
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
20
|
+
def attributes
|
21
|
+
# If we leak all the attributes (rare), we perform a copy to ensure that we
|
22
|
+
# don't have issues with modified hashes/array. Devs are crazy.
|
23
|
+
super.tap { |attrs| @old_attributes_values = attrs.deep_dup }
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
+
def _assign_attributes(attrs, options={})
|
27
|
+
super
|
28
|
+
clear_dirtiness if options[:from_db]
|
26
29
|
end
|
27
30
|
|
28
31
|
def changed?
|
29
|
-
|
32
|
+
changes.present?
|
30
33
|
end
|
31
34
|
|
32
35
|
def changed
|
33
|
-
|
36
|
+
changes.keys
|
34
37
|
end
|
35
38
|
|
36
39
|
def changes
|
37
|
-
|
40
|
+
result = {}.with_indifferent_access
|
41
|
+
old_attributes_values.each do |attr, old_value|
|
42
|
+
current_value = read_attribute(attr)
|
43
|
+
result[attr] = [old_value, current_value] if current_value != old_value
|
44
|
+
end
|
45
|
+
result
|
38
46
|
end
|
39
47
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# happened. Why is that?
|
45
|
-
value = read_attribute(attr)
|
46
|
-
value = value.clone if value.duplicable?
|
47
|
-
|
48
|
-
return if value == new_value
|
49
|
-
|
50
|
-
changed_attributes[attr] = value
|
48
|
+
def attribute_may_change(attr, current_value)
|
49
|
+
unless old_attributes_values.has_key?(attr)
|
50
|
+
old_attributes_values[attr] = current_value.deep_dup
|
51
|
+
end
|
51
52
|
end
|
52
53
|
|
53
54
|
module ClassMethods
|
54
55
|
def field(name, options={})
|
55
56
|
super
|
56
57
|
|
57
|
-
inject_in_layer :dirty_tracking
|
58
|
-
|
59
|
-
|
58
|
+
inject_in_layer :dirty_tracking do
|
59
|
+
define_method("#{name}_change") do
|
60
|
+
if old_attributes_values.has_key?(name)
|
61
|
+
result = [old_attributes_values[name], read_attribute(name)]
|
62
|
+
result = nil if result.first == result.last
|
63
|
+
result
|
64
|
+
end
|
60
65
|
end
|
61
66
|
|
62
|
-
|
63
|
-
|
67
|
+
define_method("#{name}_changed?") do
|
68
|
+
!!__send__("#{name}_change")
|
64
69
|
end
|
65
70
|
|
66
|
-
|
67
|
-
|
71
|
+
define_method("#{name}_was") do
|
72
|
+
old_attributes_values.has_key?(name) ?
|
73
|
+
old_attributes_values[name] : read_attribute(name)
|
68
74
|
end
|
69
75
|
|
70
|
-
|
71
|
-
|
72
|
-
|
76
|
+
define_method("#{name}") do
|
77
|
+
super().tap do |value|
|
78
|
+
# This take care of string/arrays/hashes that could change without going
|
79
|
+
# through the setter.
|
80
|
+
attribute_may_change(name, value) if value.respond_to?(:size)
|
81
|
+
end
|
73
82
|
end
|
74
|
-
|
83
|
+
|
84
|
+
define_method("#{name}=") do |value|
|
85
|
+
attribute_may_change(name, read_attribute(name))
|
86
|
+
super(value)
|
87
|
+
end
|
88
|
+
end
|
75
89
|
end
|
76
90
|
|
77
91
|
def remove_field(name)
|
@@ -81,6 +95,7 @@ module NoBrainer::Document::Dirty
|
|
81
95
|
undef #{name}_changed?
|
82
96
|
undef #{name}_change
|
83
97
|
undef #{name}_was
|
98
|
+
undef #{name}
|
84
99
|
undef #{name}=
|
85
100
|
RUBY
|
86
101
|
end
|
@@ -2,11 +2,19 @@ module NoBrainer::Document::DynamicAttributes
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
def read_attribute(name)
|
5
|
-
self.respond_to?("#{name}")
|
5
|
+
if self.respond_to?("#{name}")
|
6
|
+
super
|
7
|
+
else
|
8
|
+
@_attributes[name].tap { |value| attribute_may_change(name, value) if value.respond_to?(:size) }
|
9
|
+
end
|
6
10
|
end
|
7
11
|
|
8
12
|
def write_attribute(name, value)
|
9
|
-
|
10
|
-
|
13
|
+
if self.respond_to?("#{name}=")
|
14
|
+
super
|
15
|
+
else
|
16
|
+
attribute_may_change(name, read_attribute(name))
|
17
|
+
@_attributes[name] = value
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
@@ -6,8 +6,7 @@ module NoBrainer::Document::Persistance
|
|
6
6
|
define_model_callbacks :create, :update, :save, :destroy, :terminator => 'false'
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
def initialize(attrs={}, options={})
|
9
|
+
def _initialize(attrs={}, options={})
|
11
10
|
super
|
12
11
|
@new_record = !options[:from_db]
|
13
12
|
end
|
@@ -25,56 +24,44 @@ module NoBrainer::Document::Persistance
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def reload(options={})
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@attributes = {}
|
32
|
-
self.id = id
|
33
|
-
end
|
34
|
-
assign_attributes(selector.raw.first!, :pristine => true, :from_db => true)
|
27
|
+
attrs = selector.raw.first!
|
28
|
+
instance_variables.each { |ivar| remove_instance_variable(ivar) } unless options[:keep_ivars]
|
29
|
+
initialize(attrs, :pristine => true, :from_db => true)
|
35
30
|
self
|
36
31
|
end
|
37
32
|
|
38
33
|
def _create(options={})
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
true
|
47
|
-
end
|
34
|
+
if options[:validate] && !valid?
|
35
|
+
false
|
36
|
+
else
|
37
|
+
keys = self.class.insert_all(attributes)
|
38
|
+
self.id ||= keys.first
|
39
|
+
@new_record = false
|
40
|
+
true
|
48
41
|
end
|
49
42
|
end
|
50
43
|
|
51
44
|
def update(options={}, &block)
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
true
|
58
|
-
end
|
45
|
+
if options[:validate] && !valid?
|
46
|
+
false
|
47
|
+
else
|
48
|
+
selector.update_all(&block)
|
49
|
+
true
|
59
50
|
end
|
60
51
|
end
|
61
52
|
|
62
53
|
def replace(options={}, &block)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
true
|
69
|
-
end
|
54
|
+
if options[:validate] && !valid?
|
55
|
+
false
|
56
|
+
else
|
57
|
+
selector.replace_all(&block)
|
58
|
+
true
|
70
59
|
end
|
71
60
|
end
|
72
61
|
|
73
62
|
def save(options={})
|
74
63
|
options = options.reverse_merge(:validate => true)
|
75
|
-
|
76
|
-
new_record? ? _create(options) : replace(options) { attributes }
|
77
|
-
end
|
64
|
+
new_record? ? _create(options) : replace(options) { attributes }
|
78
65
|
end
|
79
66
|
|
80
67
|
def save!(*args)
|
@@ -95,12 +82,12 @@ module NoBrainer::Document::Persistance
|
|
95
82
|
selector.delete_all
|
96
83
|
@destroyed = true
|
97
84
|
end
|
98
|
-
|
85
|
+
@_attributes.freeze
|
99
86
|
true
|
100
87
|
end
|
101
88
|
|
102
89
|
def destroy
|
103
|
-
|
90
|
+
delete
|
104
91
|
end
|
105
92
|
|
106
93
|
module ClassMethods
|
@@ -2,15 +2,18 @@ module NoBrainer::Document::Timestamps
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
+
class_attribute :timestamps_disabled
|
6
|
+
|
5
7
|
self.field :created_at, :type => Time
|
6
8
|
self.field :updated_at, :type => Time
|
7
9
|
|
8
|
-
before_create { self.created_at = Time.now
|
9
|
-
before_save { self.updated_at = Time.now
|
10
|
+
before_create { self.created_at = Time.now unless self.timestamps_disabled }
|
11
|
+
before_save { self.updated_at = Time.now unless self.timestamps_disabled }
|
10
12
|
end
|
11
13
|
|
12
14
|
module ClassMethods
|
13
15
|
def disable_timestamps
|
16
|
+
self.timestamps_disabled = true
|
14
17
|
self.remove_field :created_at
|
15
18
|
self.remove_field :updated_at
|
16
19
|
end
|
@@ -1,12 +1,6 @@
|
|
1
1
|
module NoBrainer::Document::Types
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
included do
|
5
|
-
# We namespace our fake Boolean class to avoid polluting the global namespace
|
6
|
-
class_exec { class Boolean; def initialize; raise; end; end }
|
7
|
-
before_validation :add_type_errors
|
8
|
-
end
|
9
|
-
|
10
4
|
module CastingRules
|
11
5
|
extend self
|
12
6
|
|
@@ -63,19 +57,30 @@ module NoBrainer::Document::Types
|
|
63
57
|
else raise InvalidType
|
64
58
|
end
|
65
59
|
end
|
66
|
-
end
|
67
60
|
|
68
|
-
|
69
|
-
|
70
|
-
|
61
|
+
def lookup(type)
|
62
|
+
CastingRules.method(type.to_s)
|
63
|
+
rescue NameError
|
64
|
+
proc { raise InvalidType }
|
65
|
+
end
|
66
|
+
|
67
|
+
def cast(value, type, cast_method)
|
68
|
+
return value if value.nil? || type.nil? || value.is_a?(type)
|
69
|
+
cast_method.call(value)
|
70
|
+
end
|
71
71
|
end
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
included do
|
74
|
+
# We namespace our fake Boolean class to avoid polluting the global namespace
|
75
|
+
class_exec do
|
76
|
+
class Boolean
|
77
|
+
def initialize; raise; end
|
78
|
+
def self.inspect; 'Boolean'; end
|
79
|
+
def self.to_s; inspect; end
|
80
|
+
def self.name; inspect; end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
before_validation :add_type_errors
|
79
84
|
end
|
80
85
|
|
81
86
|
class InvalidType < RuntimeError
|
@@ -102,12 +107,12 @@ module NoBrainer::Document::Types
|
|
102
107
|
return unless options.has_key?(:type)
|
103
108
|
name = name.to_sym
|
104
109
|
type = options[:type]
|
105
|
-
cast_method = NoBrainer::Document::Types.
|
110
|
+
cast_method = NoBrainer::Document::Types::CastingRules.lookup(type)
|
106
111
|
|
107
112
|
inject_in_layer :types do
|
108
113
|
define_method("#{name}=") do |value|
|
109
114
|
begin
|
110
|
-
value = NoBrainer::Document::Types.cast(value, type, cast_method)
|
115
|
+
value = NoBrainer::Document::Types::CastingRules.cast(value, type, cast_method)
|
111
116
|
@pending_type_errors.try(:delete, name)
|
112
117
|
rescue NoBrainer::Document::Types::InvalidType => error
|
113
118
|
error.type ||= type
|
@@ -116,11 +121,14 @@ module NoBrainer::Document::Types
|
|
116
121
|
end
|
117
122
|
super(value)
|
118
123
|
end
|
124
|
+
|
125
|
+
define_method("#{name}?") { !!read_attribute(name) } if type == Boolean
|
119
126
|
end
|
120
127
|
end
|
121
128
|
|
122
129
|
def remove_field(name)
|
123
130
|
super
|
131
|
+
# TODO remove the name? if we added it. Low priority though.
|
124
132
|
inject_in_layer :types, <<-RUBY, __FILE__, __LINE__ + 1
|
125
133
|
undef #{name}=
|
126
134
|
RUBY
|
@@ -8,6 +8,11 @@ module NoBrainer::Document::Validation
|
|
8
8
|
end
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
+
def field(name, options={})
|
12
|
+
super
|
13
|
+
validates(name.to_sym, options[:validates]) if options[:validates]
|
14
|
+
end
|
15
|
+
|
11
16
|
def validates_uniqueness_of(*attr_names)
|
12
17
|
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
13
18
|
end
|
data/lib/nobrainer.rb
CHANGED
@@ -4,7 +4,7 @@ end
|
|
4
4
|
|
5
5
|
require 'active_support'
|
6
6
|
%w(module/delegation module/attribute_accessors class/attribute object/blank object/inclusion
|
7
|
-
object/
|
7
|
+
object/deep_dup object/try hash/keys hash/indifferent_access hash/reverse_merge array/extract_options)
|
8
8
|
.each { |dep| require "active_support/core_ext/#{dep}" }
|
9
9
|
|
10
10
|
module NoBrainer
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nobrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Viennot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rethinkdb
|
@@ -66,36 +66,37 @@ extensions: []
|
|
66
66
|
extra_rdoc_files: []
|
67
67
|
files:
|
68
68
|
- lib/no_brainer/document/id.rb
|
69
|
-
- lib/no_brainer/document/association/belongs_to.rb
|
70
69
|
- lib/no_brainer/document/association/has_many.rb
|
71
70
|
- lib/no_brainer/document/association/has_one.rb
|
72
71
|
- lib/no_brainer/document/association/has_one_through.rb
|
73
72
|
- lib/no_brainer/document/association/has_many_through.rb
|
74
|
-
- lib/no_brainer/document/association/eager_loader.rb
|
75
73
|
- lib/no_brainer/document/association/core.rb
|
76
|
-
- lib/no_brainer/document/
|
77
|
-
- lib/no_brainer/document/
|
74
|
+
- lib/no_brainer/document/association/belongs_to.rb
|
75
|
+
- lib/no_brainer/document/association/eager_loader.rb
|
78
76
|
- lib/no_brainer/document/polymorphic.rb
|
79
77
|
- lib/no_brainer/document/store_in.rb
|
80
|
-
- lib/no_brainer/document/core.rb
|
81
78
|
- lib/no_brainer/document/injection_layer.rb
|
82
79
|
- lib/no_brainer/document/index.rb
|
83
80
|
- lib/no_brainer/document/serialization.rb
|
84
81
|
- lib/no_brainer/document/association.rb
|
85
|
-
- lib/no_brainer/document/validation.rb
|
86
82
|
- lib/no_brainer/document/timestamps.rb
|
83
|
+
- lib/no_brainer/document/core.rb
|
84
|
+
- lib/no_brainer/document/callbacks.rb
|
85
|
+
- lib/no_brainer/document/validation.rb
|
87
86
|
- lib/no_brainer/document/dirty.rb
|
88
|
-
- lib/no_brainer/document/persistance.rb
|
89
87
|
- lib/no_brainer/document/attributes.rb
|
88
|
+
- lib/no_brainer/document/dynamic_attributes.rb
|
89
|
+
- lib/no_brainer/document/persistance.rb
|
90
|
+
- lib/no_brainer/document/criteria.rb
|
90
91
|
- lib/no_brainer/document/types.rb
|
91
92
|
- lib/no_brainer/query_runner/connection.rb
|
92
93
|
- lib/no_brainer/query_runner/database_on_demand.rb
|
93
|
-
- lib/no_brainer/query_runner/logger.rb
|
94
94
|
- lib/no_brainer/query_runner/table_on_demand.rb
|
95
95
|
- lib/no_brainer/query_runner/write_error.rb
|
96
96
|
- lib/no_brainer/query_runner/driver.rb
|
97
97
|
- lib/no_brainer/query_runner/missing_index.rb
|
98
98
|
- lib/no_brainer/query_runner/run_options.rb
|
99
|
+
- lib/no_brainer/query_runner/logger.rb
|
99
100
|
- lib/no_brainer/railtie/database.rake
|
100
101
|
- lib/no_brainer/criteria/chainable/limit.rb
|
101
102
|
- lib/no_brainer/criteria/chainable/order_by.rb
|
@@ -109,15 +110,13 @@ files:
|
|
109
110
|
- lib/no_brainer/criteria/termination/enumerable.rb
|
110
111
|
- lib/no_brainer/criteria/termination/update.rb
|
111
112
|
- lib/no_brainer/criteria/termination/delete.rb
|
112
|
-
- lib/no_brainer/criteria/termination/eager_loading.rb
|
113
113
|
- lib/no_brainer/criteria/termination/cache.rb
|
114
|
+
- lib/no_brainer/criteria/termination/preload.rb
|
114
115
|
- lib/no_brainer/decorated_symbol.rb
|
115
116
|
- lib/no_brainer/index_manager.rb
|
116
117
|
- lib/no_brainer/loader.rb
|
117
118
|
- lib/no_brainer/locale/en.yml
|
118
|
-
- lib/no_brainer/util.rb
|
119
119
|
- lib/no_brainer/fork.rb
|
120
|
-
- lib/no_brainer/criteria.rb
|
121
120
|
- lib/no_brainer/connection.rb
|
122
121
|
- lib/no_brainer/query_runner.rb
|
123
122
|
- lib/no_brainer/error.rb
|
@@ -125,6 +124,8 @@ files:
|
|
125
124
|
- lib/no_brainer/config.rb
|
126
125
|
- lib/no_brainer/autoload.rb
|
127
126
|
- lib/no_brainer/document.rb
|
127
|
+
- lib/no_brainer/util.rb
|
128
|
+
- lib/no_brainer/criteria.rb
|
128
129
|
- lib/rails/generators/nobrainer.rb
|
129
130
|
- lib/rails/generators/nobrainer/model/model_generator.rb
|
130
131
|
- lib/rails/generators/nobrainer/model/templates/model.rb.tt
|
@@ -1,50 +0,0 @@
|
|
1
|
-
module NoBrainer::Criteria::Termination::EagerLoading
|
2
|
-
extend ActiveSupport::Concern
|
3
|
-
|
4
|
-
included { attr_accessor :_includes }
|
5
|
-
|
6
|
-
def initialize(options={})
|
7
|
-
super
|
8
|
-
self._includes = []
|
9
|
-
end
|
10
|
-
|
11
|
-
def includes(*values)
|
12
|
-
chain(:keep_cache => true) { |criteria| criteria._includes = values }
|
13
|
-
end
|
14
|
-
|
15
|
-
def merge!(criteria, options={})
|
16
|
-
super
|
17
|
-
self._includes = self._includes + criteria._includes
|
18
|
-
|
19
|
-
# XXX Not pretty hack
|
20
|
-
if criteria._includes.present? && cached?
|
21
|
-
perform_eager_loading(@cache)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def each(options={}, &block)
|
26
|
-
return super unless should_eager_load? && !options[:no_eager_loading] && block
|
27
|
-
|
28
|
-
docs = []
|
29
|
-
super(options.merge(:no_eager_loading => true)) { |doc| docs << doc }
|
30
|
-
perform_eager_loading(docs)
|
31
|
-
docs.each(&block)
|
32
|
-
self
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def should_eager_load?
|
38
|
-
self._includes.present? && !raw?
|
39
|
-
end
|
40
|
-
|
41
|
-
def get_one(criteria)
|
42
|
-
super.tap { |doc| perform_eager_loading([doc]) }
|
43
|
-
end
|
44
|
-
|
45
|
-
def perform_eager_loading(docs)
|
46
|
-
if should_eager_load? && docs.present?
|
47
|
-
NoBrainer::Document::Association::EagerLoader.new.eager_load(docs, self._includes)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|