nobrainer 0.20.0 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|