nobrainer 0.20.0 → 0.21.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 +14 -9
- data/lib/no_brainer/connection.rb +1 -3
- data/lib/no_brainer/criteria.rb +1 -1
- data/lib/no_brainer/criteria/aggregate.rb +2 -2
- data/lib/no_brainer/criteria/cache.rb +8 -3
- data/lib/no_brainer/criteria/core.rb +1 -5
- data/lib/no_brainer/criteria/delete.rb +1 -1
- data/lib/no_brainer/criteria/eager_load.rb +51 -0
- data/lib/no_brainer/criteria/order_by.rb +1 -1
- data/lib/no_brainer/criteria/scope.rb +3 -10
- data/lib/no_brainer/criteria/update.rb +8 -6
- data/lib/no_brainer/criteria/where.rb +50 -13
- data/lib/no_brainer/document.rb +2 -2
- data/lib/no_brainer/document/aliases.rb +0 -8
- data/lib/no_brainer/document/association/belongs_to.rb +6 -2
- data/lib/no_brainer/document/association/core.rb +5 -4
- data/lib/no_brainer/document/association/eager_loader.rb +7 -8
- data/lib/no_brainer/document/association/has_many.rb +22 -8
- data/lib/no_brainer/document/association/has_many_through.rb +12 -3
- data/lib/no_brainer/document/atomic_ops.rb +63 -61
- data/lib/no_brainer/document/attributes.rb +11 -3
- data/lib/no_brainer/document/core.rb +5 -2
- data/lib/no_brainer/document/criteria.rb +14 -5
- data/lib/no_brainer/document/dirty.rb +11 -16
- data/lib/no_brainer/document/index.rb +0 -6
- data/lib/no_brainer/document/index/meta_store.rb +1 -1
- data/lib/no_brainer/document/persistance.rb +12 -2
- data/lib/no_brainer/document/types.rb +13 -12
- data/lib/no_brainer/document/types/binary.rb +0 -4
- data/lib/no_brainer/document/types/boolean.rb +0 -1
- data/lib/no_brainer/document/types/geo.rb +1 -0
- data/lib/no_brainer/document/types/string.rb +3 -0
- data/lib/no_brainer/document/types/text.rb +18 -0
- data/lib/no_brainer/document/validation.rb +31 -6
- data/lib/no_brainer/document/validation/not_null.rb +15 -0
- data/lib/no_brainer/document/{uniqueness.rb → validation/uniqueness.rb} +11 -10
- data/lib/no_brainer/error.rb +20 -23
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/lock.rb +114 -0
- 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/run_options.rb +0 -3
- data/lib/no_brainer/query_runner/table_on_demand.rb +2 -3
- data/lib/no_brainer/rql.rb +0 -4
- data/lib/nobrainer.rb +1 -1
- metadata +8 -4
- data/lib/no_brainer/criteria/preload.rb +0 -44
@@ -21,6 +21,10 @@ class NoBrainer::Document::Association::BelongsTo
|
|
21
21
|
(options[:class_name] || target_name.to_s.camelize).constantize
|
22
22
|
end
|
23
23
|
|
24
|
+
def base_criteria
|
25
|
+
target_model.unscoped
|
26
|
+
end
|
27
|
+
|
24
28
|
def hook
|
25
29
|
super
|
26
30
|
|
@@ -34,11 +38,11 @@ class NoBrainer::Document::Association::BelongsTo
|
|
34
38
|
owner_model.validates(target_name, options[:validates]) if options[:validates]
|
35
39
|
|
36
40
|
delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
|
41
|
+
delegate("#{target_name}_changed?", "#{foreign_key}_changed?", :to => :self)
|
37
42
|
add_callback_for(:after_validation)
|
38
43
|
end
|
39
44
|
|
40
|
-
eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{ primary_key }
|
41
|
-
:unscoped => true
|
45
|
+
eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{ primary_key }
|
42
46
|
end
|
43
47
|
|
44
48
|
# Note:
|
@@ -20,12 +20,13 @@ module NoBrainer::Document::Association::Core
|
|
20
20
|
association_model.new(self, owner)
|
21
21
|
end
|
22
22
|
|
23
|
-
def delegate(
|
23
|
+
def delegate(method_src, method_dst, options={})
|
24
24
|
metadata = self
|
25
25
|
owner_model.inject_in_layer :associations do
|
26
|
-
define_method(
|
26
|
+
define_method(method_src) do |*args, &block|
|
27
27
|
super(*args, &block) if options[:call_super]
|
28
|
-
associations[metadata]
|
28
|
+
target = options[:to] == :self ? self : associations[metadata]
|
29
|
+
target.__send__(method_dst, *args, &block)
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
@@ -49,7 +50,7 @@ module NoBrainer::Document::Association::Core
|
|
49
50
|
|
50
51
|
included { attr_accessor :metadata, :owner }
|
51
52
|
|
52
|
-
delegate :primary_key, :foreign_key, :target_name, :target_model, :to => :metadata
|
53
|
+
delegate :primary_key, :foreign_key, :target_name, :target_model, :base_criteria, :to => :metadata
|
53
54
|
|
54
55
|
def initialize(metadata, owner)
|
55
56
|
@metadata, @owner = metadata, owner
|
@@ -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
|
|
@@ -40,20 +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.options[:
|
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
|
53
|
+
when Array then what.each { |v| eager_load(docs, v) }
|
55
54
|
when nil then;
|
56
|
-
else eager_load_association(docs,
|
55
|
+
else eager_load_association(docs, what) # String and Symbol
|
57
56
|
end
|
58
57
|
true
|
59
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?
|
@@ -86,12 +102,10 @@ class NoBrainer::Document::Association::HasMany
|
|
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,22 +2,38 @@ module NoBrainer::Document::AtomicOps
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
class PendingAtomic
|
5
|
+
attr_accessor :type
|
6
|
+
|
5
7
|
def self._new(instance, field, value, is_user_value)
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
9
14
|
else self
|
10
|
-
end.new(instance, field, value, is_user_value)
|
15
|
+
end.new(instance, field, value, is_user_value, type)
|
11
16
|
end
|
12
17
|
|
13
|
-
def initialize(instance, field, value, is_user_value)
|
18
|
+
def initialize(instance, field, value, is_user_value, type)
|
14
19
|
@instance = instance
|
15
20
|
@field = field.to_s
|
16
21
|
@value = value
|
17
22
|
@is_user_value = is_user_value
|
23
|
+
@type = type
|
18
24
|
@ops = []
|
19
25
|
end
|
20
26
|
|
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
|
35
|
+
end
|
36
|
+
|
21
37
|
def initialize_copy(other)
|
22
38
|
super
|
23
39
|
@ops = @ops.dup
|
@@ -29,6 +45,11 @@ module NoBrainer::Document::AtomicOps
|
|
29
45
|
alias_method :inspect, :to_s
|
30
46
|
|
31
47
|
def method_missing(method, *a, &b)
|
48
|
+
if method == :<<
|
49
|
+
method = :append
|
50
|
+
modify_source!
|
51
|
+
end
|
52
|
+
|
32
53
|
@ops << [method, a, b]
|
33
54
|
self
|
34
55
|
end
|
@@ -36,11 +57,10 @@ module NoBrainer::Document::AtomicOps
|
|
36
57
|
def compile_rql_value(rql_doc)
|
37
58
|
field = @instance.class.lookup_field_alias(@field)
|
38
59
|
value = @is_user_value ? RethinkDB::RQL.new.expr(@value) : rql_doc[field]
|
60
|
+
value = value.default(default_value) if default_value
|
39
61
|
@ops.reduce(value) { |v, (method, a, b)| v.__send__(method, *a, &b) }
|
40
62
|
end
|
41
|
-
end
|
42
63
|
|
43
|
-
class PendingAtomicContainer < PendingAtomic
|
44
64
|
def modify_source!
|
45
65
|
unless @instance._is_attribute_touched?(@field)
|
46
66
|
@instance.write_attribute(@field, self)
|
@@ -48,56 +68,59 @@ module NoBrainer::Document::AtomicOps
|
|
48
68
|
end
|
49
69
|
end
|
50
70
|
|
51
|
-
class
|
52
|
-
def
|
53
|
-
@ops << [:
|
71
|
+
class PendingAtomicContainer < PendingAtomic
|
72
|
+
def &(value)
|
73
|
+
@ops << [:set_intersection, [value.to_a]]
|
54
74
|
self
|
55
75
|
end
|
56
|
-
|
76
|
+
|
77
|
+
def |(value)
|
78
|
+
@ops << [:set_union, [value.to_a]]
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
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
|
57
86
|
|
58
87
|
def delete(value)
|
59
88
|
difference([value])
|
60
89
|
end
|
90
|
+
end
|
61
91
|
|
62
|
-
|
63
|
-
|
92
|
+
class PendingAtomicSet < PendingAtomicContainer
|
93
|
+
def <<(value)
|
94
|
+
@ops << [:set_union, [[value]]]
|
95
|
+
modify_source!
|
64
96
|
self
|
65
97
|
end
|
66
|
-
def add(v); self + v; end
|
67
98
|
|
68
|
-
def
|
69
|
-
@ops << [:
|
99
|
+
def +(value)
|
100
|
+
@ops << [:set_union, [value.to_a]]
|
70
101
|
self
|
71
102
|
end
|
72
|
-
def intersection(v); self & v; end
|
73
103
|
|
74
|
-
def
|
75
|
-
@ops << [:
|
104
|
+
def -(value)
|
105
|
+
@ops << [:set_difference, [value.to_a]]
|
76
106
|
self
|
77
107
|
end
|
78
|
-
|
108
|
+
end
|
79
109
|
|
110
|
+
class PendingAtomicArray < PendingAtomicContainer
|
80
111
|
def <<(value)
|
81
112
|
@ops << [:append, [value]]
|
82
113
|
modify_source!
|
83
114
|
self
|
84
115
|
end
|
85
|
-
end
|
86
|
-
|
87
|
-
class PendingAtomicSet < PendingAtomicContainer
|
88
|
-
def -(value)
|
89
|
-
@ops << [:set_difference, [value.to_a]]
|
90
|
-
self
|
91
|
-
end
|
92
116
|
|
93
117
|
def +(value)
|
94
|
-
@ops << [
|
118
|
+
@ops << [:+, [value.to_a]]
|
95
119
|
self
|
96
120
|
end
|
97
121
|
|
98
|
-
def
|
99
|
-
@ops << [:
|
100
|
-
modify_source!
|
122
|
+
def -(value)
|
123
|
+
@ops << [:difference, [value.to_a]]
|
101
124
|
self
|
102
125
|
end
|
103
126
|
end
|
@@ -108,6 +131,9 @@ module NoBrainer::Document::AtomicOps
|
|
108
131
|
end
|
109
132
|
|
110
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.
|
111
137
|
@_touched_attributes << name.to_s
|
112
138
|
end
|
113
139
|
|
@@ -142,12 +168,12 @@ module NoBrainer::Document::AtomicOps
|
|
142
168
|
def _read_attribute(name)
|
143
169
|
ensure_exclusive_atomic!
|
144
170
|
value = super
|
171
|
+
return value unless in_atomic?
|
145
172
|
|
146
|
-
case
|
147
|
-
when
|
148
|
-
when
|
149
|
-
|
150
|
-
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))
|
151
177
|
end
|
152
178
|
end
|
153
179
|
|
@@ -174,39 +200,15 @@ module NoBrainer::Document::AtomicOps
|
|
174
200
|
if saved
|
175
201
|
@_attributes.each do |attr, value|
|
176
202
|
next unless value.is_a?(PendingAtomic)
|
177
|
-
@_attributes[attr] = value.class.new(self, attr, nil, false)
|
203
|
+
@_attributes[attr] = value.class.new(self, attr, nil, false, value.type)
|
178
204
|
end
|
179
205
|
end
|
180
206
|
end
|
181
207
|
end
|
182
208
|
|
183
|
-
def read_attribute_for_change(attr)
|
184
|
-
super
|
185
|
-
rescue NoBrainer::Error::CannotReadAtomic => e
|
186
|
-
e.value
|
187
|
-
end
|
188
|
-
|
189
|
-
def read_attribute_for_validation(attr)
|
190
|
-
super
|
191
|
-
rescue NoBrainer::Error::CannotReadAtomic => e
|
192
|
-
e.value
|
193
|
-
end
|
194
|
-
|
195
209
|
module ClassMethods
|
196
210
|
def persistable_value(k, v, options={})
|
197
211
|
v.is_a?(PendingAtomic) ? v.compile_rql_value(options[:rql_doc]) : super
|
198
212
|
end
|
199
213
|
end
|
200
214
|
end
|
201
|
-
|
202
|
-
class ActiveModel::EachValidator
|
203
|
-
# XXX Monkey Patching :(
|
204
|
-
def validate(record)
|
205
|
-
attributes.each do |attribute|
|
206
|
-
value = record.read_attribute_for_validation(attribute)
|
207
|
-
next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- This is the added line
|
208
|
-
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
209
|
-
validate_each(record, attribute, value)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module NoBrainer::Document::Attributes
|
2
2
|
VALID_FIELD_OPTIONS = [:index, :default, :type, :readonly, :primary_key, :lazy_fetch, :store_as,
|
3
|
-
:validates, :required, :unique, :uniq, :format, :in]
|
3
|
+
:validates, :required, :unique, :uniq, :format, :in, :length, :min_length, :max_length]
|
4
4
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] \
|
5
5
|
+ NoBrainer::Criteria::Where::OPERATORS
|
6
6
|
extend ActiveSupport::Concern
|
7
|
+
include ActiveModel::ForbiddenAttributesProtection
|
7
8
|
|
8
9
|
included do
|
9
10
|
singleton_class.send(:attr_accessor, :fields)
|
@@ -23,6 +24,10 @@ module NoBrainer::Document::Attributes
|
|
23
24
|
Hash[readable_attributes.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
|
24
25
|
end
|
25
26
|
|
27
|
+
def raw_attributes
|
28
|
+
@_attributes
|
29
|
+
end
|
30
|
+
|
26
31
|
def _read_attribute(name)
|
27
32
|
@_attributes[name]
|
28
33
|
end
|
@@ -54,12 +59,14 @@ module NoBrainer::Document::Attributes
|
|
54
59
|
end
|
55
60
|
|
56
61
|
default_value = field_options[:default]
|
57
|
-
default_value = default_value
|
62
|
+
default_value = instance_exec(&default_value) if default_value.is_a?(Proc)
|
58
63
|
self.write_attribute(name, default_value)
|
59
64
|
end
|
60
65
|
end
|
61
66
|
|
62
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
|
+
|
63
70
|
if options[:pristine]
|
64
71
|
if options[:keep_ivars] && options[:missing_attributes].try(:[], :pluck)
|
65
72
|
options[:missing_attributes][:pluck].keys.each { |k| @_attributes.delete(k) }
|
@@ -74,6 +81,7 @@ module NoBrainer::Document::Attributes
|
|
74
81
|
clear_dirtiness(options)
|
75
82
|
else
|
76
83
|
clear_dirtiness(options) if options[:pristine]
|
84
|
+
attrs = sanitize_for_mass_assignment(attrs)
|
77
85
|
attrs.each { |k,v| self.write_attribute(k,v) }
|
78
86
|
end
|
79
87
|
assign_defaults(options) if options[:pristine]
|
@@ -82,7 +90,7 @@ module NoBrainer::Document::Attributes
|
|
82
90
|
|
83
91
|
def inspectable_attributes
|
84
92
|
# TODO test that thing
|
85
|
-
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
|
86
94
|
end
|
87
95
|
|
88
96
|
def to_s
|