activemodel 5.2.3
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 +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,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
class AttributeMutationTracker # :nodoc:
|
7
|
+
OPTION_NOT_GIVEN = Object.new
|
8
|
+
|
9
|
+
def initialize(attributes)
|
10
|
+
@attributes = attributes
|
11
|
+
@forced_changes = Set.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def changed_attribute_names
|
15
|
+
attr_names.select { |attr_name| changed?(attr_name) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def changed_values
|
19
|
+
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
20
|
+
if changed?(attr_name)
|
21
|
+
result[attr_name] = attributes[attr_name].original_value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def changes
|
27
|
+
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
28
|
+
change = change_to_attribute(attr_name)
|
29
|
+
if change
|
30
|
+
result.merge!(attr_name => change)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def change_to_attribute(attr_name)
|
36
|
+
attr_name = attr_name.to_s
|
37
|
+
if changed?(attr_name)
|
38
|
+
[attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def any_changes?
|
43
|
+
attr_names.any? { |attr| changed?(attr) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
47
|
+
attr_name = attr_name.to_s
|
48
|
+
forced_changes.include?(attr_name) ||
|
49
|
+
attributes[attr_name].changed? &&
|
50
|
+
(OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
|
51
|
+
(OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
|
52
|
+
end
|
53
|
+
|
54
|
+
def changed_in_place?(attr_name)
|
55
|
+
attributes[attr_name.to_s].changed_in_place?
|
56
|
+
end
|
57
|
+
|
58
|
+
def forget_change(attr_name)
|
59
|
+
attr_name = attr_name.to_s
|
60
|
+
attributes[attr_name] = attributes[attr_name].forgetting_assignment
|
61
|
+
forced_changes.delete(attr_name)
|
62
|
+
end
|
63
|
+
|
64
|
+
def original_value(attr_name)
|
65
|
+
attributes[attr_name.to_s].original_value
|
66
|
+
end
|
67
|
+
|
68
|
+
def force_change(attr_name)
|
69
|
+
forced_changes << attr_name.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
73
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
74
|
+
protected
|
75
|
+
|
76
|
+
attr_reader :attributes, :forced_changes
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def attr_names
|
81
|
+
attributes.keys
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class NullMutationTracker # :nodoc:
|
86
|
+
include Singleton
|
87
|
+
|
88
|
+
def changed_attribute_names(*)
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
|
92
|
+
def changed_values(*)
|
93
|
+
{}
|
94
|
+
end
|
95
|
+
|
96
|
+
def changes(*)
|
97
|
+
{}
|
98
|
+
end
|
99
|
+
|
100
|
+
def change_to_attribute(attr_name)
|
101
|
+
end
|
102
|
+
|
103
|
+
def any_changes?(*)
|
104
|
+
false
|
105
|
+
end
|
106
|
+
|
107
|
+
def changed?(*)
|
108
|
+
false
|
109
|
+
end
|
110
|
+
|
111
|
+
def changed_in_place?(*)
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
def forget_change(*)
|
116
|
+
end
|
117
|
+
|
118
|
+
def original_value(*)
|
119
|
+
end
|
120
|
+
|
121
|
+
def force_change(*)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/deep_dup"
|
4
|
+
require "active_model/attribute_set/builder"
|
5
|
+
require "active_model/attribute_set/yaml_encoder"
|
6
|
+
|
7
|
+
module ActiveModel
|
8
|
+
class AttributeSet # :nodoc:
|
9
|
+
delegate :each_value, :fetch, :except, to: :attributes
|
10
|
+
|
11
|
+
def initialize(attributes)
|
12
|
+
@attributes = attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](name)
|
16
|
+
attributes[name] || Attribute.null(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(name, value)
|
20
|
+
attributes[name] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def values_before_type_cast
|
24
|
+
attributes.transform_values(&:value_before_type_cast)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
initialized_attributes.transform_values(&:value)
|
29
|
+
end
|
30
|
+
alias_method :to_h, :to_hash
|
31
|
+
|
32
|
+
def key?(name)
|
33
|
+
attributes.key?(name) && self[name].initialized?
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
attributes.each_key.select { |name| self[name].initialized? }
|
38
|
+
end
|
39
|
+
|
40
|
+
if defined?(JRUBY_VERSION)
|
41
|
+
# This form is significantly faster on JRuby, and this is one of our biggest hotspots.
|
42
|
+
# https://github.com/jruby/jruby/pull/2562
|
43
|
+
def fetch_value(name, &block)
|
44
|
+
self[name].value(&block)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
def fetch_value(name)
|
48
|
+
self[name].value { |n| yield n if block_given? }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def write_from_database(name, value)
|
53
|
+
attributes[name] = self[name].with_value_from_database(value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_from_user(name, value)
|
57
|
+
attributes[name] = self[name].with_value_from_user(value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_cast_value(name, value)
|
61
|
+
attributes[name] = self[name].with_cast_value(value)
|
62
|
+
end
|
63
|
+
|
64
|
+
def freeze
|
65
|
+
@attributes.freeze
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
def deep_dup
|
70
|
+
self.class.allocate.tap do |copy|
|
71
|
+
copy.instance_variable_set(:@attributes, attributes.deep_dup)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize_dup(_)
|
76
|
+
@attributes = attributes.dup
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize_clone(_)
|
81
|
+
@attributes = attributes.clone
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
def reset(key)
|
86
|
+
if key?(key)
|
87
|
+
write_from_database(key, nil)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def accessed
|
92
|
+
attributes.select { |_, attr| attr.has_been_read? }.keys
|
93
|
+
end
|
94
|
+
|
95
|
+
def map(&block)
|
96
|
+
new_attributes = attributes.transform_values(&block)
|
97
|
+
AttributeSet.new(new_attributes)
|
98
|
+
end
|
99
|
+
|
100
|
+
def ==(other)
|
101
|
+
attributes == other.attributes
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
attr_reader :attributes
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def initialized_attributes
|
111
|
+
attributes.select { |_, attr| attr.initialized? }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
class AttributeSet # :nodoc:
|
7
|
+
class Builder # :nodoc:
|
8
|
+
attr_reader :types, :default_attributes
|
9
|
+
|
10
|
+
def initialize(types, default_attributes = {})
|
11
|
+
@types = types
|
12
|
+
@default_attributes = default_attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_from_database(values = {}, additional_types = {})
|
16
|
+
attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
|
17
|
+
AttributeSet.new(attributes)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class LazyAttributeHash # :nodoc:
|
23
|
+
delegate :transform_values, :each_key, :each_value, :fetch, :except, to: :materialize
|
24
|
+
|
25
|
+
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
26
|
+
@types = types
|
27
|
+
@values = values
|
28
|
+
@additional_types = additional_types
|
29
|
+
@materialized = false
|
30
|
+
@delegate_hash = delegate_hash
|
31
|
+
@default_attributes = default_attributes
|
32
|
+
end
|
33
|
+
|
34
|
+
def key?(key)
|
35
|
+
delegate_hash.key?(key) || values.key?(key) || types.key?(key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](key)
|
39
|
+
delegate_hash[key] || assign_default_value(key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def []=(key, value)
|
43
|
+
if frozen?
|
44
|
+
raise RuntimeError, "Can't modify frozen hash"
|
45
|
+
end
|
46
|
+
delegate_hash[key] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def deep_dup
|
50
|
+
dup.tap do |copy|
|
51
|
+
copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize_dup(_)
|
56
|
+
@delegate_hash = Hash[delegate_hash]
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
def select
|
61
|
+
keys = types.keys | values.keys | delegate_hash.keys
|
62
|
+
keys.each_with_object({}) do |key, hash|
|
63
|
+
attribute = self[key]
|
64
|
+
if yield(key, attribute)
|
65
|
+
hash[key] = attribute
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def ==(other)
|
71
|
+
if other.is_a?(LazyAttributeHash)
|
72
|
+
materialize == other.materialize
|
73
|
+
else
|
74
|
+
materialize == other
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def marshal_dump
|
79
|
+
[@types, @values, @additional_types, @default_attributes, @delegate_hash]
|
80
|
+
end
|
81
|
+
|
82
|
+
def marshal_load(values)
|
83
|
+
if values.is_a?(Hash)
|
84
|
+
empty_hash = {}.freeze
|
85
|
+
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
86
|
+
@materialized = true
|
87
|
+
else
|
88
|
+
initialize(*values)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
95
|
+
|
96
|
+
def materialize
|
97
|
+
unless @materialized
|
98
|
+
values.each_key { |key| self[key] }
|
99
|
+
types.each_key { |key| self[key] }
|
100
|
+
unless frozen?
|
101
|
+
@materialized = true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
delegate_hash
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def assign_default_value(name)
|
110
|
+
type = additional_types.fetch(name, types[name])
|
111
|
+
value_present = true
|
112
|
+
value = values.fetch(name) { value_present = false }
|
113
|
+
|
114
|
+
if value_present
|
115
|
+
delegate_hash[name] = Attribute.from_database(name, value, type)
|
116
|
+
elsif types.key?(name)
|
117
|
+
attr = default_attributes[name]
|
118
|
+
if attr
|
119
|
+
delegate_hash[name] = attr.dup
|
120
|
+
else
|
121
|
+
delegate_hash[name] = Attribute.uninitialized(name, type)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class AttributeSet
|
5
|
+
# Attempts to do more intelligent YAML dumping of an
|
6
|
+
# ActiveModel::AttributeSet to reduce the size of the resulting string
|
7
|
+
class YAMLEncoder # :nodoc:
|
8
|
+
def initialize(default_types)
|
9
|
+
@default_types = default_types
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode(attribute_set, coder)
|
13
|
+
coder["concise_attributes"] = attribute_set.each_value.map do |attr|
|
14
|
+
if attr.type.equal?(default_types[attr.name])
|
15
|
+
attr.with_type(nil)
|
16
|
+
else
|
17
|
+
attr
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def decode(coder)
|
23
|
+
if coder["attributes"]
|
24
|
+
coder["attributes"]
|
25
|
+
else
|
26
|
+
attributes_hash = Hash[coder["concise_attributes"].map do |attr|
|
27
|
+
if attr.type.nil?
|
28
|
+
attr = attr.with_type(default_types[attr.name])
|
29
|
+
end
|
30
|
+
[attr.name, attr]
|
31
|
+
end]
|
32
|
+
AttributeSet.new(attributes_hash)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
attr_reader :default_types
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute_set"
|
4
|
+
require "active_model/attribute/user_provided_default"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
module Attributes #:nodoc:
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
include ActiveModel::AttributeMethods
|
10
|
+
|
11
|
+
included do
|
12
|
+
attribute_method_suffix "="
|
13
|
+
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
14
|
+
self.attribute_types = Hash.new(Type.default_value)
|
15
|
+
self._default_attributes = AttributeSet.new({})
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def attribute(name, type = Type::Value.new, **options)
|
20
|
+
name = name.to_s
|
21
|
+
if type.is_a?(Symbol)
|
22
|
+
type = ActiveModel::Type.lookup(type, **options.except(:default))
|
23
|
+
end
|
24
|
+
self.attribute_types = attribute_types.merge(name => type)
|
25
|
+
define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
|
26
|
+
define_attribute_method(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def define_method_attribute=(name)
|
32
|
+
safe_name = name.unpack("h*".freeze).first
|
33
|
+
ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
34
|
+
|
35
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
36
|
+
def __temp__#{safe_name}=(value)
|
37
|
+
name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
38
|
+
write_attribute(name, value)
|
39
|
+
end
|
40
|
+
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
41
|
+
undef_method :__temp__#{safe_name}=
|
42
|
+
STR
|
43
|
+
end
|
44
|
+
|
45
|
+
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
46
|
+
private_constant :NO_DEFAULT_PROVIDED
|
47
|
+
|
48
|
+
def define_default_attribute(name, value, type)
|
49
|
+
self._default_attributes = _default_attributes.deep_dup
|
50
|
+
if value == NO_DEFAULT_PROVIDED
|
51
|
+
default_attribute = _default_attributes[name].with_type(type)
|
52
|
+
else
|
53
|
+
default_attribute = Attribute::UserProvidedDefault.new(
|
54
|
+
name,
|
55
|
+
value,
|
56
|
+
type,
|
57
|
+
_default_attributes.fetch(name.to_s) { nil },
|
58
|
+
)
|
59
|
+
end
|
60
|
+
_default_attributes[name] = default_attribute
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(*)
|
65
|
+
@attributes = self.class._default_attributes.deep_dup
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
def attributes
|
70
|
+
@attributes.to_hash
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def write_attribute(attr_name, value)
|
76
|
+
name = if self.class.attribute_alias?(attr_name)
|
77
|
+
self.class.attribute_alias(attr_name).to_s
|
78
|
+
else
|
79
|
+
attr_name.to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
@attributes.write_from_user(name, value)
|
83
|
+
value
|
84
|
+
end
|
85
|
+
|
86
|
+
def attribute(attr_name)
|
87
|
+
name = if self.class.attribute_alias?(attr_name)
|
88
|
+
self.class.attribute_alias(attr_name).to_s
|
89
|
+
else
|
90
|
+
attr_name.to_s
|
91
|
+
end
|
92
|
+
@attributes.fetch_value(name)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Handle *= for method_missing.
|
96
|
+
def attribute=(attribute_name, value)
|
97
|
+
write_attribute(attribute_name, value)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module AttributeMethods #:nodoc:
|
102
|
+
AttrNames = Module.new {
|
103
|
+
def self.set_name_cache(name, value)
|
104
|
+
const_name = "ATTR_#{name}"
|
105
|
+
unless const_defined? const_name
|
106
|
+
const_set const_name, value.dup.freeze
|
107
|
+
end
|
108
|
+
end
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end
|