forked from mongodb/mongoid
/
multi_parameter_attributes.rb
106 lines (93 loc) · 3.48 KB
/
multi_parameter_attributes.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# encoding: utf-8
module Mongoid #:nodoc:
# Adds Rails' multi-parameter attribute support to Mongoid.
#
# @todo: Durran: This module needs an overhaul.
module MultiParameterAttributes
module Errors #:nodoc:
# Raised when an error occurred while doing a mass assignment to an
# attribute through the <tt>attributes=</tt> method. The exception
# has an +attribute+ property that is the name of the offending attribute.
class AttributeAssignmentError < Mongoid::Errors::MongoidError
attr_reader :exception, :attribute
def initialize(message, exception, attribute)
@exception = exception
@attribute = attribute
@message = message
end
end
# Raised when there are multiple errors while doing a mass assignment
# through the +attributes+ method. The exception has an +errors+
# property that contains an array of AttributeAssignmentError
# objects, each corresponding to the error while assigning to an
# attribute.
class MultiparameterAssignmentErrors < Mongoid::Errors::MongoidError
attr_reader :errors
def initialize(errors)
@errors = errors
end
end
end
# Process the provided attributes casting them to their proper values if a
# field exists for them on the document. This will be limited to only the
# attributes provided in the suppied +Hash+ so that no extra nil values get
# put into the document's attributes.
#
# @example Process the attributes.
# person.process(:title => "sir", :age => 40)
#
# @param [ Hash ] attrs The attributes to set.
# @param [ Symbol ] role A role for scoped mass assignment.
# @param [ Boolean ] guard_protected_attributes False to skip mass assignment protection.
#
# @since 2.0.0.rc.7
def process(attrs = nil, role = :default, guard_protected_attributes = true)
if attrs
errors = []
attributes = {}
multi_parameter_attributes = {}
attrs.each_pair do |key, value|
if key =~ /^([^\(]+)\((\d+)([if])\)$/
key, index = $1, $2.to_i
(multi_parameter_attributes[key] ||= {})[index] = value.empty? ? nil : value.send("to_#{$3}")
else
attributes[key] = value
end
end
multi_parameter_attributes.each_pair do |key, values|
begin
values = (values.keys.min..values.keys.max).map { |i| values[i] }
field = self.class.fields[key]
attributes[key] = instantiate_object(field, values)
rescue => e
errors << Errors::AttributeAssignmentError.new(
"error on assignment #{values.inspect} to #{key}", e, key
)
end
end
unless errors.empty?
raise(
Errors::MultiparameterAssignmentErrors.new(errors),
"#{errors.size} error(s) on assignment of multiparameter attributes"
)
end
super attributes, role, guard_protected_attributes
else
super
end
end
protected
def instantiate_object(field, values_with_empty_parameters)
return nil if values_with_empty_parameters.all? { |v| v.nil? }
values = values_with_empty_parameters.collect { |v| v.nil? ? 1 : v }
klass = field.type
if klass == DateTime || klass == Date || klass == Time
field.serialize(values)
elsif klass
klass.new *values
else
values
end
end
end
end