activemodel 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +114 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +264 -0
- data/lib/active_model.rb +77 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute/user_provided_default.rb +52 -0
- data/lib/active_model/attribute_assignment.rb +57 -0
- data/lib/active_model/attribute_methods.rb +478 -0
- data/lib/active_model/attribute_mutation_tracker.rb +124 -0
- data/lib/active_model/attribute_set.rb +114 -0
- data/lib/active_model/attribute_set/builder.rb +126 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_model/attributes.rb +111 -0
- data/lib/active_model/callbacks.rb +153 -0
- data/lib/active_model/conversion.rb +111 -0
- data/lib/active_model/dirty.rb +343 -0
- data/lib/active_model/errors.rb +517 -0
- data/lib/active_model/forbidden_attributes_protection.rb +31 -0
- data/lib/active_model/gem_version.rb +17 -0
- data/lib/active_model/lint.rb +118 -0
- data/lib/active_model/locale/en.yml +36 -0
- data/lib/active_model/model.rb +99 -0
- data/lib/active_model/naming.rb +318 -0
- data/lib/active_model/railtie.rb +14 -0
- data/lib/active_model/secure_password.rb +129 -0
- data/lib/active_model/serialization.rb +192 -0
- data/lib/active_model/serializers/json.rb +146 -0
- data/lib/active_model/translation.rb +70 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/type/big_integer.rb +15 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +38 -0
- data/lib/active_model/type/date.rb +57 -0
- data/lib/active_model/type/date_time.rb +51 -0
- data/lib/active_model/type/decimal.rb +70 -0
- data/lib/active_model/type/float.rb +36 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +37 -0
- data/lib/active_model/type/helpers/time_value.rb +68 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/immutable_string.rb +32 -0
- data/lib/active_model/type/integer.rb +70 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +26 -0
- data/lib/active_model/type/time.rb +51 -0
- data/lib/active_model/type/value.rb +126 -0
- data/lib/active_model/validations.rb +439 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +106 -0
- data/lib/active_model/validations/callbacks.rb +122 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/confirmation.rb +80 -0
- data/lib/active_model/validations/exclusion.rb +49 -0
- data/lib/active_model/validations/format.rb +114 -0
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +47 -0
- data/lib/active_model/validations/length.rb +129 -0
- data/lib/active_model/validations/numericality.rb +189 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/validates.rb +174 -0
- data/lib/active_model/validations/with.rb +147 -0
- data/lib/active_model/validator.rb +183 -0
- data/lib/active_model/version.rb +10 -0
- metadata +125 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
# == \Active \Model Absence Validator
|
6
|
+
class AbsenceValidator < EachValidator #:nodoc:
|
7
|
+
def validate_each(record, attr_name, value)
|
8
|
+
record.errors.add(attr_name, :present, options) if value.present?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module HelperMethods
|
13
|
+
# Validates that the specified attributes are blank (as defined by
|
14
|
+
# Object#blank?). Happens by default on save.
|
15
|
+
#
|
16
|
+
# class Person < ActiveRecord::Base
|
17
|
+
# validates_absence_of :first_name
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# The first_name attribute must be in the object and it must be blank.
|
21
|
+
#
|
22
|
+
# Configuration options:
|
23
|
+
# * <tt>:message</tt> - A custom error message (default is: "must be blank").
|
24
|
+
#
|
25
|
+
# There is also a list of default options supported by every validator:
|
26
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
27
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
28
|
+
def validates_absence_of(*attr_names)
|
29
|
+
validates_with AbsenceValidator, _merge_attributes(attr_names)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class AcceptanceValidator < EachValidator # :nodoc:
|
6
|
+
def initialize(options)
|
7
|
+
super({ allow_nil: true, accept: ["1", true] }.merge!(options))
|
8
|
+
setup!(options[:class])
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
unless acceptable_option?(value)
|
13
|
+
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def setup!(klass)
|
20
|
+
klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes)))
|
21
|
+
end
|
22
|
+
|
23
|
+
def acceptable_option?(value)
|
24
|
+
Array(options[:accept]).include?(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
class LazilyDefineAttributes < Module
|
28
|
+
def initialize(attribute_definition)
|
29
|
+
define_method(:respond_to_missing?) do |method_name, include_private = false|
|
30
|
+
super(method_name, include_private) || attribute_definition.matches?(method_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
define_method(:method_missing) do |method_name, *args, &block|
|
34
|
+
if attribute_definition.matches?(method_name)
|
35
|
+
attribute_definition.define_on(self.class)
|
36
|
+
send(method_name, *args, &block)
|
37
|
+
else
|
38
|
+
super(method_name, *args, &block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class AttributeDefinition
|
45
|
+
def initialize(attributes)
|
46
|
+
@attributes = attributes.map(&:to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
def matches?(method_name)
|
50
|
+
attr_name = convert_to_reader_name(method_name)
|
51
|
+
attributes.include?(attr_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_on(klass)
|
55
|
+
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
|
56
|
+
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
|
57
|
+
klass.send(:attr_reader, *attr_readers)
|
58
|
+
klass.send(:attr_writer, *attr_writers)
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
62
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
63
|
+
protected
|
64
|
+
|
65
|
+
attr_reader :attributes
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def convert_to_reader_name(method_name)
|
70
|
+
method_name.to_s.chomp("=")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module HelperMethods
|
76
|
+
# Encapsulates the pattern of wanting to validate the acceptance of a
|
77
|
+
# terms of service check box (or similar agreement).
|
78
|
+
#
|
79
|
+
# class Person < ActiveRecord::Base
|
80
|
+
# validates_acceptance_of :terms_of_service
|
81
|
+
# validates_acceptance_of :eula, message: 'must be abided'
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# If the database column does not exist, the +terms_of_service+ attribute
|
85
|
+
# is entirely virtual. This check is performed only if +terms_of_service+
|
86
|
+
# is not +nil+ and by default on save.
|
87
|
+
#
|
88
|
+
# Configuration options:
|
89
|
+
# * <tt>:message</tt> - A custom error message (default is: "must be
|
90
|
+
# accepted").
|
91
|
+
# * <tt>:accept</tt> - Specifies a value that is considered accepted.
|
92
|
+
# Also accepts an array of possible values. The default value is
|
93
|
+
# an array ["1", true], which makes it easy to relate to an HTML
|
94
|
+
# checkbox. This should be set to, or include, +true+ if you are validating
|
95
|
+
# a database column, since the attribute is typecast from "1" to +true+
|
96
|
+
# before validation.
|
97
|
+
#
|
98
|
+
# There is also a list of default options supported by every validator:
|
99
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
100
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information.
|
101
|
+
def validates_acceptance_of(*attr_names)
|
102
|
+
validates_with AcceptanceValidator, _merge_attributes(attr_names)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
# == Active \Model \Validation \Callbacks
|
6
|
+
#
|
7
|
+
# Provides an interface for any class to have +before_validation+ and
|
8
|
+
# +after_validation+ callbacks.
|
9
|
+
#
|
10
|
+
# First, include ActiveModel::Validations::Callbacks from the class you are
|
11
|
+
# creating:
|
12
|
+
#
|
13
|
+
# class MyModel
|
14
|
+
# include ActiveModel::Validations::Callbacks
|
15
|
+
#
|
16
|
+
# before_validation :do_stuff_before_validation
|
17
|
+
# after_validation :do_stuff_after_validation
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Like other <tt>before_*</tt> callbacks if +before_validation+ throws
|
21
|
+
# +:abort+ then <tt>valid?</tt> will not be called.
|
22
|
+
module Callbacks
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
included do
|
26
|
+
include ActiveSupport::Callbacks
|
27
|
+
define_callbacks :validation,
|
28
|
+
skip_after_callbacks_if_terminated: true,
|
29
|
+
scope: [:kind, :name]
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
# Defines a callback that will get called right before validation.
|
34
|
+
#
|
35
|
+
# class Person
|
36
|
+
# include ActiveModel::Validations
|
37
|
+
# include ActiveModel::Validations::Callbacks
|
38
|
+
#
|
39
|
+
# attr_accessor :name
|
40
|
+
#
|
41
|
+
# validates_length_of :name, maximum: 6
|
42
|
+
#
|
43
|
+
# before_validation :remove_whitespaces
|
44
|
+
#
|
45
|
+
# private
|
46
|
+
#
|
47
|
+
# def remove_whitespaces
|
48
|
+
# name.strip!
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# person = Person.new
|
53
|
+
# person.name = ' bob '
|
54
|
+
# person.valid? # => true
|
55
|
+
# person.name # => "bob"
|
56
|
+
def before_validation(*args, &block)
|
57
|
+
options = args.extract_options!
|
58
|
+
|
59
|
+
if options.key?(:on)
|
60
|
+
options = options.dup
|
61
|
+
options[:on] = Array(options[:on])
|
62
|
+
options[:if] = Array(options[:if])
|
63
|
+
options[:if].unshift ->(o) {
|
64
|
+
!(options[:on] & Array(o.validation_context)).empty?
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
set_callback(:validation, :before, *args, options, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Defines a callback that will get called right after validation.
|
72
|
+
#
|
73
|
+
# class Person
|
74
|
+
# include ActiveModel::Validations
|
75
|
+
# include ActiveModel::Validations::Callbacks
|
76
|
+
#
|
77
|
+
# attr_accessor :name, :status
|
78
|
+
#
|
79
|
+
# validates_presence_of :name
|
80
|
+
#
|
81
|
+
# after_validation :set_status
|
82
|
+
#
|
83
|
+
# private
|
84
|
+
#
|
85
|
+
# def set_status
|
86
|
+
# self.status = errors.empty?
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# person = Person.new
|
91
|
+
# person.name = ''
|
92
|
+
# person.valid? # => false
|
93
|
+
# person.status # => false
|
94
|
+
# person.name = 'bob'
|
95
|
+
# person.valid? # => true
|
96
|
+
# person.status # => true
|
97
|
+
def after_validation(*args, &block)
|
98
|
+
options = args.extract_options!
|
99
|
+
options = options.dup
|
100
|
+
options[:prepend] = true
|
101
|
+
|
102
|
+
if options.key?(:on)
|
103
|
+
options[:on] = Array(options[:on])
|
104
|
+
options[:if] = Array(options[:if])
|
105
|
+
options[:if].unshift ->(o) {
|
106
|
+
!(options[:on] & Array(o.validation_context)).empty?
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
set_callback(:validation, :after, *args, options, &block)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Overwrite run validations to include callbacks.
|
117
|
+
def run_validations!
|
118
|
+
_run_validation_callbacks { super }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/range"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Validations
|
7
|
+
module Clusivity #:nodoc:
|
8
|
+
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
|
9
|
+
"and must be supplied as the :in (or :within) option of the configuration hash"
|
10
|
+
|
11
|
+
def check_validity!
|
12
|
+
unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
|
13
|
+
raise ArgumentError, ERROR_MESSAGE
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def include?(record, value)
|
20
|
+
members = if delimiter.respond_to?(:call)
|
21
|
+
delimiter.call(record)
|
22
|
+
elsif delimiter.respond_to?(:to_sym)
|
23
|
+
record.send(delimiter)
|
24
|
+
else
|
25
|
+
delimiter
|
26
|
+
end
|
27
|
+
|
28
|
+
members.send(inclusion_method(members), value)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delimiter
|
32
|
+
@delimiter ||= options[:in] || options[:within]
|
33
|
+
end
|
34
|
+
|
35
|
+
# In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
|
36
|
+
# possible values in the range for equality, which is slower but more accurate.
|
37
|
+
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
|
38
|
+
# endpoints, which is fast but is only accurate on Numeric, Time, Date,
|
39
|
+
# or DateTime ranges.
|
40
|
+
def inclusion_method(enumerable)
|
41
|
+
if enumerable.is_a? Range
|
42
|
+
case enumerable.first
|
43
|
+
when Numeric, Time, DateTime, Date
|
44
|
+
:cover?
|
45
|
+
else
|
46
|
+
:include?
|
47
|
+
end
|
48
|
+
else
|
49
|
+
:include?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class ConfirmationValidator < EachValidator # :nodoc:
|
6
|
+
def initialize(options)
|
7
|
+
super({ case_sensitive: true }.merge!(options))
|
8
|
+
setup!(options[:class])
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
unless (confirmed = record.send("#{attribute}_confirmation")).nil?
|
13
|
+
unless confirmation_value_equal?(record, attribute, value, confirmed)
|
14
|
+
human_attribute_name = record.class.human_attribute_name(attribute)
|
15
|
+
record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def setup!(klass)
|
22
|
+
klass.send(:attr_reader, *attributes.map do |attribute|
|
23
|
+
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
|
24
|
+
end.compact)
|
25
|
+
|
26
|
+
klass.send(:attr_writer, *attributes.map do |attribute|
|
27
|
+
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
|
28
|
+
end.compact)
|
29
|
+
end
|
30
|
+
|
31
|
+
def confirmation_value_equal?(record, attribute, value, confirmed)
|
32
|
+
if !options[:case_sensitive] && value.is_a?(String)
|
33
|
+
value.casecmp(confirmed) == 0
|
34
|
+
else
|
35
|
+
value == confirmed
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module HelperMethods
|
41
|
+
# Encapsulates the pattern of wanting to validate a password or email
|
42
|
+
# address field with a confirmation.
|
43
|
+
#
|
44
|
+
# Model:
|
45
|
+
# class Person < ActiveRecord::Base
|
46
|
+
# validates_confirmation_of :user_name, :password
|
47
|
+
# validates_confirmation_of :email_address,
|
48
|
+
# message: 'should match confirmation'
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# View:
|
52
|
+
# <%= password_field "person", "password" %>
|
53
|
+
# <%= password_field "person", "password_confirmation" %>
|
54
|
+
#
|
55
|
+
# The added +password_confirmation+ attribute is virtual; it exists only
|
56
|
+
# as an in-memory attribute for validating the password. To achieve this,
|
57
|
+
# the validation adds accessors to the model for the confirmation
|
58
|
+
# attribute.
|
59
|
+
#
|
60
|
+
# NOTE: This check is performed only if +password_confirmation+ is not
|
61
|
+
# +nil+. To require confirmation, make sure to add a presence check for
|
62
|
+
# the confirmation attribute:
|
63
|
+
#
|
64
|
+
# validates_presence_of :password_confirmation, if: :password_changed?
|
65
|
+
#
|
66
|
+
# Configuration options:
|
67
|
+
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
|
68
|
+
# <tt>%{translated_attribute_name}</tt>").
|
69
|
+
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
70
|
+
# non-text columns (+true+ by default).
|
71
|
+
#
|
72
|
+
# There is also a list of default options supported by every validator:
|
73
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
74
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
75
|
+
def validates_confirmation_of(*attr_names)
|
76
|
+
validates_with ConfirmationValidator, _merge_attributes(attr_names)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/validations/clusivity"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Validations
|
7
|
+
class ExclusionValidator < EachValidator # :nodoc:
|
8
|
+
include Clusivity
|
9
|
+
|
10
|
+
def validate_each(record, attribute, value)
|
11
|
+
if include?(record, value)
|
12
|
+
record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module HelperMethods
|
18
|
+
# Validates that the value of the specified attribute is not in a
|
19
|
+
# particular enumerable object.
|
20
|
+
#
|
21
|
+
# class Person < ActiveRecord::Base
|
22
|
+
# validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here"
|
23
|
+
# validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60'
|
24
|
+
# validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed"
|
25
|
+
# validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] },
|
26
|
+
# message: 'should not be the same as your username or first name'
|
27
|
+
# validates_exclusion_of :karma, in: :reserved_karmas
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# Configuration options:
|
31
|
+
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't
|
32
|
+
# be part of. This can be supplied as a proc, lambda or symbol which returns an
|
33
|
+
# enumerable. If the enumerable is a numerical, time or datetime range the test
|
34
|
+
# is performed with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When
|
35
|
+
# using a proc or lambda the instance under validation is passed as an argument.
|
36
|
+
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
|
37
|
+
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
|
38
|
+
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
|
39
|
+
# reserved").
|
40
|
+
#
|
41
|
+
# There is also a list of default options supported by every validator:
|
42
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
43
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
44
|
+
def validates_exclusion_of(*attr_names)
|
45
|
+
validates_with ExclusionValidator, _merge_attributes(attr_names)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|