tallty_duck_record 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +41 -0
- data/README.md +82 -0
- data/Rakefile +28 -0
- data/lib/core_ext/array_without_blank.rb +46 -0
- data/lib/duck_record.rb +65 -0
- data/lib/duck_record/associations.rb +130 -0
- data/lib/duck_record/associations/association.rb +271 -0
- data/lib/duck_record/associations/belongs_to_association.rb +71 -0
- data/lib/duck_record/associations/builder/association.rb +127 -0
- data/lib/duck_record/associations/builder/belongs_to.rb +44 -0
- data/lib/duck_record/associations/builder/collection_association.rb +45 -0
- data/lib/duck_record/associations/builder/embeds_many.rb +9 -0
- data/lib/duck_record/associations/builder/embeds_one.rb +9 -0
- data/lib/duck_record/associations/builder/has_many.rb +11 -0
- data/lib/duck_record/associations/builder/has_one.rb +20 -0
- data/lib/duck_record/associations/builder/singular_association.rb +33 -0
- data/lib/duck_record/associations/collection_association.rb +476 -0
- data/lib/duck_record/associations/collection_proxy.rb +1160 -0
- data/lib/duck_record/associations/embeds_association.rb +92 -0
- data/lib/duck_record/associations/embeds_many_association.rb +203 -0
- data/lib/duck_record/associations/embeds_many_proxy.rb +892 -0
- data/lib/duck_record/associations/embeds_one_association.rb +48 -0
- data/lib/duck_record/associations/foreign_association.rb +11 -0
- data/lib/duck_record/associations/has_many_association.rb +17 -0
- data/lib/duck_record/associations/has_one_association.rb +39 -0
- data/lib/duck_record/associations/singular_association.rb +73 -0
- data/lib/duck_record/attribute.rb +213 -0
- data/lib/duck_record/attribute/user_provided_default.rb +30 -0
- data/lib/duck_record/attribute_assignment.rb +118 -0
- data/lib/duck_record/attribute_decorators.rb +89 -0
- data/lib/duck_record/attribute_methods.rb +325 -0
- data/lib/duck_record/attribute_methods/before_type_cast.rb +76 -0
- data/lib/duck_record/attribute_methods/dirty.rb +107 -0
- data/lib/duck_record/attribute_methods/read.rb +78 -0
- data/lib/duck_record/attribute_methods/serialization.rb +66 -0
- data/lib/duck_record/attribute_methods/write.rb +70 -0
- data/lib/duck_record/attribute_mutation_tracker.rb +108 -0
- data/lib/duck_record/attribute_set.rb +98 -0
- data/lib/duck_record/attribute_set/yaml_encoder.rb +41 -0
- data/lib/duck_record/attributes.rb +262 -0
- data/lib/duck_record/base.rb +300 -0
- data/lib/duck_record/callbacks.rb +324 -0
- data/lib/duck_record/coders/json.rb +13 -0
- data/lib/duck_record/coders/yaml_column.rb +48 -0
- data/lib/duck_record/core.rb +262 -0
- data/lib/duck_record/define_callbacks.rb +23 -0
- data/lib/duck_record/enum.rb +139 -0
- data/lib/duck_record/errors.rb +71 -0
- data/lib/duck_record/inheritance.rb +130 -0
- data/lib/duck_record/locale/en.yml +46 -0
- data/lib/duck_record/model_schema.rb +71 -0
- data/lib/duck_record/nested_attributes.rb +555 -0
- data/lib/duck_record/nested_validate_association.rb +262 -0
- data/lib/duck_record/persistence.rb +39 -0
- data/lib/duck_record/readonly_attributes.rb +36 -0
- data/lib/duck_record/reflection.rb +650 -0
- data/lib/duck_record/serialization.rb +26 -0
- data/lib/duck_record/translation.rb +22 -0
- data/lib/duck_record/type.rb +77 -0
- data/lib/duck_record/type/array.rb +36 -0
- data/lib/duck_record/type/array_without_blank.rb +36 -0
- data/lib/duck_record/type/date.rb +7 -0
- data/lib/duck_record/type/date_time.rb +7 -0
- data/lib/duck_record/type/decimal_without_scale.rb +13 -0
- data/lib/duck_record/type/internal/abstract_json.rb +33 -0
- data/lib/duck_record/type/internal/timezone.rb +15 -0
- data/lib/duck_record/type/json.rb +6 -0
- data/lib/duck_record/type/registry.rb +97 -0
- data/lib/duck_record/type/serialized.rb +63 -0
- data/lib/duck_record/type/text.rb +9 -0
- data/lib/duck_record/type/time.rb +19 -0
- data/lib/duck_record/type/unsigned_integer.rb +15 -0
- data/lib/duck_record/validations.rb +67 -0
- data/lib/duck_record/validations/subset.rb +74 -0
- data/lib/duck_record/validations/uniqueness_on_real_record.rb +248 -0
- data/lib/duck_record/version.rb +3 -0
- data/lib/tasks/acts_as_record_tasks.rake +4 -0
- metadata +181 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Associations
|
3
|
+
class EmbedsOneAssociation < EmbedsAssociation #:nodoc:
|
4
|
+
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
|
+
def reader
|
6
|
+
target
|
7
|
+
end
|
8
|
+
|
9
|
+
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
10
|
+
def writer(record)
|
11
|
+
replace(record)
|
12
|
+
end
|
13
|
+
|
14
|
+
def build(attributes = {})
|
15
|
+
record = build_record(attributes)
|
16
|
+
yield(record) if block_given?
|
17
|
+
set_new_record(record)
|
18
|
+
record
|
19
|
+
end
|
20
|
+
|
21
|
+
# Implements the reload reader method, e.g. foo.reload_bar for
|
22
|
+
# Foo.has_one :bar
|
23
|
+
def force_reload_reader
|
24
|
+
klass.uncached { reload }
|
25
|
+
target
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def replace(record)
|
31
|
+
self.target =
|
32
|
+
if record.is_a? klass
|
33
|
+
record
|
34
|
+
elsif record.nil?
|
35
|
+
nil
|
36
|
+
elsif record.respond_to?(:to_h)
|
37
|
+
build_record(record.to_h)
|
38
|
+
end
|
39
|
+
rescue
|
40
|
+
raise_on_type_mismatch!(record)
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_new_record(record)
|
44
|
+
replace(record)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
# = Active Record Has Many Association
|
3
|
+
module Associations
|
4
|
+
# This is the proxy that handles a has many association.
|
5
|
+
#
|
6
|
+
# If the association has a <tt>:through</tt> option further specialization
|
7
|
+
# is provided by its child HasManyThroughAssociation.
|
8
|
+
class HasManyAssociation < CollectionAssociation #:nodoc:
|
9
|
+
include ForeignAssociation
|
10
|
+
|
11
|
+
def insert_record(record, validate = true, raise = false)
|
12
|
+
set_owner_attributes(record)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
# = Active Record Has One Association
|
3
|
+
module Associations
|
4
|
+
class HasOneAssociation < SingularAssociation #:nodoc:
|
5
|
+
include ForeignAssociation
|
6
|
+
|
7
|
+
def replace(record)
|
8
|
+
if owner.class.readonly_attributes.include?(reflection.foreign_key.to_s)
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
raise_on_type_mismatch!(record) if record
|
13
|
+
load_target
|
14
|
+
|
15
|
+
return target unless target || record
|
16
|
+
|
17
|
+
self.target = record
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def foreign_key_present?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
# The reason that the save param for replace is false, if for create (not just build),
|
27
|
+
# is because the setting of the foreign keys is actually handled by the scoping when
|
28
|
+
# the record is instantiated, and so they are set straight away and do not need to be
|
29
|
+
# updated within replace.
|
30
|
+
def set_new_record(record)
|
31
|
+
replace(record)
|
32
|
+
end
|
33
|
+
|
34
|
+
def nullify_owner_attributes(record)
|
35
|
+
record[reflection.foreign_key] = nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Associations
|
3
|
+
class SingularAssociation < Association #:nodoc:
|
4
|
+
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
|
+
def reader
|
6
|
+
if !loaded? || stale_target?
|
7
|
+
reload
|
8
|
+
end
|
9
|
+
|
10
|
+
target
|
11
|
+
end
|
12
|
+
|
13
|
+
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
14
|
+
def writer(record)
|
15
|
+
replace(record)
|
16
|
+
end
|
17
|
+
|
18
|
+
def build(attributes = {})
|
19
|
+
record = build_record(attributes)
|
20
|
+
yield(record) if block_given?
|
21
|
+
set_new_record(record)
|
22
|
+
record
|
23
|
+
end
|
24
|
+
|
25
|
+
# Implements the reload reader method, e.g. foo.reload_bar for
|
26
|
+
# Foo.has_one :bar
|
27
|
+
def force_reload_reader
|
28
|
+
klass.uncached { reload }
|
29
|
+
target
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def create_scope
|
35
|
+
scope.scope_for_create.stringify_keys.except(klass.primary_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_target
|
39
|
+
return scope.take if skip_statement_cache?
|
40
|
+
|
41
|
+
conn = klass.connection
|
42
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
43
|
+
ActiveRecord::StatementCache.create(conn) { |params|
|
44
|
+
as = ActiveRecord::Associations::AssociationScope.create { params.bind }
|
45
|
+
target_scope.merge(as.scope(self, conn)).limit(1)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
binds = ActiveRecord::Associations::AssociationScope.get_bind_values(owner, reflection.chain)
|
50
|
+
sc.execute(binds, klass, conn).first
|
51
|
+
rescue ::RangeError
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def replace(record)
|
56
|
+
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_new_record(record)
|
60
|
+
replace(record)
|
61
|
+
end
|
62
|
+
|
63
|
+
def _create_record(attributes, raise_error = false)
|
64
|
+
record = build_record(attributes)
|
65
|
+
yield(record) if block_given?
|
66
|
+
saved = record.save
|
67
|
+
set_new_record(record)
|
68
|
+
raise ActiveRecord::RecordInvalid.new(record) if !saved && raise_error
|
69
|
+
record
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
class Attribute # :nodoc:
|
3
|
+
class << self
|
4
|
+
def from_database(name, value, type)
|
5
|
+
FromDatabase.new(name, value, type)
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_user(name, value, type, original_attribute = nil)
|
9
|
+
FromUser.new(name, value, type, original_attribute)
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_cast_value(name, value, type)
|
13
|
+
WithCastValue.new(name, value, type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def null(name)
|
17
|
+
Null.new(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def uninitialized(name, type)
|
21
|
+
Uninitialized.new(name, type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :name, :value_before_type_cast, :type
|
26
|
+
|
27
|
+
# This method should not be called directly.
|
28
|
+
# Use #from_database or #from_user
|
29
|
+
def initialize(name, value_before_type_cast, type, original_attribute = nil)
|
30
|
+
@name = name
|
31
|
+
@value_before_type_cast = value_before_type_cast
|
32
|
+
@type = type
|
33
|
+
@original_attribute = original_attribute
|
34
|
+
end
|
35
|
+
|
36
|
+
def value
|
37
|
+
# `defined?` is cheaper than `||=` when we get back falsy values
|
38
|
+
@value = type_cast(value_before_type_cast) unless defined?(@value)
|
39
|
+
@value
|
40
|
+
end
|
41
|
+
|
42
|
+
def original_value
|
43
|
+
if assigned?
|
44
|
+
original_attribute.original_value
|
45
|
+
else
|
46
|
+
type_cast(value_before_type_cast)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def value_for_database
|
51
|
+
type.serialize(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def changed?
|
55
|
+
changed_from_assignment? || changed_in_place?
|
56
|
+
end
|
57
|
+
|
58
|
+
def changed_in_place?
|
59
|
+
has_been_read? && type.changed_in_place?(original_value_for_database, value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def forgetting_assignment
|
63
|
+
with_value_from_database(value_for_database)
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_value_from_user(value)
|
67
|
+
type.assert_valid_value(value)
|
68
|
+
self.class.from_user(name, value, type, original_attribute || self)
|
69
|
+
end
|
70
|
+
|
71
|
+
def with_value_from_database(value)
|
72
|
+
self.class.from_database(name, value, type)
|
73
|
+
end
|
74
|
+
|
75
|
+
def with_cast_value(value)
|
76
|
+
self.class.with_cast_value(name, value, type)
|
77
|
+
end
|
78
|
+
|
79
|
+
def with_type(type)
|
80
|
+
self.class.new(name, value_before_type_cast, type, original_attribute)
|
81
|
+
end
|
82
|
+
|
83
|
+
def type_cast(*)
|
84
|
+
raise NotImplementedError
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialized?
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def came_from_user?
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_been_read?
|
96
|
+
defined?(@value)
|
97
|
+
end
|
98
|
+
|
99
|
+
def ==(other)
|
100
|
+
self.class == other.class &&
|
101
|
+
name == other.name &&
|
102
|
+
value_before_type_cast == other.value_before_type_cast &&
|
103
|
+
type == other.type
|
104
|
+
end
|
105
|
+
alias eql? ==
|
106
|
+
|
107
|
+
def hash
|
108
|
+
[self.class, name, value_before_type_cast, type].hash
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
attr_reader :original_attribute
|
114
|
+
alias_method :assigned?, :original_attribute
|
115
|
+
|
116
|
+
def initialize_dup(other)
|
117
|
+
if defined?(@value) && @value.duplicable?
|
118
|
+
@value = @value.dup
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def changed_from_assignment?
|
123
|
+
assigned? && type.changed?(original_value, value, value_before_type_cast)
|
124
|
+
end
|
125
|
+
|
126
|
+
def original_value_for_database
|
127
|
+
if assigned?
|
128
|
+
original_attribute.original_value_for_database
|
129
|
+
else
|
130
|
+
_original_value_for_database
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def _original_value_for_database
|
135
|
+
type.serialize(original_value)
|
136
|
+
end
|
137
|
+
|
138
|
+
class FromDatabase < Attribute # :nodoc:
|
139
|
+
def type_cast(value)
|
140
|
+
type.deserialize(value)
|
141
|
+
end
|
142
|
+
|
143
|
+
def _original_value_for_database
|
144
|
+
value_before_type_cast
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class FromUser < Attribute # :nodoc:
|
149
|
+
def type_cast(value)
|
150
|
+
type.cast(value)
|
151
|
+
end
|
152
|
+
|
153
|
+
def came_from_user?
|
154
|
+
true
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class WithCastValue < Attribute # :nodoc:
|
159
|
+
def type_cast(value)
|
160
|
+
value
|
161
|
+
end
|
162
|
+
|
163
|
+
def changed_in_place?
|
164
|
+
false
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class Null < Attribute # :nodoc:
|
169
|
+
def initialize(name)
|
170
|
+
super(name, nil, Type::Value.new)
|
171
|
+
end
|
172
|
+
|
173
|
+
def type_cast(*)
|
174
|
+
nil
|
175
|
+
end
|
176
|
+
|
177
|
+
def with_type(type)
|
178
|
+
self.class.with_cast_value(name, nil, type)
|
179
|
+
end
|
180
|
+
|
181
|
+
def with_value_from_database(value)
|
182
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
|
183
|
+
end
|
184
|
+
alias_method :with_value_from_user, :with_value_from_database
|
185
|
+
end
|
186
|
+
|
187
|
+
class Uninitialized < Attribute # :nodoc:
|
188
|
+
UNINITIALIZED_ORIGINAL_VALUE = Object.new
|
189
|
+
|
190
|
+
def initialize(name, type)
|
191
|
+
super(name, nil, type)
|
192
|
+
end
|
193
|
+
|
194
|
+
def value
|
195
|
+
if block_given?
|
196
|
+
yield name
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def original_value
|
201
|
+
UNINITIALIZED_ORIGINAL_VALUE
|
202
|
+
end
|
203
|
+
|
204
|
+
def value_for_database
|
205
|
+
end
|
206
|
+
|
207
|
+
def initialized?
|
208
|
+
false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "duck_record/attribute"
|
2
|
+
|
3
|
+
module DuckRecord
|
4
|
+
class Attribute # :nodoc:
|
5
|
+
class UserProvidedDefault < FromUser # :nodoc:
|
6
|
+
def initialize(name, value, type, default)
|
7
|
+
@user_provided_value = value
|
8
|
+
super(name, value, type, default)
|
9
|
+
end
|
10
|
+
|
11
|
+
def value_before_type_cast
|
12
|
+
if user_provided_value.is_a?(Proc)
|
13
|
+
@memoized_value_before_type_cast ||= user_provided_value.call
|
14
|
+
else
|
15
|
+
@user_provided_value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_type(type)
|
20
|
+
self.class.new(name, user_provided_value, type, original_attribute)
|
21
|
+
end
|
22
|
+
|
23
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
24
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
25
|
+
protected
|
26
|
+
|
27
|
+
attr_reader :user_provided_value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require "active_support/core_ext/hash/keys"
|
2
|
+
require "active_model/forbidden_attributes_protection"
|
3
|
+
|
4
|
+
module DuckRecord
|
5
|
+
module AttributeAssignment
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include ActiveModel::ForbiddenAttributesProtection
|
8
|
+
|
9
|
+
# Alias for ActiveModel::AttributeAssignment#assign_attributes. See ActiveModel::AttributeAssignment.
|
10
|
+
def attributes=(attributes)
|
11
|
+
assign_attributes(attributes)
|
12
|
+
end
|
13
|
+
|
14
|
+
def assign_attributes(new_attributes)
|
15
|
+
unless new_attributes.respond_to?(:stringify_keys)
|
16
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
17
|
+
end
|
18
|
+
return if new_attributes.nil? || new_attributes.empty?
|
19
|
+
|
20
|
+
attributes = new_attributes.stringify_keys
|
21
|
+
_assign_attributes(sanitize_for_mass_assignment(attributes))
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def _assign_attributes(attributes)
|
27
|
+
multi_parameter_attributes = {}
|
28
|
+
nested_parameter_attributes = {}
|
29
|
+
|
30
|
+
attributes.each do |k, v|
|
31
|
+
if k.include?("(")
|
32
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
33
|
+
elsif v.is_a?(Hash)
|
34
|
+
nested_parameter_attributes[k] = attributes.delete(k)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attributes.each do |k, v|
|
39
|
+
_assign_attribute(k, v)
|
40
|
+
end
|
41
|
+
|
42
|
+
unless nested_parameter_attributes.empty?
|
43
|
+
assign_nested_parameter_attributes(nested_parameter_attributes)
|
44
|
+
end
|
45
|
+
|
46
|
+
unless multi_parameter_attributes.empty?
|
47
|
+
assign_multiparameter_attributes(multi_parameter_attributes)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
52
|
+
def assign_nested_parameter_attributes(pairs)
|
53
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
57
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
58
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
59
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
60
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
61
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
62
|
+
def assign_multiparameter_attributes(pairs)
|
63
|
+
execute_callstack_for_multiparameter_attributes(
|
64
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
69
|
+
errors = []
|
70
|
+
callstack.each do |name, values_with_empty_parameters|
|
71
|
+
begin
|
72
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
73
|
+
values = nil
|
74
|
+
else
|
75
|
+
values = values_with_empty_parameters
|
76
|
+
end
|
77
|
+
send("#{name}=", values)
|
78
|
+
rescue => ex
|
79
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
unless errors.empty?
|
83
|
+
error_descriptions = errors.map(&:message).join(",")
|
84
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
89
|
+
attributes = {}
|
90
|
+
|
91
|
+
pairs.each do |(multiparameter_name, value)|
|
92
|
+
attribute_name = multiparameter_name.split("(").first
|
93
|
+
attributes[attribute_name] ||= {}
|
94
|
+
|
95
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
96
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
97
|
+
end
|
98
|
+
|
99
|
+
attributes
|
100
|
+
end
|
101
|
+
|
102
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
103
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_parameter_position(multiparameter_name)
|
107
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
108
|
+
end
|
109
|
+
|
110
|
+
def _assign_attribute(k, v)
|
111
|
+
if respond_to?("#{k}=")
|
112
|
+
public_send("#{k}=", v)
|
113
|
+
else
|
114
|
+
raise UnknownAttributeError.new(self, k)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|