nobrainer 0.9.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/autoload.rb +18 -7
- data/lib/no_brainer/config.rb +2 -0
- data/lib/no_brainer/criteria/chainable/where.rb +42 -15
- data/lib/no_brainer/criteria/termination/cache.rb +1 -1
- data/lib/no_brainer/criteria/termination/preload.rb +55 -0
- data/lib/no_brainer/criteria.rb +1 -1
- data/lib/no_brainer/document/association/belongs_to.rb +4 -2
- data/lib/no_brainer/document/association/eager_loader.rb +10 -8
- data/lib/no_brainer/document/association.rb +2 -1
- data/lib/no_brainer/document/attributes.rb +21 -30
- 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/index.rb +0 -1
- data/lib/no_brainer/document/injection_layer.rb +1 -1
- data/lib/no_brainer/document/persistance.rb +24 -37
- data/lib/no_brainer/document/timestamps.rb +7 -4
- data/lib/no_brainer/document/types.rb +137 -0
- data/lib/no_brainer/document/validation.rb +5 -1
- data/lib/no_brainer/document.rb +3 -3
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/railtie.rb +5 -5
- data/lib/nobrainer.rb +12 -8
- data/lib/rails/generators/nobrainer/model/model_generator.rb +17 -0
- data/lib/rails/generators/nobrainer/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/nobrainer.rb +18 -0
- metadata +27 -23
- data/lib/no_brainer/criteria/termination/eager_loading.rb +0 -50
- data/lib/no_brainer/version.rb +0 -3
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/autoload.rb
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
module NoBrainer::Autoload
|
|
2
2
|
include ActiveSupport::Autoload
|
|
3
3
|
|
|
4
|
-
def
|
|
5
|
-
|
|
4
|
+
def self.extended(base)
|
|
5
|
+
ActiveSupport::Autoload.extended(base)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
def autoload(*constants)
|
|
9
|
+
constants.each { |constant| super(constant) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def eager_autoload(*constants)
|
|
13
|
+
super() { autoload(*constants) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def autoload_and_include(*constants)
|
|
17
|
+
constants.each { |constant| autoload constant }
|
|
18
|
+
constants.each { |constant| include const_get constant }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def eager_autoload_and_include(*constants)
|
|
22
|
+
eager_autoload(*constants)
|
|
23
|
+
constants.each { |constant| include const_get constant }
|
|
13
24
|
end
|
|
14
25
|
end
|
data/lib/no_brainer/config.rb
CHANGED
|
@@ -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/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
|
|
|
@@ -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
|
|
@@ -33,24 +33,26 @@ class NoBrainer::Document::Association::EagerLoader
|
|
|
33
33
|
def eager_load_association(docs, association_name, criteria=nil)
|
|
34
34
|
docs = docs.compact
|
|
35
35
|
return [] if docs.empty?
|
|
36
|
-
|
|
36
|
+
meta = docs.first.root_class.association_metadata
|
|
37
|
+
# TODO test the singularize thingy.
|
|
38
|
+
association = meta[association_name.to_sym] || meta[association_name.to_s.singularize.to_sym]
|
|
37
39
|
raise "Unknown association #{association_name}" unless association
|
|
38
40
|
association.eager_load(docs, criteria)
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
def eager_load(docs,
|
|
42
|
-
case
|
|
43
|
-
when Hash then
|
|
43
|
+
def eager_load(docs, preloads)
|
|
44
|
+
case preloads
|
|
45
|
+
when Hash then preloads.each do |k,v|
|
|
44
46
|
if v.is_a?(NoBrainer::Criteria)
|
|
45
47
|
v = v.dup
|
|
46
|
-
|
|
47
|
-
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)
|
|
48
50
|
else
|
|
49
51
|
eager_load(eager_load_association(docs, k), v)
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
|
-
when Array then
|
|
53
|
-
else eager_load_association(docs,
|
|
54
|
+
when Array then preloads.each { |v| eager_load(docs, v) }
|
|
55
|
+
else eager_load_association(docs, preloads)
|
|
54
56
|
end
|
|
55
57
|
true
|
|
56
58
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module NoBrainer::Document::Association
|
|
2
2
|
extend NoBrainer::Autoload
|
|
3
3
|
autoload :Core, :BelongsTo, :HasMany, :HasManyThrough, :HasOne, :HasOneThrough, :EagerLoader
|
|
4
|
+
METHODS = [:belongs_to, :has_many, :has_one]
|
|
4
5
|
|
|
5
6
|
extend ActiveSupport::Concern
|
|
6
7
|
|
|
@@ -19,7 +20,7 @@ module NoBrainer::Document::Association
|
|
|
19
20
|
subclass.association_metadata = self.association_metadata.dup
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
METHODS.each do |association|
|
|
23
24
|
define_method(association) do |target, options={}|
|
|
24
25
|
target = target.to_sym
|
|
25
26
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module NoBrainer::Document::Attributes
|
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default]
|
|
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)
|
|
@@ -40,39 +39,31 @@ module NoBrainer::Document::Attributes
|
|
|
40
39
|
end
|
|
41
40
|
end
|
|
42
41
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@attributes.clear if options[:pristine]
|
|
47
|
-
|
|
48
|
-
if options[:from_db]
|
|
49
|
-
# TODO Should we reject undeclared fields ?
|
|
50
|
-
#
|
|
51
|
-
# TODO Not using the getter/setters, the dirty tracking won't notice it,
|
|
52
|
-
# also we should start thinking about custom serializer/deserializer.
|
|
53
|
-
@attributes.merge! attrs
|
|
54
|
-
else
|
|
55
|
-
attrs.each { |k,v| self.write_attribute(k, v) }
|
|
56
|
-
end
|
|
42
|
+
def _assign_attributes(attrs, options={})
|
|
43
|
+
attrs.each { |k,v| self.write_attribute(k,v) }
|
|
44
|
+
end
|
|
57
45
|
|
|
58
|
-
|
|
46
|
+
def assign_attributes(attrs, options={})
|
|
47
|
+
@_attributes.clear if options[:pristine]
|
|
48
|
+
_assign_attributes(attrs, options)
|
|
49
|
+
assign_defaults if options[:pristine]
|
|
59
50
|
self
|
|
60
51
|
end
|
|
61
52
|
def attributes=(*args); assign_attributes(*args); end
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"#<#{self.class} #{attrs.join(', ')}>"
|
|
54
|
+
def inspectable_attributes
|
|
55
|
+
# TODO test that thing
|
|
56
|
+
Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
|
|
67
57
|
end
|
|
68
58
|
|
|
69
|
-
def
|
|
70
|
-
inspect
|
|
59
|
+
def inspect
|
|
60
|
+
"#<#{self.class} #{inspectable_attributes.map { |k,v| "#{k}: #{v.inspect}" }.join(', ')}>"
|
|
71
61
|
end
|
|
72
62
|
|
|
73
63
|
module ClassMethods
|
|
74
64
|
def new_from_db(attrs, options={})
|
|
75
|
-
|
|
65
|
+
options = options.reverse_merge(:pristine => true, :from_db => true)
|
|
66
|
+
klass_from_attrs(attrs).new(attrs, options) if attrs
|
|
76
67
|
end
|
|
77
68
|
|
|
78
69
|
def inherited(subclass)
|
|
@@ -96,11 +87,11 @@ module NoBrainer::Document::Attributes
|
|
|
96
87
|
# Using a layer so the user can use super when overriding these methods
|
|
97
88
|
inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
|
|
98
89
|
def #{name}=(value)
|
|
99
|
-
@
|
|
90
|
+
@_attributes['#{name}'] = value
|
|
100
91
|
end
|
|
101
92
|
|
|
102
93
|
def #{name}
|
|
103
|
-
@
|
|
94
|
+
@_attributes['#{name}']
|
|
104
95
|
end
|
|
105
96
|
RUBY
|
|
106
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
|
|
@@ -23,7 +23,6 @@ module NoBrainer::Document::Index
|
|
|
23
23
|
else raise "Index argument must be a lambda or a list of fields"
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# FIXME Primary key may not always be :id
|
|
27
26
|
if name.in?(NoBrainer::Document::Attributes::RESERVED_FIELD_NAMES)
|
|
28
27
|
raise "Cannot use a reserved field name: #{name}"
|
|
29
28
|
end
|
|
@@ -3,7 +3,7 @@ module NoBrainer::Document::InjectionLayer
|
|
|
3
3
|
|
|
4
4
|
module ClassMethods
|
|
5
5
|
def inject_in_layer(name, code=nil, file=nil, line=nil, &block)
|
|
6
|
-
mod = class_eval "module
|
|
6
|
+
mod = class_eval "module NoBrainerLayer; module #{name.to_s.camelize}; self; end; end"
|
|
7
7
|
mod.module_eval(code, file, line) if code
|
|
8
8
|
mod.module_exec(&block) if block
|
|
9
9
|
include mod
|
|
@@ -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
|
-
|
|
6
|
-
self.field :updated_at
|
|
5
|
+
class_attribute :timestamps_disabled
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
self.field :created_at, :type => Time
|
|
8
|
+
self.field :updated_at, :type => Time
|
|
9
|
+
|
|
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
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
module NoBrainer::Document::Types
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
module CastingRules
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
def String(value)
|
|
8
|
+
case value
|
|
9
|
+
when Symbol then value.to_s
|
|
10
|
+
else raise InvalidType
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def Integer(value)
|
|
15
|
+
case value
|
|
16
|
+
when String
|
|
17
|
+
value = value.strip.gsub(/^\+/, '')
|
|
18
|
+
value.to_i.tap { |new_value| new_value.to_s == value or raise InvalidType }
|
|
19
|
+
when Float
|
|
20
|
+
value.to_i.tap { |new_value| new_value.to_f == value or raise InvalidType }
|
|
21
|
+
else raise InvalidType
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def Float(value)
|
|
26
|
+
case value
|
|
27
|
+
when Integer then value.to_f
|
|
28
|
+
when String
|
|
29
|
+
value = value.strip.gsub(/^\+/, '')
|
|
30
|
+
value = value.gsub(/0+$/, '') if value['.']
|
|
31
|
+
value = value.gsub(/\.$/, '')
|
|
32
|
+
value = "#{value}.0" unless value['.']
|
|
33
|
+
value.to_f.tap { |new_value| new_value.to_s == value or raise InvalidType }
|
|
34
|
+
else raise InvalidType
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def Boolean(value)
|
|
39
|
+
case value
|
|
40
|
+
when TrueClass then true
|
|
41
|
+
when FalseClass then false
|
|
42
|
+
when String, Integer
|
|
43
|
+
value = value.to_s.strip.downcase
|
|
44
|
+
return true if value.in? %w(true yes t 1)
|
|
45
|
+
return false if value.in? %w(false no f 0)
|
|
46
|
+
raise InvalidType
|
|
47
|
+
else raise InvalidType
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def Symbol(value)
|
|
52
|
+
case value
|
|
53
|
+
when String
|
|
54
|
+
value = value.strip
|
|
55
|
+
raise InvalidType if value.empty?
|
|
56
|
+
value.to_sym
|
|
57
|
+
else raise InvalidType
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
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
|
+
end
|
|
72
|
+
|
|
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
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class InvalidType < RuntimeError
|
|
87
|
+
attr_accessor :type
|
|
88
|
+
def initialize(type=nil)
|
|
89
|
+
@type = type
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validation_error_args
|
|
93
|
+
[:invalid_type, :type => type.to_s.underscore.humanize.downcase]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def add_type_errors
|
|
98
|
+
return unless @pending_type_errors
|
|
99
|
+
@pending_type_errors.each do |name, error|
|
|
100
|
+
errors.add(name, *error.validation_error_args)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module ClassMethods
|
|
105
|
+
def field(name, options={})
|
|
106
|
+
super
|
|
107
|
+
return unless options.has_key?(:type)
|
|
108
|
+
name = name.to_sym
|
|
109
|
+
type = options[:type]
|
|
110
|
+
cast_method = NoBrainer::Document::Types::CastingRules.lookup(type)
|
|
111
|
+
|
|
112
|
+
inject_in_layer :types do
|
|
113
|
+
define_method("#{name}=") do |value|
|
|
114
|
+
begin
|
|
115
|
+
value = NoBrainer::Document::Types::CastingRules.cast(value, type, cast_method)
|
|
116
|
+
@pending_type_errors.try(:delete, name)
|
|
117
|
+
rescue NoBrainer::Document::Types::InvalidType => error
|
|
118
|
+
error.type ||= type
|
|
119
|
+
@pending_type_errors ||= {}
|
|
120
|
+
@pending_type_errors[name] = error
|
|
121
|
+
end
|
|
122
|
+
super(value)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
define_method("#{name}?") { !!read_attribute(name) } if type == Boolean
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def remove_field(name)
|
|
130
|
+
super
|
|
131
|
+
# TODO remove the name? if we added it. Low priority though.
|
|
132
|
+
inject_in_layer :types, <<-RUBY, __FILE__, __LINE__ + 1
|
|
133
|
+
undef #{name}=
|
|
134
|
+
RUBY
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -3,12 +3,16 @@ module NoBrainer::Document::Validation
|
|
|
3
3
|
include ActiveModel::Validations
|
|
4
4
|
include ActiveModel::Validations::Callbacks
|
|
5
5
|
|
|
6
|
-
# TODO Test that thing
|
|
7
6
|
def valid?(context=nil)
|
|
8
7
|
super(context || (new_record? ? :create : :update))
|
|
9
8
|
end
|
|
10
9
|
|
|
11
10
|
module ClassMethods
|
|
11
|
+
def field(name, options={})
|
|
12
|
+
super
|
|
13
|
+
validates(name.to_sym, options[:validates]) if options[:validates]
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
def validates_uniqueness_of(*attr_names)
|
|
13
17
|
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
|
14
18
|
end
|
data/lib/no_brainer/document.rb
CHANGED
|
@@ -4,9 +4,9 @@ module NoBrainer::Document
|
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
extend NoBrainer::Autoload
|
|
6
6
|
|
|
7
|
-
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :
|
|
8
|
-
:
|
|
9
|
-
:Polymorphic, :Index, :Timestamps
|
|
7
|
+
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Validation, :Types,
|
|
8
|
+
:Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
|
|
9
|
+
:Criteria, :Polymorphic, :Index, :Timestamps
|
|
10
10
|
|
|
11
11
|
autoload :DynamicAttributes
|
|
12
12
|
|
data/lib/no_brainer/railtie.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
require
|
|
2
|
-
require "rails"
|
|
1
|
+
require 'nobrainer'
|
|
3
2
|
|
|
4
3
|
class NoBrainer::Railtie < Rails::Railtie
|
|
4
|
+
config.app_generators.orm :nobrainer
|
|
5
|
+
config.eager_load_namespaces << NoBrainer
|
|
6
|
+
|
|
5
7
|
config.action_dispatch.rescue_responses.merge!(
|
|
6
8
|
"NoBrainer::Errors::DocumentNotFound" => :not_found,
|
|
7
9
|
"NoBrainer::Errors::DocumentInvalid" => :unprocessable_entity,
|
|
@@ -16,7 +18,7 @@ class NoBrainer::Railtie < Rails::Railtie
|
|
|
16
18
|
|
|
17
19
|
if defined?(ActiveRecord) && NoBrainer::Config.warn_on_active_record
|
|
18
20
|
STDERR.puts "[NoBrainer] WARNING: ActiveRecord is loaded which is probably not what you want."
|
|
19
|
-
STDERR.puts "[NoBrainer] Follow the instructions on http://
|
|
21
|
+
STDERR.puts "[NoBrainer] Follow the instructions on http://nobrainer.io/docs/configuration/#removing_activerecord"
|
|
20
22
|
STDERR.puts "[NoBrainer] Configure NoBrainer with 'config.warn_on_active_record = false' to disable with warning."
|
|
21
23
|
end
|
|
22
24
|
|
|
@@ -30,6 +32,4 @@ class NoBrainer::Railtie < Rails::Railtie
|
|
|
30
32
|
NoBrainer::Loader.cleanup
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
|
-
|
|
34
|
-
#config.eager_load_namespaces << NoBrainer
|
|
35
35
|
end
|
data/lib/nobrainer.rb
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
require 'active_support/core_ext'
|
|
2
|
-
|
|
3
1
|
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('1.9')
|
|
4
2
|
raise 'Please use Ruby 1.9 or later'
|
|
5
3
|
end
|
|
6
4
|
|
|
5
|
+
require 'active_support'
|
|
6
|
+
%w(module/delegation module/attribute_accessors class/attribute object/blank object/inclusion
|
|
7
|
+
object/deep_dup object/try hash/keys hash/indifferent_access hash/reverse_merge array/extract_options)
|
|
8
|
+
.each { |dep| require "active_support/core_ext/#{dep}" }
|
|
9
|
+
|
|
7
10
|
module NoBrainer
|
|
8
11
|
require 'no_brainer/autoload'
|
|
9
12
|
extend NoBrainer::Autoload
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
# We eager load things that could be loaded for the first time during the web request
|
|
15
|
+
autoload :Document, :IndexManager, :Loader, :Fork, :DecoratedSymbol
|
|
16
|
+
eager_autoload :Config, :Connection, :Error, :QueryRunner, :Criteria, :Util
|
|
13
17
|
|
|
14
18
|
class << self
|
|
15
19
|
# Note: we always access the connection explicitly, so that in the future,
|
|
@@ -34,14 +38,14 @@ module NoBrainer
|
|
|
34
38
|
end
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
# Not using modules to extend, it's nicer to see the NoBrainer module API here.
|
|
38
41
|
delegate :db_create, :db_drop, :db_list,
|
|
39
42
|
:table_create, :table_drop, :table_list,
|
|
40
43
|
:drop!, :purge!, :to => :connection
|
|
41
|
-
|
|
42
|
-
delegate :
|
|
44
|
+
|
|
45
|
+
delegate :configure, :logger, :to => 'NoBrainer::Config'
|
|
46
|
+
delegate :run, :to => 'NoBrainer::QueryRunner'
|
|
47
|
+
delegate :update_indexes, :to => 'NoBrainer::IndexManager'
|
|
43
48
|
delegate :with, :with_database, :to => 'NoBrainer::QueryRunner::RunOptions'
|
|
44
|
-
delegate :configure, :logger, :to => 'NoBrainer::Config'
|
|
45
49
|
|
|
46
50
|
def jruby?
|
|
47
51
|
RUBY_PLATFORM == 'java'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "rails/generators/nobrainer"
|
|
2
|
+
|
|
3
|
+
module NoBrainer::Generators
|
|
4
|
+
class ModelGenerator < Base
|
|
5
|
+
argument :attributes, :type => :array, default: [], banner: "field ... field"
|
|
6
|
+
|
|
7
|
+
check_class_collision
|
|
8
|
+
|
|
9
|
+
class_option :parent, :type => :string, :desc => "The parent class for the generated model"
|
|
10
|
+
|
|
11
|
+
def create_model_file
|
|
12
|
+
template "model.rb.tt", File.join("app/models", class_path, "#{file_name}.rb")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
hook_for :test_framework
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<% module_namespacing do -%>
|
|
2
|
+
class <%= class_name %><%= " < #{options[:parent].classify}" if options[:parent] %>
|
|
3
|
+
<% unless options[:parent] -%>
|
|
4
|
+
include NoBrainer::Document
|
|
5
|
+
<% end -%>
|
|
6
|
+
<% attributes.reject(&:reference?).each do |attribute| -%>
|
|
7
|
+
field :<%= attribute.name %>
|
|
8
|
+
<% end -%>
|
|
9
|
+
<% attributes.select(&:reference?).each do |attribute| -%>
|
|
10
|
+
belongs_to :<%= attribute.name %>
|
|
11
|
+
<% end -%>
|
|
12
|
+
end
|
|
13
|
+
<% end -%>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'rails/generators/named_base'
|
|
2
|
+
require 'nobrainer'
|
|
3
|
+
|
|
4
|
+
module NoBrainer::Generators
|
|
5
|
+
class Base < Rails::Generators::NamedBase
|
|
6
|
+
def self.base_name
|
|
7
|
+
'nobrainer'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.base_root
|
|
11
|
+
File.dirname(__FILE__)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.namespace
|
|
15
|
+
super.gsub(/no_brainer/,'nobrainer')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
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,35 +66,37 @@ extensions: []
|
|
|
66
66
|
extra_rdoc_files: []
|
|
67
67
|
files:
|
|
68
68
|
- lib/no_brainer/document/id.rb
|
|
69
|
-
- lib/no_brainer/document/injection_layer.rb
|
|
70
|
-
- lib/no_brainer/document/timestamps.rb
|
|
71
|
-
- lib/no_brainer/document/association/belongs_to.rb
|
|
72
|
-
- lib/no_brainer/document/association/core.rb
|
|
73
|
-
- lib/no_brainer/document/association/eager_loader.rb
|
|
74
|
-
- lib/no_brainer/document/association/has_many_through.rb
|
|
75
69
|
- lib/no_brainer/document/association/has_many.rb
|
|
76
70
|
- lib/no_brainer/document/association/has_one.rb
|
|
77
71
|
- lib/no_brainer/document/association/has_one_through.rb
|
|
72
|
+
- lib/no_brainer/document/association/has_many_through.rb
|
|
73
|
+
- lib/no_brainer/document/association/core.rb
|
|
74
|
+
- lib/no_brainer/document/association/belongs_to.rb
|
|
75
|
+
- lib/no_brainer/document/association/eager_loader.rb
|
|
76
|
+
- lib/no_brainer/document/polymorphic.rb
|
|
77
|
+
- lib/no_brainer/document/store_in.rb
|
|
78
|
+
- lib/no_brainer/document/injection_layer.rb
|
|
79
|
+
- lib/no_brainer/document/index.rb
|
|
80
|
+
- lib/no_brainer/document/serialization.rb
|
|
81
|
+
- lib/no_brainer/document/association.rb
|
|
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
|
|
78
86
|
- lib/no_brainer/document/dirty.rb
|
|
87
|
+
- lib/no_brainer/document/attributes.rb
|
|
79
88
|
- lib/no_brainer/document/dynamic_attributes.rb
|
|
80
89
|
- lib/no_brainer/document/persistance.rb
|
|
81
|
-
- lib/no_brainer/document/validation.rb
|
|
82
|
-
- lib/no_brainer/document/serialization.rb
|
|
83
90
|
- lib/no_brainer/document/criteria.rb
|
|
84
|
-
- lib/no_brainer/document/
|
|
85
|
-
- lib/no_brainer/document/polymorphic.rb
|
|
86
|
-
- lib/no_brainer/document/store_in.rb
|
|
87
|
-
- lib/no_brainer/document/attributes.rb
|
|
88
|
-
- lib/no_brainer/document/core.rb
|
|
89
|
-
- lib/no_brainer/document/association.rb
|
|
91
|
+
- lib/no_brainer/document/types.rb
|
|
90
92
|
- lib/no_brainer/query_runner/connection.rb
|
|
91
93
|
- lib/no_brainer/query_runner/database_on_demand.rb
|
|
92
|
-
- lib/no_brainer/query_runner/logger.rb
|
|
93
94
|
- lib/no_brainer/query_runner/table_on_demand.rb
|
|
94
95
|
- lib/no_brainer/query_runner/write_error.rb
|
|
95
96
|
- lib/no_brainer/query_runner/driver.rb
|
|
96
97
|
- lib/no_brainer/query_runner/missing_index.rb
|
|
97
98
|
- lib/no_brainer/query_runner/run_options.rb
|
|
99
|
+
- lib/no_brainer/query_runner/logger.rb
|
|
98
100
|
- lib/no_brainer/railtie/database.rake
|
|
99
101
|
- lib/no_brainer/criteria/chainable/limit.rb
|
|
100
102
|
- lib/no_brainer/criteria/chainable/order_by.rb
|
|
@@ -108,23 +110,25 @@ files:
|
|
|
108
110
|
- lib/no_brainer/criteria/termination/enumerable.rb
|
|
109
111
|
- lib/no_brainer/criteria/termination/update.rb
|
|
110
112
|
- lib/no_brainer/criteria/termination/delete.rb
|
|
111
|
-
- lib/no_brainer/criteria/termination/eager_loading.rb
|
|
112
113
|
- lib/no_brainer/criteria/termination/cache.rb
|
|
114
|
+
- lib/no_brainer/criteria/termination/preload.rb
|
|
113
115
|
- lib/no_brainer/decorated_symbol.rb
|
|
114
116
|
- lib/no_brainer/index_manager.rb
|
|
115
117
|
- lib/no_brainer/loader.rb
|
|
116
118
|
- lib/no_brainer/locale/en.yml
|
|
117
|
-
- lib/no_brainer/util.rb
|
|
118
119
|
- lib/no_brainer/fork.rb
|
|
119
|
-
- lib/no_brainer/criteria.rb
|
|
120
120
|
- lib/no_brainer/connection.rb
|
|
121
121
|
- lib/no_brainer/query_runner.rb
|
|
122
122
|
- lib/no_brainer/error.rb
|
|
123
|
-
- lib/no_brainer/document.rb
|
|
124
|
-
- lib/no_brainer/config.rb
|
|
125
123
|
- lib/no_brainer/railtie.rb
|
|
124
|
+
- lib/no_brainer/config.rb
|
|
126
125
|
- lib/no_brainer/autoload.rb
|
|
127
|
-
- lib/no_brainer/
|
|
126
|
+
- lib/no_brainer/document.rb
|
|
127
|
+
- lib/no_brainer/util.rb
|
|
128
|
+
- lib/no_brainer/criteria.rb
|
|
129
|
+
- lib/rails/generators/nobrainer.rb
|
|
130
|
+
- lib/rails/generators/nobrainer/model/model_generator.rb
|
|
131
|
+
- lib/rails/generators/nobrainer/model/templates/model.rb.tt
|
|
128
132
|
- lib/nobrainer.rb
|
|
129
133
|
- README.md
|
|
130
134
|
- LICENSE.md
|
|
@@ -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
|
data/lib/no_brainer/version.rb
DELETED