nobrainer 0.18.0 → 0.22.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 +0 -5
- data/lib/no_brainer/config.rb +73 -39
- data/lib/no_brainer/connection.rb +2 -4
- data/lib/no_brainer/criteria/after_find.rb +3 -11
- data/lib/no_brainer/criteria/aggregate.rb +2 -2
- data/lib/no_brainer/criteria/cache.rb +15 -10
- data/lib/no_brainer/criteria/core.rb +46 -11
- data/lib/no_brainer/criteria/delete.rb +2 -2
- data/lib/no_brainer/criteria/eager_load.rb +51 -0
- data/lib/no_brainer/criteria/extend.rb +4 -16
- data/lib/no_brainer/criteria/find.rb +27 -0
- data/lib/no_brainer/criteria/index.rb +7 -13
- data/lib/no_brainer/criteria/limit.rb +5 -12
- data/lib/no_brainer/criteria/order_by.rb +20 -36
- data/lib/no_brainer/criteria/pluck.rb +16 -22
- data/lib/no_brainer/criteria/raw.rb +4 -10
- data/lib/no_brainer/criteria/scope.rb +6 -19
- data/lib/no_brainer/criteria/update.rb +8 -6
- data/lib/no_brainer/criteria/where.rb +252 -138
- data/lib/no_brainer/criteria.rb +3 -2
- data/lib/no_brainer/document/aliases.rb +3 -3
- data/lib/no_brainer/document/association/belongs_to.rb +9 -5
- data/lib/no_brainer/document/association/core.rb +6 -5
- data/lib/no_brainer/document/association/eager_loader.rb +9 -9
- data/lib/no_brainer/document/association/has_many.rb +23 -9
- data/lib/no_brainer/document/association/has_many_through.rb +12 -3
- data/lib/no_brainer/document/atomic_ops.rb +79 -78
- data/lib/no_brainer/document/attributes.rb +24 -20
- data/lib/no_brainer/document/callbacks.rb +1 -1
- data/lib/no_brainer/document/core.rb +5 -2
- data/lib/no_brainer/document/criteria.rb +14 -19
- data/lib/no_brainer/document/dirty.rb +11 -16
- data/lib/no_brainer/document/index/index.rb +2 -1
- data/lib/no_brainer/document/index/meta_store.rb +1 -1
- data/lib/no_brainer/document/index.rb +14 -10
- data/lib/no_brainer/document/persistance.rb +24 -13
- data/lib/no_brainer/document/primary_key/generator.rb +83 -0
- data/lib/no_brainer/document/{id.rb → primary_key.rb} +9 -36
- data/lib/no_brainer/document/store_in.rb +2 -2
- data/lib/no_brainer/document/timestamps.rb +4 -2
- data/lib/no_brainer/document/types/binary.rb +2 -7
- data/lib/no_brainer/document/types/boolean.rb +2 -4
- data/lib/no_brainer/document/types/date.rb +2 -2
- data/lib/no_brainer/document/types/float.rb +2 -2
- data/lib/no_brainer/document/types/geo.rb +1 -0
- data/lib/no_brainer/document/types/integer.rb +2 -2
- data/lib/no_brainer/document/types/set.rb +2 -2
- data/lib/no_brainer/document/types/string.rb +5 -2
- data/lib/no_brainer/document/types/symbol.rb +2 -2
- data/lib/no_brainer/document/types/text.rb +18 -0
- data/lib/no_brainer/document/types/time.rb +2 -2
- data/lib/no_brainer/document/types.rb +17 -18
- data/lib/no_brainer/document/validation/not_null.rb +15 -0
- data/lib/no_brainer/document/{uniqueness.rb → validation/uniqueness.rb} +11 -11
- data/lib/no_brainer/document/validation.rb +35 -6
- data/lib/no_brainer/document.rb +1 -1
- data/lib/no_brainer/error.rb +21 -19
- data/lib/no_brainer/geo/base.rb +16 -0
- data/lib/no_brainer/geo/circle.rb +25 -0
- data/lib/no_brainer/geo/line_string.rb +11 -0
- data/lib/no_brainer/geo/point.rb +49 -0
- data/lib/no_brainer/geo/polygon.rb +11 -0
- data/lib/no_brainer/geo.rb +4 -0
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/lock.rb +114 -0
- data/lib/no_brainer/query_runner/connection_lock.rb +1 -1
- data/lib/no_brainer/query_runner/database_on_demand.rb +0 -1
- data/lib/no_brainer/query_runner/missing_index.rb +1 -1
- data/lib/no_brainer/query_runner/reconnect.rb +9 -11
- data/lib/no_brainer/query_runner/run_options.rb +0 -3
- data/lib/no_brainer/query_runner/table_on_demand.rb +3 -4
- data/lib/no_brainer/railtie/database.rake +2 -2
- data/lib/no_brainer/rql.rb +1 -5
- data/lib/nobrainer.rb +2 -6
- data/lib/rails/generators/nobrainer.rb +1 -1
- metadata +34 -9
- data/lib/no_brainer/criteria/preload.rb +0 -50
- data/lib/no_brainer/decorated_symbol.rb +0 -17
|
@@ -7,9 +7,8 @@ class NoBrainer::Document::Association::EagerLoader
|
|
|
7
7
|
owner_key = instance_exec(&options[:owner_key])
|
|
8
8
|
target_key = instance_exec(&options[:target_key])
|
|
9
9
|
|
|
10
|
-
criteria =
|
|
10
|
+
criteria = base_criteria
|
|
11
11
|
criteria = criteria.merge(additional_criteria) if additional_criteria
|
|
12
|
-
criteria = criteria.unscoped if options[:unscoped]
|
|
13
12
|
|
|
14
13
|
unloaded_docs = docs.reject { |doc| doc.associations[self].loaded? }
|
|
15
14
|
|
|
@@ -17,7 +16,7 @@ class NoBrainer::Document::Association::EagerLoader
|
|
|
17
16
|
if owner_keys.present?
|
|
18
17
|
targets = criteria.where(target_key.in => owner_keys)
|
|
19
18
|
.map { |target| [target.read_attribute(target_key), target] }
|
|
20
|
-
.
|
|
19
|
+
.each_with_object(Hash.new { |k,v| k[v] = [] }) { |(k,v),h| h[k] << v }
|
|
21
20
|
|
|
22
21
|
unloaded_docs.each do |doc|
|
|
23
22
|
doc_targets = targets[doc.read_attribute(owner_key)]
|
|
@@ -40,19 +39,20 @@ class NoBrainer::Document::Association::EagerLoader
|
|
|
40
39
|
association.eager_load(docs, criteria)
|
|
41
40
|
end
|
|
42
41
|
|
|
43
|
-
def eager_load(docs,
|
|
44
|
-
case
|
|
45
|
-
when Hash then
|
|
42
|
+
def eager_load(docs, what)
|
|
43
|
+
case what
|
|
44
|
+
when Hash then what.each do |k,v|
|
|
46
45
|
if v.is_a?(NoBrainer::Criteria)
|
|
47
46
|
v = v.dup
|
|
48
|
-
nested_preloads, v.
|
|
47
|
+
nested_preloads, v.options[:eager_load] = v.options[:eager_load], []
|
|
49
48
|
eager_load(eager_load_association(docs, k, v), nested_preloads)
|
|
50
49
|
else
|
|
51
50
|
eager_load(eager_load_association(docs, k), v)
|
|
52
51
|
end
|
|
53
52
|
end
|
|
54
|
-
when Array then
|
|
55
|
-
|
|
53
|
+
when Array then what.each { |v| eager_load(docs, v) }
|
|
54
|
+
when nil then;
|
|
55
|
+
else eager_load_association(docs, what) # String and Symbol
|
|
56
56
|
end
|
|
57
57
|
true
|
|
58
58
|
end
|
|
@@ -2,7 +2,7 @@ class NoBrainer::Document::Association::HasMany
|
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
|
3
3
|
|
|
4
4
|
class Metadata
|
|
5
|
-
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :dependent]
|
|
5
|
+
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :dependent, :scope]
|
|
6
6
|
include NoBrainer::Document::Association::Core::Metadata
|
|
7
7
|
extend NoBrainer::Document::Association::EagerLoader::Generic
|
|
8
8
|
|
|
@@ -21,6 +21,10 @@ class NoBrainer::Document::Association::HasMany
|
|
|
21
21
|
(options[:class_name] || target_name.to_s.singularize.camelize).constantize
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def base_criteria
|
|
25
|
+
options[:scope] ? target_model.instance_exec(&options[:scope]) : target_model.all
|
|
26
|
+
end
|
|
27
|
+
|
|
24
28
|
def inverses
|
|
25
29
|
# We can always infer the inverse association of a has_many relationship,
|
|
26
30
|
# because a belongs_to association cannot have a scope applied on the
|
|
@@ -37,15 +41,27 @@ class NoBrainer::Document::Association::HasMany
|
|
|
37
41
|
|
|
38
42
|
def hook
|
|
39
43
|
super
|
|
40
|
-
|
|
44
|
+
|
|
45
|
+
if options[:scope]
|
|
46
|
+
raise ":scope must be passed a lambda like this: `:scope => ->{ where(...) }'" unless options[:scope].is_a?(Proc)
|
|
47
|
+
raise ":dependent and :scope cannot be used together" if options[:dependent]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if options[:dependent]
|
|
51
|
+
unless [:destroy, :delete, :nullify, :restrict, nil].include?(options[:dependent])
|
|
52
|
+
raise "Invalid dependent option: `#{options[:dependent].inspect}'. " +
|
|
53
|
+
"Valid options are: :destroy, :delete, :nullify, or :restrict"
|
|
54
|
+
end
|
|
55
|
+
add_callback_for(:before_destroy)
|
|
56
|
+
end
|
|
41
57
|
end
|
|
42
58
|
|
|
43
59
|
eager_load_with :owner_key => ->{ primary_key }, :target_key => ->{ foreign_key }
|
|
44
60
|
end
|
|
45
61
|
|
|
46
62
|
def target_criteria
|
|
47
|
-
@target_criteria ||=
|
|
48
|
-
|
|
63
|
+
@target_criteria ||= base_criteria.where(foreign_key => owner.pk_value)
|
|
64
|
+
.after_find(set_inverse_proc)
|
|
49
65
|
end
|
|
50
66
|
|
|
51
67
|
def read
|
|
@@ -53,8 +69,8 @@ class NoBrainer::Document::Association::HasMany
|
|
|
53
69
|
end
|
|
54
70
|
|
|
55
71
|
def write(new_children)
|
|
56
|
-
raise "You can't assign
|
|
57
|
-
"Instead, you must modify delete and create
|
|
72
|
+
raise "You can't assign `#{target_name}'. " \
|
|
73
|
+
"Instead, you must modify delete and create `#{target_model}' manually."
|
|
58
74
|
end
|
|
59
75
|
|
|
60
76
|
def loaded?
|
|
@@ -80,18 +96,16 @@ class NoBrainer::Document::Association::HasMany
|
|
|
80
96
|
end
|
|
81
97
|
|
|
82
98
|
def set_inverse_proc
|
|
83
|
-
|
|
99
|
+
->(target){ set_inverses_of([target]) if target.is_a?(NoBrainer::Document) }
|
|
84
100
|
end
|
|
85
101
|
|
|
86
102
|
def before_destroy_callback
|
|
87
103
|
criteria = target_criteria.unscoped.without_cache
|
|
88
104
|
case metadata.options[:dependent]
|
|
89
|
-
when nil then
|
|
90
105
|
when :destroy then criteria.destroy_all
|
|
91
106
|
when :delete then criteria.delete_all
|
|
92
107
|
when :nullify then criteria.update_all(foreign_key => nil)
|
|
93
108
|
when :restrict then raise NoBrainer::Error::ChildrenExist unless criteria.count.zero?
|
|
94
|
-
else raise "Unrecognized dependent option: #{metadata.options[:dependent]}"
|
|
95
109
|
end
|
|
96
110
|
end
|
|
97
111
|
end
|
|
@@ -2,7 +2,7 @@ class NoBrainer::Document::Association::HasManyThrough
|
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
|
3
3
|
|
|
4
4
|
class Metadata
|
|
5
|
-
VALID_OPTIONS = [:through]
|
|
5
|
+
VALID_OPTIONS = [:through, :scope]
|
|
6
6
|
include NoBrainer::Document::Association::Core::Metadata
|
|
7
7
|
|
|
8
8
|
def through_association_name
|
|
@@ -14,10 +14,18 @@ class NoBrainer::Document::Association::HasManyThrough
|
|
|
14
14
|
raise "#{through_association_name} association not found"
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def eager_load(docs,
|
|
17
|
+
def eager_load(docs, additional_criteria=nil)
|
|
18
|
+
criteria = target_model.instance_exec(&options[:scope]) if options[:scope]
|
|
19
|
+
criteria = criteria ? criteria.merge(additional_criteria) : additional_criteria if additional_criteria
|
|
18
20
|
NoBrainer::Document::Association::EagerLoader.new
|
|
19
21
|
.eager_load_association(through_association.eager_load(docs), target_name, criteria)
|
|
20
22
|
end
|
|
23
|
+
|
|
24
|
+
def target_model
|
|
25
|
+
meta = through_association.target_model.association_metadata
|
|
26
|
+
association = meta[target_name.to_sym] || meta[target_name.to_s.singularize.to_sym]
|
|
27
|
+
association.target_model
|
|
28
|
+
end
|
|
21
29
|
end
|
|
22
30
|
|
|
23
31
|
def read
|
|
@@ -26,6 +34,7 @@ class NoBrainer::Document::Association::HasManyThrough
|
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def write(new_children)
|
|
29
|
-
raise "You can't assign
|
|
37
|
+
raise "You can't assign `#{target_name}'. " \
|
|
38
|
+
"Instead, you must modify delete and create `#{target_model}' manually."
|
|
30
39
|
end
|
|
31
40
|
end
|
|
@@ -2,103 +2,125 @@ module NoBrainer::Document::AtomicOps
|
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
class PendingAtomic
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
attr_accessor :type
|
|
6
|
+
|
|
7
|
+
def self._new(instance, field, value, is_user_value)
|
|
8
|
+
type = instance.class.fields[field.to_sym].try(:[], :type)
|
|
9
|
+
type ||= value.class unless value.nil?
|
|
10
|
+
|
|
11
|
+
case
|
|
12
|
+
when type == Array then PendingAtomicArray
|
|
13
|
+
when type == Set then PendingAtomicSet
|
|
14
|
+
else self
|
|
15
|
+
end.new(instance, field, value, is_user_value, type)
|
|
12
16
|
end
|
|
13
17
|
|
|
14
|
-
def initialize(instance, field, value, is_user_value,
|
|
18
|
+
def initialize(instance, field, value, is_user_value, type)
|
|
15
19
|
@instance = instance
|
|
16
20
|
@field = field.to_s
|
|
17
21
|
@value = value
|
|
18
22
|
@is_user_value = is_user_value
|
|
19
|
-
@
|
|
23
|
+
@type = type
|
|
20
24
|
@ops = []
|
|
21
25
|
end
|
|
22
26
|
|
|
23
|
-
def
|
|
24
|
-
|
|
27
|
+
def default_value
|
|
28
|
+
case
|
|
29
|
+
when @type == Array then []
|
|
30
|
+
when @type == Set then []
|
|
31
|
+
when @type == Integer then 0
|
|
32
|
+
when @type == Float then 0.0
|
|
33
|
+
when @type == String then ""
|
|
34
|
+
end
|
|
25
35
|
end
|
|
26
36
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@instance.write_attribute(@field, self)
|
|
31
|
-
end
|
|
37
|
+
def initialize_copy(other)
|
|
38
|
+
super
|
|
39
|
+
@ops = @ops.dup
|
|
32
40
|
end
|
|
33
41
|
|
|
34
42
|
def to_s
|
|
35
|
-
"
|
|
43
|
+
"<`#{@field}' with #{@ops.size} pending atomic operations>"
|
|
36
44
|
end
|
|
37
|
-
|
|
45
|
+
alias_method :inspect, :to_s
|
|
46
|
+
|
|
47
|
+
def method_missing(method, *a, &b)
|
|
48
|
+
if method == :<<
|
|
49
|
+
method = :append
|
|
50
|
+
modify_source!
|
|
51
|
+
end
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
@ops << [method_name, a, b]
|
|
53
|
+
@ops << [method, a, b]
|
|
41
54
|
self
|
|
42
55
|
end
|
|
43
56
|
|
|
44
57
|
def compile_rql_value(rql_doc)
|
|
45
58
|
field = @instance.class.lookup_field_alias(@field)
|
|
46
59
|
value = @is_user_value ? RethinkDB::RQL.new.expr(@value) : rql_doc[field]
|
|
47
|
-
|
|
48
|
-
value
|
|
60
|
+
value = value.default(default_value) if default_value
|
|
61
|
+
@ops.reduce(value) { |v, (method, a, b)| v.__send__(method, *a, &b) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def modify_source!
|
|
65
|
+
unless @instance._is_attribute_touched?(@field)
|
|
66
|
+
@instance.write_attribute(@field, self)
|
|
67
|
+
end
|
|
49
68
|
end
|
|
50
69
|
end
|
|
51
70
|
|
|
52
|
-
class
|
|
53
|
-
def
|
|
54
|
-
@ops << [:
|
|
71
|
+
class PendingAtomicContainer < PendingAtomic
|
|
72
|
+
def &(value)
|
|
73
|
+
@ops << [:set_intersection, [value.to_a]]
|
|
55
74
|
self
|
|
56
75
|
end
|
|
57
|
-
def difference(v); self - v; end
|
|
58
76
|
|
|
59
|
-
def
|
|
60
|
-
|
|
77
|
+
def |(value)
|
|
78
|
+
@ops << [:set_union, [value.to_a]]
|
|
79
|
+
self
|
|
61
80
|
end
|
|
62
81
|
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
def add(v); self + v; end
|
|
83
|
+
def difference(v); self - v; end
|
|
84
|
+
def intersection(v); self & v; end
|
|
85
|
+
def union(v); self | v; end
|
|
86
|
+
|
|
87
|
+
def delete(value)
|
|
88
|
+
difference([value])
|
|
66
89
|
end
|
|
67
|
-
|
|
90
|
+
end
|
|
68
91
|
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
class PendingAtomicSet < PendingAtomicContainer
|
|
93
|
+
def <<(value)
|
|
94
|
+
@ops << [:set_union, [[value]]]
|
|
95
|
+
modify_source!
|
|
71
96
|
self
|
|
72
97
|
end
|
|
73
|
-
def intersection(v); self & v; end
|
|
74
98
|
|
|
75
|
-
def
|
|
99
|
+
def +(value)
|
|
76
100
|
@ops << [:set_union, [value.to_a]]
|
|
77
101
|
self
|
|
78
102
|
end
|
|
79
|
-
def union(v); self | v; end
|
|
80
103
|
|
|
81
|
-
def
|
|
82
|
-
@ops << [:
|
|
83
|
-
ensure_writeable!
|
|
104
|
+
def -(value)
|
|
105
|
+
@ops << [:set_difference, [value.to_a]]
|
|
84
106
|
self
|
|
85
107
|
end
|
|
86
108
|
end
|
|
87
109
|
|
|
88
|
-
class
|
|
89
|
-
def
|
|
90
|
-
@ops << [:
|
|
110
|
+
class PendingAtomicArray < PendingAtomicContainer
|
|
111
|
+
def <<(value)
|
|
112
|
+
@ops << [:append, [value]]
|
|
113
|
+
modify_source!
|
|
91
114
|
self
|
|
92
115
|
end
|
|
93
116
|
|
|
94
117
|
def +(value)
|
|
95
|
-
@ops << [
|
|
118
|
+
@ops << [:+, [value.to_a]]
|
|
96
119
|
self
|
|
97
120
|
end
|
|
98
121
|
|
|
99
|
-
def
|
|
100
|
-
@ops << [:
|
|
101
|
-
ensure_writeable!
|
|
122
|
+
def -(value)
|
|
123
|
+
@ops << [:difference, [value.to_a]]
|
|
102
124
|
self
|
|
103
125
|
end
|
|
104
126
|
end
|
|
@@ -109,6 +131,9 @@ module NoBrainer::Document::AtomicOps
|
|
|
109
131
|
end
|
|
110
132
|
|
|
111
133
|
def _touch_attribute(name)
|
|
134
|
+
# The difference with dirty tracking and this is that dirty tracking does
|
|
135
|
+
# not take into account fields that are set with their old value, whereas the
|
|
136
|
+
# touched attribute does.
|
|
112
137
|
@_touched_attributes << name.to_s
|
|
113
138
|
end
|
|
114
139
|
|
|
@@ -143,12 +168,12 @@ module NoBrainer::Document::AtomicOps
|
|
|
143
168
|
def _read_attribute(name)
|
|
144
169
|
ensure_exclusive_atomic!
|
|
145
170
|
value = super
|
|
171
|
+
return value unless in_atomic?
|
|
146
172
|
|
|
147
|
-
case
|
|
148
|
-
when
|
|
149
|
-
when
|
|
150
|
-
|
|
151
|
-
when [false, false] then value
|
|
173
|
+
case value
|
|
174
|
+
when PendingAtomicContainer then value
|
|
175
|
+
when PendingAtomic then value.dup
|
|
176
|
+
else PendingAtomic._new(self, name, value, _is_attribute_touched?(name))
|
|
152
177
|
end
|
|
153
178
|
end
|
|
154
179
|
|
|
@@ -156,9 +181,9 @@ module NoBrainer::Document::AtomicOps
|
|
|
156
181
|
ensure_exclusive_atomic!
|
|
157
182
|
|
|
158
183
|
case [in_atomic?, value.is_a?(PendingAtomic)]
|
|
159
|
-
when [true, true] then super
|
|
160
184
|
when [true, false] then raise NoBrainer::Error::AtomicBlock.new('Avoid the use of atomic blocks for non atomic operations')
|
|
161
185
|
when [false, true] then raise NoBrainer::Error::AtomicBlock.new('Use atomic blocks for atomic operations')
|
|
186
|
+
when [true, true] then super.tap { _touch_attribute(name) }
|
|
162
187
|
when [false, false] then super.tap { _touch_attribute(name) }
|
|
163
188
|
end
|
|
164
189
|
end
|
|
@@ -175,39 +200,15 @@ module NoBrainer::Document::AtomicOps
|
|
|
175
200
|
if saved
|
|
176
201
|
@_attributes.each do |attr, value|
|
|
177
202
|
next unless value.is_a?(PendingAtomic)
|
|
178
|
-
@_attributes[attr] = value.class.new(self, attr, nil, false)
|
|
203
|
+
@_attributes[attr] = value.class.new(self, attr, nil, false, value.type)
|
|
179
204
|
end
|
|
180
205
|
end
|
|
181
206
|
end
|
|
182
207
|
end
|
|
183
208
|
|
|
184
|
-
def read_attribute_for_change(attr)
|
|
185
|
-
super
|
|
186
|
-
rescue NoBrainer::Error::CannotReadAtomic => e
|
|
187
|
-
e.value
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def read_attribute_for_validation(attr)
|
|
191
|
-
super
|
|
192
|
-
rescue NoBrainer::Error::CannotReadAtomic => e
|
|
193
|
-
e.value
|
|
194
|
-
end
|
|
195
|
-
|
|
196
209
|
module ClassMethods
|
|
197
210
|
def persistable_value(k, v, options={})
|
|
198
211
|
v.is_a?(PendingAtomic) ? v.compile_rql_value(options[:rql_doc]) : super
|
|
199
212
|
end
|
|
200
213
|
end
|
|
201
214
|
end
|
|
202
|
-
|
|
203
|
-
class ActiveModel::EachValidator
|
|
204
|
-
# XXX Monkey Patching :(
|
|
205
|
-
def validate(record)
|
|
206
|
-
attributes.each do |attribute|
|
|
207
|
-
value = record.read_attribute_for_validation(attribute)
|
|
208
|
-
next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- This is the added line
|
|
209
|
-
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
|
210
|
-
validate_each(record, attribute, value)
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
module NoBrainer::Document::Attributes
|
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type, :
|
|
3
|
-
:validates, :required, :unique, :uniq, :format, :in,
|
|
4
|
-
:readonly, :primary_key, :as, :lazy_fetch]
|
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :readonly, :primary_key, :lazy_fetch, :store_as,
|
|
3
|
+
:validates, :required, :unique, :uniq, :format, :in, :length, :min_length, :max_length]
|
|
5
4
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] \
|
|
6
|
-
+ NoBrainer::
|
|
5
|
+
+ NoBrainer::Criteria::Where::OPERATORS
|
|
7
6
|
extend ActiveSupport::Concern
|
|
7
|
+
include ActiveModel::ForbiddenAttributesProtection
|
|
8
8
|
|
|
9
9
|
included do
|
|
10
|
-
# Not using class_attribute because we want to
|
|
11
|
-
# use our custom logic
|
|
12
10
|
singleton_class.send(:attr_accessor, :fields)
|
|
13
11
|
self.fields = {}
|
|
14
12
|
end
|
|
@@ -26,6 +24,10 @@ module NoBrainer::Document::Attributes
|
|
|
26
24
|
Hash[readable_attributes.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
|
|
27
25
|
end
|
|
28
26
|
|
|
27
|
+
def raw_attributes
|
|
28
|
+
@_attributes
|
|
29
|
+
end
|
|
30
|
+
|
|
29
31
|
def _read_attribute(name)
|
|
30
32
|
@_attributes[name]
|
|
31
33
|
end
|
|
@@ -46,24 +48,25 @@ module NoBrainer::Document::Attributes
|
|
|
46
48
|
|
|
47
49
|
def assign_defaults(options)
|
|
48
50
|
self.class.fields.each do |name, field_options|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
default_value = field_options[:default]
|
|
60
|
-
default_value = default_value.call if default_value.is_a?(Proc)
|
|
61
|
-
self.write_attribute(name, default_value)
|
|
51
|
+
next unless field_options.has_key?(:default) &&
|
|
52
|
+
!@_attributes.has_key?(name)
|
|
53
|
+
|
|
54
|
+
if opt = options[:missing_attributes]
|
|
55
|
+
if (opt[:pluck] && !opt[:pluck][name]) ||
|
|
56
|
+
(opt[:without] && opt[:without][name])
|
|
57
|
+
next
|
|
58
|
+
end
|
|
62
59
|
end
|
|
60
|
+
|
|
61
|
+
default_value = field_options[:default]
|
|
62
|
+
default_value = instance_exec(&default_value) if default_value.is_a?(Proc)
|
|
63
|
+
self.write_attribute(name, default_value)
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
def assign_attributes(attrs, options={})
|
|
68
|
+
raise ArgumentError, "To assign attributes, please pass a hash instead of `#{attrs.class}'" unless attrs.is_a?(Hash)
|
|
69
|
+
|
|
67
70
|
if options[:pristine]
|
|
68
71
|
if options[:keep_ivars] && options[:missing_attributes].try(:[], :pluck)
|
|
69
72
|
options[:missing_attributes][:pluck].keys.each { |k| @_attributes.delete(k) }
|
|
@@ -78,6 +81,7 @@ module NoBrainer::Document::Attributes
|
|
|
78
81
|
clear_dirtiness(options)
|
|
79
82
|
else
|
|
80
83
|
clear_dirtiness(options) if options[:pristine]
|
|
84
|
+
attrs = sanitize_for_mass_assignment(attrs)
|
|
81
85
|
attrs.each { |k,v| self.write_attribute(k,v) }
|
|
82
86
|
end
|
|
83
87
|
assign_defaults(options) if options[:pristine]
|
|
@@ -86,7 +90,7 @@ module NoBrainer::Document::Attributes
|
|
|
86
90
|
|
|
87
91
|
def inspectable_attributes
|
|
88
92
|
# TODO test that thing
|
|
89
|
-
Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
|
|
93
|
+
Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }].with_indifferent_access.freeze
|
|
90
94
|
end
|
|
91
95
|
|
|
92
96
|
def to_s
|
|
@@ -2,7 +2,7 @@ module NoBrainer::Document::Core
|
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
singleton_class.class_eval do
|
|
5
|
-
attr_accessor :_all
|
|
5
|
+
attr_accessor :_all, :_all_nobrainer
|
|
6
6
|
|
|
7
7
|
def all
|
|
8
8
|
Rails.application.eager_load! if defined?(Rails.application.eager_load!)
|
|
@@ -10,10 +10,12 @@ module NoBrainer::Document::Core
|
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
self._all = []
|
|
13
|
+
self._all_nobrainer = []
|
|
13
14
|
|
|
14
15
|
include ActiveModel::Conversion
|
|
15
16
|
|
|
16
17
|
def to_key
|
|
18
|
+
# ActiveModel::Conversion stuff
|
|
17
19
|
[pk_value]
|
|
18
20
|
end
|
|
19
21
|
|
|
@@ -22,6 +24,7 @@ module NoBrainer::Document::Core
|
|
|
22
24
|
extend ActiveModel::Naming
|
|
23
25
|
extend ActiveModel::Translation
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
list_name = self.name =~ /^NoBrainer::/ ? :_all_nobrainer : :_all
|
|
28
|
+
NoBrainer::Document::Core.__send__(list_name) << self
|
|
26
29
|
end
|
|
27
30
|
end
|
|
@@ -2,12 +2,14 @@ module NoBrainer::Document::Criteria
|
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
def selector
|
|
5
|
-
|
|
5
|
+
# Used for writes
|
|
6
|
+
self.class.rql_table.get(pk_value)
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
included do
|
|
9
|
-
cattr_accessor :default_scope_proc, :instance_accessor => false
|
|
10
10
|
cattr_accessor :perf_warnings_disabled, :instance_accessor => false
|
|
11
|
+
singleton_class.send(:attr_accessor, :default_scopes)
|
|
12
|
+
self.default_scopes = []
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
module ClassMethods
|
|
@@ -21,12 +23,13 @@ module NoBrainer::Document::Criteria
|
|
|
21
23
|
:with_cache, :without_cache, # Cache
|
|
22
24
|
:count, :empty?, :any?, # Count
|
|
23
25
|
:delete_all, :destroy_all, # Delete
|
|
24
|
-
:
|
|
26
|
+
:preload, :eager_load, # EagerLoad
|
|
25
27
|
:each, :to_a, # Enumerable
|
|
26
28
|
:first, :last, :first!, :last!, :sample, # First
|
|
27
29
|
:min, :max, :sum, :avg, # Aggregate
|
|
28
30
|
:update_all, :replace_all, # Update
|
|
29
31
|
:pluck, :without, :lazy_fetch, :without_plucking, # Pluck
|
|
32
|
+
:find_by?, :find_by, :find_by!, :find?, :find, :find!, # Find
|
|
30
33
|
:to => :all
|
|
31
34
|
|
|
32
35
|
def all
|
|
@@ -42,25 +45,17 @@ module NoBrainer::Document::Criteria
|
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def default_scope(criteria=nil, &block)
|
|
45
|
-
criteria
|
|
46
|
-
raise "
|
|
47
|
-
self.default_scope_proc = criteria.is_a?(Proc) ? criteria : proc { criteria }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def selector_for(pk)
|
|
51
|
-
rql_table.get(pk)
|
|
52
|
-
end
|
|
48
|
+
criteria_proc = block || (criteria.is_a?(Proc) ? criteria : proc { criteria })
|
|
49
|
+
raise "default_scope only accepts a criteria or a proc that returns criteria" unless criteria_proc.is_a?(Proc)
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
new_from_db(attrs).tap { |doc| doc.run_callbacks(:find) } if attrs
|
|
51
|
+
([self] + self.descendants).each do |model|
|
|
52
|
+
model.default_scopes << criteria_proc
|
|
53
|
+
end
|
|
58
54
|
end
|
|
59
55
|
|
|
60
|
-
def
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
56
|
+
def inherited(subclass)
|
|
57
|
+
subclass.default_scopes = self.default_scopes.dup
|
|
58
|
+
super
|
|
64
59
|
end
|
|
65
60
|
|
|
66
61
|
def disable_perf_warnings
|
|
@@ -31,14 +31,10 @@ module NoBrainer::Document::Dirty
|
|
|
31
31
|
changes.keys
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def read_attribute_for_change(attr)
|
|
35
|
-
read_attribute(attr)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
34
|
def changes
|
|
39
35
|
result = {}.with_indifferent_access
|
|
40
36
|
@_old_attributes.each do |attr, old_value|
|
|
41
|
-
current_value =
|
|
37
|
+
current_value = read_attribute(attr)
|
|
42
38
|
if current_value != old_value || !@_old_attributes_keys.include?(attr)
|
|
43
39
|
result[attr] = [old_value, current_value]
|
|
44
40
|
end
|
|
@@ -46,16 +42,15 @@ module NoBrainer::Document::Dirty
|
|
|
46
42
|
result
|
|
47
43
|
end
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
current_value
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
class None; end
|
|
46
|
+
def attribute_may_change(attr, current_value = None)
|
|
47
|
+
if current_value == None
|
|
48
|
+
current_value = begin
|
|
49
|
+
assert_access_field(attr)
|
|
50
|
+
read_attribute(attr)
|
|
51
|
+
rescue NoBrainer::Error::MissingAttribute => e
|
|
52
|
+
e
|
|
56
53
|
end
|
|
57
|
-
rescue NoBrainer::Error::MissingAttribute => e
|
|
58
|
-
e
|
|
59
54
|
end
|
|
60
55
|
|
|
61
56
|
unless @_old_attributes.has_key?(attr)
|
|
@@ -84,7 +79,7 @@ module NoBrainer::Document::Dirty
|
|
|
84
79
|
inject_in_layer :dirty_tracking do
|
|
85
80
|
define_method("#{attr}_change") do
|
|
86
81
|
if @_old_attributes.has_key?(attr)
|
|
87
|
-
result = [@_old_attributes[attr],
|
|
82
|
+
result = [@_old_attributes[attr], read_attribute(attr)]
|
|
88
83
|
result if result.first != result.last || !@_old_attributes_keys.include?(attr)
|
|
89
84
|
end
|
|
90
85
|
end
|
|
@@ -94,7 +89,7 @@ module NoBrainer::Document::Dirty
|
|
|
94
89
|
end
|
|
95
90
|
|
|
96
91
|
define_method("#{attr}_was") do
|
|
97
|
-
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] :
|
|
92
|
+
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] : read_attribute(attr)
|
|
98
93
|
end
|
|
99
94
|
end
|
|
100
95
|
end
|
|
@@ -9,7 +9,8 @@ class NoBrainer::Document::Index::Index < Struct.new(
|
|
|
9
9
|
self.name = self.name.to_sym
|
|
10
10
|
self.aliased_name = self.aliased_name.to_sym
|
|
11
11
|
self.external = !!self.external
|
|
12
|
-
|
|
12
|
+
# geo defaults for true with geo types.
|
|
13
|
+
self.geo = !!model.fields[name].try(:[], :type).try(:<, NoBrainer::Geo::Base) if self.geo.nil?
|
|
13
14
|
self.multi = !!self.multi
|
|
14
15
|
end
|
|
15
16
|
|