tallty_duck_record 1.0.0
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/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
|