acts_as_table 0.0.1
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/.yardopts +1 -0
- data/LICENSE +30 -0
- data/README.md +256 -0
- data/Rakefile +13 -0
- data/app/models/acts_as_table/belongs_to.rb +81 -0
- data/app/models/acts_as_table/column_model.rb +89 -0
- data/app/models/acts_as_table/foreign_key.rb +299 -0
- data/app/models/acts_as_table/foreign_key_map.rb +121 -0
- data/app/models/acts_as_table/has_many.rb +90 -0
- data/app/models/acts_as_table/has_many_target.rb +40 -0
- data/app/models/acts_as_table/lense.rb +131 -0
- data/app/models/acts_as_table/primary_key.rb +108 -0
- data/app/models/acts_as_table/record.rb +109 -0
- data/app/models/acts_as_table/record_error.rb +50 -0
- data/app/models/acts_as_table/record_model.rb +194 -0
- data/app/models/acts_as_table/row_model.rb +285 -0
- data/app/models/acts_as_table/table.rb +41 -0
- data/app/models/acts_as_table/value.rb +80 -0
- data/app/models/concerns/acts_as_table/record_model_class_methods.rb +554 -0
- data/app/models/concerns/acts_as_table/value_provider.rb +223 -0
- data/app/models/concerns/acts_as_table/value_provider_association_methods.rb +57 -0
- data/config/locales/en.yml +8 -0
- data/db/migrate/1_acts_as_table_migration.rb +186 -0
- data/lib/acts_as_table.rb +288 -0
- data/lib/acts_as_table/adapter.rb +81 -0
- data/lib/acts_as_table/engine.rb +14 -0
- data/lib/acts_as_table/headers.rb +196 -0
- data/lib/acts_as_table/mapper.rb +464 -0
- data/lib/acts_as_table/path.rb +157 -0
- data/lib/acts_as_table/reader.rb +149 -0
- data/lib/acts_as_table/version.rb +4 -0
- data/lib/acts_as_table/writer.rb +116 -0
- metadata +181 -0
@@ -0,0 +1,285 @@
|
|
1
|
+
module ActsAsTable
|
2
|
+
# ActsAsTable row model (value provider).
|
3
|
+
#
|
4
|
+
# @!attribute [rw] name
|
5
|
+
# Returns the name of this ActsAsTable row model.
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
class RowModel < ::ActiveRecord::Base
|
9
|
+
# @!parse
|
10
|
+
# include ActsAsTable::ValueProvider
|
11
|
+
# include ActsAsTable::ValueProvider::InstanceMethods
|
12
|
+
# include ActsAsTable::ValueProviderAssociationMethods
|
13
|
+
|
14
|
+
self.table_name = ActsAsTable.row_models_table
|
15
|
+
|
16
|
+
acts_as_table_value_provider
|
17
|
+
|
18
|
+
# Returns the root ActsAsTable record model for this ActsAsTable row model.
|
19
|
+
belongs_to :root_record_model, **{
|
20
|
+
class_name: 'ActsAsTable::RecordModel',
|
21
|
+
inverse_of: :row_models_as_root,
|
22
|
+
required: true,
|
23
|
+
}
|
24
|
+
|
25
|
+
# Returns the ActsAsTable column models for this ActsAsTable row model.
|
26
|
+
has_many :column_models, -> { order(position: :asc) }, **{
|
27
|
+
autosave: true,
|
28
|
+
class_name: 'ActsAsTable::ColumnModel',
|
29
|
+
dependent: :destroy,
|
30
|
+
foreign_key: 'row_model_id',
|
31
|
+
inverse_of: :row_model,
|
32
|
+
validate: true,
|
33
|
+
}
|
34
|
+
|
35
|
+
# Returns the ActsAsTable singular macro associations for this ActsAsTable row model.
|
36
|
+
has_many :belongs_tos, **{
|
37
|
+
autosave: true,
|
38
|
+
class_name: 'ActsAsTable::BelongsTo',
|
39
|
+
dependent: :destroy,
|
40
|
+
foreign_key: 'row_model_id',
|
41
|
+
inverse_of: :row_model,
|
42
|
+
validate: true,
|
43
|
+
}
|
44
|
+
|
45
|
+
# Returns the ActsAsTable collection macro associations for this ActsAsTable row model.
|
46
|
+
has_many :has_manies, **{
|
47
|
+
autosave: true,
|
48
|
+
class_name: 'ActsAsTable::HasMany',
|
49
|
+
dependent: :destroy,
|
50
|
+
foreign_key: 'row_model_id',
|
51
|
+
inverse_of: :row_model,
|
52
|
+
validate: true,
|
53
|
+
}
|
54
|
+
|
55
|
+
# Returns the ActsAsTable record models for this ActsAsTable row model.
|
56
|
+
has_many :record_models, **{
|
57
|
+
autosave: true,
|
58
|
+
class_name: 'ActsAsTable::RecordModel',
|
59
|
+
dependent: :destroy,
|
60
|
+
foreign_key: 'row_model_id',
|
61
|
+
inverse_of: :row_model,
|
62
|
+
validate: true,
|
63
|
+
}
|
64
|
+
|
65
|
+
# Returns the ActsAsTable tables that have been provided by this ActsAsTable row model.
|
66
|
+
has_many :tables, **{
|
67
|
+
class_name: 'ActsAsTable::Table',
|
68
|
+
dependent: :restrict_with_exception,
|
69
|
+
foreign_key: 'row_model_id',
|
70
|
+
inverse_of: :row_model,
|
71
|
+
}
|
72
|
+
|
73
|
+
validates :name, **{
|
74
|
+
presence: true,
|
75
|
+
}
|
76
|
+
|
77
|
+
validate :record_models_includes_root_record_model, **{
|
78
|
+
if: ::Proc.new { |row_model| row_model.root_record_model.present? },
|
79
|
+
}
|
80
|
+
|
81
|
+
# Draw an ActsAsTable row model.
|
82
|
+
#
|
83
|
+
# @yieldparam [ActsAsTable::Mapper::RowModel] row_model_mapper
|
84
|
+
# @yieldreturn [void]
|
85
|
+
# @return [void]
|
86
|
+
#
|
87
|
+
# @see ActsAsTable::Mapper::RowModel
|
88
|
+
def draw(&block)
|
89
|
+
ActsAsTable::Mapper::RowModel.new(self, &block)
|
90
|
+
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the ActsAsTable headers array object for the ActsAsTable column models for this ActsAsTable row model.
|
95
|
+
#
|
96
|
+
# @return [ActsAsTable::Headers::Array]
|
97
|
+
def to_headers
|
98
|
+
ActsAsTable::Headers::Array.new(self.column_models)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the ActsAsTable records for the given row.
|
102
|
+
#
|
103
|
+
# @param [Array<String, nil>, nil] row
|
104
|
+
# @param [ActiveRecord::Relation<ActsAsTable::Record>] records
|
105
|
+
# @return [Array<ActsAsTable::Record>]
|
106
|
+
# @raise [ArgumentError] If the name of a class for a given record does not match the class name for the corresponding ActsAsTable record model.
|
107
|
+
def from_row(row = [], records = ActsAsTable::Record.all)
|
108
|
+
# @return [Hash<ActsAsTable::RecordModel, Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>>]
|
109
|
+
value_by_record_model_and_value_provider = self.record_models.inject({}) { |acc_for_record_model, record_model|
|
110
|
+
acc_for_record_model[record_model] = record_model.each_acts_as_table_value_provider(except: [:row_model]).inject({}) { |acc_for_value_provider, value_provider|
|
111
|
+
acc_for_value_provider[value_provider] = value_provider.column_model.try { |column_model|
|
112
|
+
# @return [Integer]
|
113
|
+
index = column_model.position - 1
|
114
|
+
|
115
|
+
if (index >= 0) && (index < row.size)
|
116
|
+
row[index]
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
}
|
121
|
+
|
122
|
+
acc_for_value_provider
|
123
|
+
}
|
124
|
+
|
125
|
+
acc_for_record_model
|
126
|
+
}
|
127
|
+
|
128
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
129
|
+
hash = ActsAsTable.adapter.set_value_for(self, nil, value_by_record_model_and_value_provider, default: true)
|
130
|
+
|
131
|
+
hash.target_value.each_pair.collect { |pair|
|
132
|
+
record_model, pair = *pair
|
133
|
+
|
134
|
+
new_record_or_persisted, pair_by_value_provider = *pair
|
135
|
+
|
136
|
+
records.build(record_model_changed: pair_by_value_provider.changed?) do |record|
|
137
|
+
record.base = new_record_or_persisted
|
138
|
+
|
139
|
+
record.record_model = record_model
|
140
|
+
|
141
|
+
# @note {ActiveRecord::Validations#validate} is an alias for {ActiveRecord::Validations#valid?} that does not raise an exception when the record is invalid.
|
142
|
+
new_record_or_persisted.validate
|
143
|
+
|
144
|
+
# @return [Array<String>]
|
145
|
+
attribute_names = []
|
146
|
+
|
147
|
+
pair_by_value_provider.target_value.each do |value_provider, target_value|
|
148
|
+
# @return [String]
|
149
|
+
attribute_name = \
|
150
|
+
case value_provider
|
151
|
+
when ActsAsTable::ForeignKey
|
152
|
+
klass = record_model.class_name.constantize
|
153
|
+
|
154
|
+
reflection = klass.reflect_on_association(value_provider.method_name)
|
155
|
+
|
156
|
+
reflection.foreign_key
|
157
|
+
else
|
158
|
+
value_provider.method_name
|
159
|
+
end
|
160
|
+
|
161
|
+
attribute_names << attribute_name
|
162
|
+
|
163
|
+
record.values.build(target_value: target_value.target_value, value_provider_changed: target_value.changed?) do |value|
|
164
|
+
value.value_provider = value_provider
|
165
|
+
|
166
|
+
value_provider.column_model.try { |column_model|
|
167
|
+
value.column_model = column_model
|
168
|
+
|
169
|
+
value.position = column_model.position
|
170
|
+
|
171
|
+
# @return [Integer]
|
172
|
+
index = column_model.position - 1
|
173
|
+
|
174
|
+
value.source_value = \
|
175
|
+
if (index >= 0) && (index < row.size)
|
176
|
+
row[index]
|
177
|
+
else
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
}
|
181
|
+
|
182
|
+
new_record_or_persisted.errors[attribute_name].each do |message|
|
183
|
+
value.record_errors.build(attribute_name: attribute_name, message: message)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
new_record_or_persisted.errors.each do |attribute_name, message|
|
189
|
+
unless attribute_names.include?(attribute_name.to_s)
|
190
|
+
record.record_errors.build(attribute_name: attribute_name, message: message)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns the row for the given record.
|
198
|
+
#
|
199
|
+
# @param [ActiveRecord::Base, nil] base
|
200
|
+
# @return [Array<String, nil>, nil]
|
201
|
+
# @raise [ArgumentError]
|
202
|
+
def to_row(base = nil)
|
203
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
204
|
+
value_by_record_model_and_value_provider = ActsAsTable.adapter.get_value_for(self, base, default: false)
|
205
|
+
|
206
|
+
# @return [Integer]
|
207
|
+
column_models_maximum_position = (self.persisted? ? self.column_models.maximum(:position) : self.column_models.to_a.collect(&:position).max) || 0
|
208
|
+
|
209
|
+
# @return [Array<String, nil>]
|
210
|
+
row = ::Array.new(column_models_maximum_position) { nil }
|
211
|
+
|
212
|
+
self.record_models.each do |record_model|
|
213
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue, nil]
|
214
|
+
value_by_value_provider = value_by_record_model_and_value_provider.target_value.try(:[], record_model)
|
215
|
+
|
216
|
+
record_model.each_acts_as_table_value_provider(except: [:row_model]) do |value_provider|
|
217
|
+
value_provider.column_model.try { |column_model|
|
218
|
+
# @return [Integer]
|
219
|
+
index = column_model.position - 1
|
220
|
+
|
221
|
+
if (index >= 0) && (index < column_models_maximum_position)
|
222
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue, nil]
|
223
|
+
value = value_by_value_provider.try(:target_value).try(:[], value_provider)
|
224
|
+
|
225
|
+
row[index] = value.try(:target_value)
|
226
|
+
end
|
227
|
+
}
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
row.all?(&:nil?) ? nil : row
|
232
|
+
end
|
233
|
+
|
234
|
+
include ActsAsTable::RecordModelClassMethods
|
235
|
+
|
236
|
+
# Returns `true` if the given ActsAsTable record model is reachable from the root ActsAsTable record model for this ActsAsTable row model. Otherwise, returns `false`.
|
237
|
+
#
|
238
|
+
# @param [ActsAsTable::RecordModel] record_model
|
239
|
+
# @return [Boolean]
|
240
|
+
def reachable_record_model?(record_model)
|
241
|
+
self.class.reachable_record_model_for?(self.root_record_model, record_model)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Returns the ActsAsTable record models that are reachable from the root ActsAsTable record model for this ActsAsTable row model (in topological order).
|
245
|
+
#
|
246
|
+
# @return [Array<ActsAsTable::RecordModel>]
|
247
|
+
def reachable_record_models
|
248
|
+
self.class.reachable_record_models_for(self.root_record_model)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Get the value for the given record using the given options.
|
252
|
+
#
|
253
|
+
# @param [ActiveRecord::Base, nil] base
|
254
|
+
# @param [Hash<Symbol, Object>] options
|
255
|
+
# @option options [Boolean] :default
|
256
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
257
|
+
# @raise [ArgumentError]
|
258
|
+
def get_value(base = nil, **options)
|
259
|
+
self.class.get_value_for(self.root_record_model, base, **options)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Set the new value for the given record using the given options.
|
263
|
+
#
|
264
|
+
# @param [ActiveRecord::Base, nil] base
|
265
|
+
# @param [Hash<ActsAsTable::RecordModel, Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>>] new_value_by_record_model_and_value_provider
|
266
|
+
# @param [Hash<Symbol, Object>] options
|
267
|
+
# @option options [Boolean] :default
|
268
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
269
|
+
# @raise [ArgumentError]
|
270
|
+
def set_value(base = nil, new_value_by_record_model_and_value_provider = {}, **options)
|
271
|
+
self.class.set_value_for(self.root_record_model, base, new_value_by_record_model_and_value_provider, **options)
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
# @return [void]
|
277
|
+
def record_models_includes_root_record_model
|
278
|
+
unless self.record_models.include?(self.root_record_model)
|
279
|
+
self.errors.add('root_record_model_id', :inclusion)
|
280
|
+
end
|
281
|
+
|
282
|
+
return
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActsAsTable
|
2
|
+
# ActsAsTable table.
|
3
|
+
#
|
4
|
+
# @!attribute [r] records_count
|
5
|
+
# Returns the number of ActsAsTable records for this ActsAsTable table.
|
6
|
+
#
|
7
|
+
# @return [Integer]
|
8
|
+
class Table < ::ActiveRecord::Base
|
9
|
+
# @!parse
|
10
|
+
# include ActsAsTable::ValueProvider
|
11
|
+
# include ActsAsTable::ValueProviderAssociationMethods
|
12
|
+
|
13
|
+
self.table_name = ActsAsTable.tables_table
|
14
|
+
|
15
|
+
# Returns the ActsAsTable row model for this ActsAsTable table.
|
16
|
+
belongs_to :row_model, **{
|
17
|
+
class_name: 'ActsAsTable::RowModel',
|
18
|
+
inverse_of: :tables,
|
19
|
+
required: true,
|
20
|
+
}
|
21
|
+
|
22
|
+
# Returns the ActsAsTable records for this ActsAsTable table.
|
23
|
+
has_many :records, -> { order(position: :asc) }, **{
|
24
|
+
autosave: true,
|
25
|
+
class_name: 'ActsAsTable::Record',
|
26
|
+
dependent: :destroy,
|
27
|
+
foreign_key: 'table_id',
|
28
|
+
inverse_of: :table,
|
29
|
+
validate: true,
|
30
|
+
}
|
31
|
+
|
32
|
+
# Returns the ActsAsTable records for the given row.
|
33
|
+
#
|
34
|
+
# @param [Array<String, nil>, nil] row
|
35
|
+
# @return [Array<ActsAsTable::Record>]
|
36
|
+
# @raise [ArgumentError] If the name of a class for a given record does not match the class name for the corresponding ActsAsTable record model.
|
37
|
+
def from_row(row = [])
|
38
|
+
self.row_model.from_row(row, self.records)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module ActsAsTable
|
2
|
+
# ActsAsTable value.
|
3
|
+
#
|
4
|
+
# @!attribute [rw] position
|
5
|
+
# Returns the position of this ActsAsTable value or `nil`.
|
6
|
+
#
|
7
|
+
# @return [Integer, nil]
|
8
|
+
# @!attribute [r] record_errors_count
|
9
|
+
# Returns the number of ActsAsTable record errors for this ActsAsTable value.
|
10
|
+
#
|
11
|
+
# @return [Integer]
|
12
|
+
# @!attribute [rw] source_value
|
13
|
+
# Returns the source value for this ActsAsTable value.
|
14
|
+
#
|
15
|
+
# @return [String, nil]
|
16
|
+
# @!attribute [rw] target_value
|
17
|
+
# Returns the target value for this ActsAsTable value.
|
18
|
+
#
|
19
|
+
# @return [String, nil]
|
20
|
+
# @!attribute [rw] value_provider_changed
|
21
|
+
# Returns `true` if the ActsAsTable value provider changed the value for this ActsAsTable value. Otherwise, returns `false`.
|
22
|
+
#
|
23
|
+
# @return [Boolean]
|
24
|
+
class Value < ::ActiveRecord::Base
|
25
|
+
# @!parse
|
26
|
+
# include ActsAsTable::ValueProvider
|
27
|
+
# include ActsAsTable::ValueProviderAssociationMethods
|
28
|
+
|
29
|
+
self.table_name = ActsAsTable.values_table
|
30
|
+
|
31
|
+
# Returns the ActsAsTable column model that provided this ActsAsTable value or `nil`.
|
32
|
+
#
|
33
|
+
# @return [ActsAsTable::ColumnModel, nil]
|
34
|
+
belongs_to :column_model, **{
|
35
|
+
class_name: 'ActsAsTable::ColumnModel',
|
36
|
+
inverse_of: :values,
|
37
|
+
required: false,
|
38
|
+
}
|
39
|
+
|
40
|
+
# Returns the ActsAsTable record for this ActsAsTable value.
|
41
|
+
belongs_to :record, **{
|
42
|
+
class_name: 'ActsAsTable::Record',
|
43
|
+
counter_cache: 'values_count',
|
44
|
+
inverse_of: :values,
|
45
|
+
required: true,
|
46
|
+
}
|
47
|
+
|
48
|
+
# Returns the ActsAsTable value provider that provider the value for this ActsAsTable value.
|
49
|
+
#
|
50
|
+
# @return [ActsAsTable::ValueProvider::InstanceMethods]
|
51
|
+
belongs_to :value_provider, **{
|
52
|
+
polymorphic: true,
|
53
|
+
required: true,
|
54
|
+
}
|
55
|
+
|
56
|
+
# Returns the ActsAsTable record errors for this ActsAsTable value.
|
57
|
+
has_many :record_errors, -> { order(attribute_name: :asc, message: :asc) }, **{
|
58
|
+
autosave: false,
|
59
|
+
class_name: 'ActsAsTable::RecordError',
|
60
|
+
dependent: :nullify,
|
61
|
+
foreign_key: 'value_id',
|
62
|
+
inverse_of: :values,
|
63
|
+
validate: false,
|
64
|
+
}
|
65
|
+
|
66
|
+
validates :record_id, **{
|
67
|
+
uniqueness: {
|
68
|
+
scope: ['value_provider_id', 'value_provider_type'],
|
69
|
+
},
|
70
|
+
}
|
71
|
+
|
72
|
+
# validates :position, **{}
|
73
|
+
|
74
|
+
# validates :source_value, **{}
|
75
|
+
|
76
|
+
# validates :target_value, **{}
|
77
|
+
|
78
|
+
# validates :value_provider_changed, **{}
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,554 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module ActsAsTable
|
4
|
+
# ActsAsTable record model class methods (concern).
|
5
|
+
module RecordModelClassMethods
|
6
|
+
extend ::ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
# Returns `true` if the target ActsAsTable record model is reachable from the source ActsAsTable record model. Otherwise, returns `false`.
|
10
|
+
#
|
11
|
+
# @param [ActsAsTable::RecordModel] source_record_model
|
12
|
+
# @param [ActsAsTable::RecordModel] target_record_model
|
13
|
+
# @return [Boolean]
|
14
|
+
def reachable_record_model_for?(source_record_model, target_record_model)
|
15
|
+
Inject.instance.inject(false, source_record_model) { |acc, path, &block|
|
16
|
+
# @note Continue until the target ActsAsTable record model is reached.
|
17
|
+
acc || (path.options.dig(:data, :target_record_model) == target_record_model) || block.call(acc)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the ActsAsTable record models that are reachable from the given ActsAsTable record models (in topological order).
|
22
|
+
#
|
23
|
+
# @param [ActsAsTable::RecordModel] record_model
|
24
|
+
# @return [Array<ActsAsTable::RecordModel>]
|
25
|
+
def reachable_record_models_for(record_model)
|
26
|
+
Inject.instance.inject([], record_model) { |acc, path, &block|
|
27
|
+
# @return [ActsAsTable::RecordModel]
|
28
|
+
target_record_model = path.options.dig(:data, :target_record_model)
|
29
|
+
|
30
|
+
# @note Continue until every target ActsAsTable record is reached one or more times.
|
31
|
+
if acc.include?(target_record_model)
|
32
|
+
acc
|
33
|
+
else
|
34
|
+
acc << target_record_model
|
35
|
+
|
36
|
+
block.call(acc)
|
37
|
+
end
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get the value for the given record using the given options.
|
42
|
+
#
|
43
|
+
# @param [ActsAsTable::RecordModel] record_model
|
44
|
+
# @param [ActiveRecord::Base, nil] base
|
45
|
+
# @param [Hash<Symbol, Object>] options
|
46
|
+
# @option options [Boolean] :default
|
47
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
48
|
+
# @raise [ArgumentError]
|
49
|
+
def get_value_for(record_model, base = nil, **options)
|
50
|
+
GetValue.new(**options).call(record_model, base)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set the new value for the given record using the given options.
|
54
|
+
#
|
55
|
+
# @param [ActsAsTable::RecordModel] record_model
|
56
|
+
# @param [ActiveRecord::Base, nil] base
|
57
|
+
# @param [Hash<ActsAsTable::RecordModel, Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>>] new_value_by_record_model_and_value_provider
|
58
|
+
# @param [Hash<Symbol, Object>] options
|
59
|
+
# @option options [Boolean] :default
|
60
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
61
|
+
# @raise [ArgumentError]
|
62
|
+
def set_value_for(record_model, base = nil, new_value_by_record_model_and_value_provider = {}, **options)
|
63
|
+
SetValue.new(new_value_by_record_model_and_value_provider, **options).call(record_model, base)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# ActsAsTable "non-destructive" traversal (does not create new records if they are not found).
|
68
|
+
class Inject
|
69
|
+
include ::Singleton
|
70
|
+
|
71
|
+
# Combine all ActsAsTable paths using an associative binary operation.
|
72
|
+
#
|
73
|
+
# @param [Object] acc
|
74
|
+
# @param [ActsAsTable::RecordModel] record_model
|
75
|
+
# @yieldparam [Object] acc
|
76
|
+
# @yieldparam [ActsAsTable::Path] path
|
77
|
+
# @yieldreturn [Object]
|
78
|
+
# @return [Object]
|
79
|
+
def inject(acc, record_model, &block)
|
80
|
+
# @return [Class]
|
81
|
+
klass = record_model.class_name.constantize
|
82
|
+
|
83
|
+
# @return [ActsAsTable::Path]
|
84
|
+
path = ActsAsTable::Path.new(klass, nil, data: {
|
85
|
+
source_record_model: nil,
|
86
|
+
target_record_model: record_model,
|
87
|
+
})
|
88
|
+
|
89
|
+
_inject(acc, path, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# @param [Object] orig_acc
|
95
|
+
# @yieldparam [ActsAsTable::Path] orig_path
|
96
|
+
# @yieldparam [Object] acc
|
97
|
+
# @yieldparam [ActsAsTable::Path] path
|
98
|
+
# @yieldreturn [Object]
|
99
|
+
# @return [Object]
|
100
|
+
def _inject(orig_acc, orig_path, &block)
|
101
|
+
block.call(orig_acc, orig_path) do |new_acc|
|
102
|
+
# @return [ActsAsTable::RecordModel]
|
103
|
+
source_record_model = orig_path.options.dig(:data, :target_record_model)
|
104
|
+
|
105
|
+
%i(belongs_to).each do |macro|
|
106
|
+
# @return [Array<ActsAsTable::BelongsTo>]
|
107
|
+
macro_reflection_models = source_record_model.persisted? ? source_record_model.send(:"#{macro.to_s.pluralize}_as_source").to_a : source_record_model.row_model.send(:"#{macro.to_s.pluralize}").to_a.select { |macro_reflection_model| macro_reflection_model.source_record_model == source_record_model }
|
108
|
+
|
109
|
+
macro_reflection_models.each do |macro_reflection_model|
|
110
|
+
# @return [ActsAsTable::RecordModel]
|
111
|
+
target_record_model = macro_reflection_model.target_record_model
|
112
|
+
|
113
|
+
# @return [ActsAsTable::Path]
|
114
|
+
new_path = orig_path.send(macro_reflection_model.macro, macro_reflection_model.method_name, data: {
|
115
|
+
source_record_model: source_record_model,
|
116
|
+
target_record_model: target_record_model,
|
117
|
+
})
|
118
|
+
|
119
|
+
new_acc = _inject(new_acc, new_path, &block)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
%i(has_many).each do |macro|
|
124
|
+
# @return [Array<ActsAsTable::HasMany>]
|
125
|
+
macro_reflection_models = source_record_model.persisted? ? source_record_model.send(:"#{macro.to_s.pluralize}_as_source").to_a : source_record_model.row_model.send(:"#{macro.to_s.pluralize}").to_a.select { |macro_reflection_model| macro_reflection_model.source_record_model == source_record_model }
|
126
|
+
|
127
|
+
macro_reflection_models.each do |macro_reflection_model|
|
128
|
+
# @return [Array<ActsAsTable::HasManyTarget>]
|
129
|
+
macro_reflection_model_targets = macro_reflection_model.persisted? ? macro_reflection_model.send(:"#{macro.to_s.singularize}_targets").to_a : macro_reflection_model.send(:"#{macro.to_s.singularize}_targets").to_a.sort_by(&:position)
|
130
|
+
|
131
|
+
macro_reflection_model_targets.each do |macro_reflection_model_target|
|
132
|
+
# @return [ActsAsTable::RecordModel]
|
133
|
+
target_record_model = macro_reflection_model_target.record_model
|
134
|
+
|
135
|
+
# @return [Integer]
|
136
|
+
index = macro_reflection_model_target.position - 1
|
137
|
+
|
138
|
+
# @return [ActsAsTable::Path]
|
139
|
+
new_path = orig_path.send(macro_reflection_model.macro, macro_reflection_model.method_name, index, data: {
|
140
|
+
source_record_model: source_record_model,
|
141
|
+
target_record_model: target_record_model,
|
142
|
+
})
|
143
|
+
|
144
|
+
new_acc = _inject(new_acc, new_path, &block)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
new_acc
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# ActsAsTable "destructive" traversal (creates new records if they are not found and updates persisted records).
|
155
|
+
class FindOrInitializeBy
|
156
|
+
# Returns the number of mandatory arguments.
|
157
|
+
#
|
158
|
+
# @return [Integer]
|
159
|
+
def arity
|
160
|
+
1
|
161
|
+
end
|
162
|
+
|
163
|
+
# Invokes the traversal.
|
164
|
+
#
|
165
|
+
# @param [ActsAsTable::RecordModel] record_model
|
166
|
+
# @param [ActiveRecord::Base, nil] base
|
167
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
168
|
+
# @raise [ArgumentError] If the name of a class for a given record does not match the class name for the corresponding ActsAsTable record model.
|
169
|
+
def call(record_model, base = nil)
|
170
|
+
raise ::NotImplementedError.new("#{self.class}#call")
|
171
|
+
end
|
172
|
+
|
173
|
+
# Combine all ActsAsTable paths using an associative binary operation.
|
174
|
+
#
|
175
|
+
# @param [Object] acc
|
176
|
+
# @param [ActsAsTable::RecordModel] record_model
|
177
|
+
# @param [ActiveRecord::Base, nil] base
|
178
|
+
# @yieldparam [Object] acc
|
179
|
+
# @yieldparam [ActsAsTable::Path] path
|
180
|
+
# @yieldreturn [Object]
|
181
|
+
# @return [Object]
|
182
|
+
# @raise [ArgumentError] If the name of a class for a given record does not match the class name for the corresponding ActsAsTable record model.
|
183
|
+
def inject(acc, record_model, base = nil)
|
184
|
+
# @return [Class]
|
185
|
+
klass = record_model.class_name.constantize
|
186
|
+
|
187
|
+
# @return [ActsAsTable::Path]
|
188
|
+
path = ActsAsTable::Path.new(klass, nil, data: {
|
189
|
+
source_record_model: nil,
|
190
|
+
source_record: nil,
|
191
|
+
target_record_model: record_model,
|
192
|
+
target_record: base,
|
193
|
+
})
|
194
|
+
|
195
|
+
_inject(acc, path, **{
|
196
|
+
find_or_initialize_by: ::Proc.new { |*args, &block|
|
197
|
+
ActsAsTable.adapter.find_or_initialize_by_for(record_model, klass, :find_by!, *args, &block)
|
198
|
+
},
|
199
|
+
new: ::Proc.new { |*args, &block|
|
200
|
+
ActsAsTable.adapter.new_for(record_model, klass, :new, *args, &block)
|
201
|
+
},
|
202
|
+
})
|
203
|
+
end
|
204
|
+
|
205
|
+
protected
|
206
|
+
|
207
|
+
# @param [Object] acc
|
208
|
+
# @param [ActsAsTable::Path] path
|
209
|
+
# @yieldparam [Object] acc
|
210
|
+
# @yieldparam [ActsAsTable::Path] path
|
211
|
+
# @yieldreturn [Object]
|
212
|
+
# @return [Object]
|
213
|
+
# @raise [ArgumentError]
|
214
|
+
def _around_inject(acc, path, &block)
|
215
|
+
block.call(acc)
|
216
|
+
end
|
217
|
+
|
218
|
+
# @param [Object] acc
|
219
|
+
# @param [ActsAsTable::RecordModel] record_model
|
220
|
+
# @param [ActiveRecord::Base, nil] base
|
221
|
+
# @param [Hash<Symbol, Object>] options
|
222
|
+
# @option options [#call] :find_or_initialize_by
|
223
|
+
# @option options [#call] :new
|
224
|
+
# @return [Array<Object>]
|
225
|
+
def _find_or_initialize(acc, record_model, base = nil, **options)
|
226
|
+
[acc, base]
|
227
|
+
end
|
228
|
+
|
229
|
+
# @param [Object] acc
|
230
|
+
# @param [ActsAsTable::RecordModel] record_model
|
231
|
+
# @return [ActiveRecord::Base, nil]
|
232
|
+
def _at(acc, record_model)
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
# @param [Object] orig_acc
|
239
|
+
# @param [ActsAsTable::Path] orig_path
|
240
|
+
# @param [Hash<Symbol, Object>] options
|
241
|
+
# @option options [#call] :find_or_initialize_by
|
242
|
+
# @option options [#call] :new
|
243
|
+
# @yieldparam [ActsAsTable::Path] orig_path
|
244
|
+
# @yieldparam [Object] acc
|
245
|
+
# @yieldparam [ActsAsTable::Path] path
|
246
|
+
# @yieldreturn [Object]
|
247
|
+
# @return [Object]
|
248
|
+
# @raise [ArgumentError]
|
249
|
+
def _inject(orig_acc, orig_path, **options)
|
250
|
+
options.assert_valid_keys(:find_or_initialize_by, :new)
|
251
|
+
|
252
|
+
_around_inject(orig_acc, orig_path) do |new_acc|
|
253
|
+
# @return [ActsAsTable::RecordModel]
|
254
|
+
source_record_model = orig_path.options.dig(:data, :target_record_model)
|
255
|
+
|
256
|
+
# @return [ActiveRecord::Base, nil]
|
257
|
+
source_record = orig_path.options.dig(:data, :target_record)
|
258
|
+
|
259
|
+
unless source_record.nil? || source_record.class.name.eql?(source_record_model.class_name)
|
260
|
+
raise ::ArgumentError.new("#{self.name}#_inject - source_record - expected: #{source_record_model.class_name.inspect}, found: #{source_record.inspect}")
|
261
|
+
end
|
262
|
+
|
263
|
+
new_acc, source_record = *_find_or_initialize(new_acc, source_record_model, source_record, **options)
|
264
|
+
|
265
|
+
unless source_record.nil?
|
266
|
+
%i(belongs_to).each do |macro|
|
267
|
+
# @return [Array<ActsAsTable::BelongsTo>]
|
268
|
+
macro_reflection_models = source_record_model.persisted? ? source_record_model.send(:"#{macro.to_s.pluralize}_as_source").to_a : source_record_model.row_model.send(:"#{macro.to_s.pluralize}").to_a.select { |macro_reflection_model| macro_reflection_model.source_record_model == source_record_model }
|
269
|
+
|
270
|
+
macro_reflection_models.each do |macro_reflection_model|
|
271
|
+
# @return [ActsAsTable::RecordModel]
|
272
|
+
target_record_model = macro_reflection_model.target_record_model
|
273
|
+
|
274
|
+
# @return [ActiveRecord::Reflection::MacroReflection]
|
275
|
+
reflection = source_record.class.reflect_on_association(macro_reflection_model.method_name)
|
276
|
+
|
277
|
+
# @return [ActiveRecord::Base, nil]
|
278
|
+
target_record = source_record.send(reflection.name)
|
279
|
+
|
280
|
+
if target_record.nil?
|
281
|
+
target_record = source_record.send(:"#{reflection.name}=", _at(new_acc, target_record_model))
|
282
|
+
end
|
283
|
+
|
284
|
+
# @return [ActsAsTable::Path]
|
285
|
+
new_path = orig_path.send(macro_reflection_model.macro, macro_reflection_model.method_name, data: {
|
286
|
+
source_record_model: source_record_model,
|
287
|
+
source_record: source_record,
|
288
|
+
target_record_model: target_record_model,
|
289
|
+
target_record: target_record,
|
290
|
+
})
|
291
|
+
|
292
|
+
new_acc = _inject(new_acc, new_path, **options.merge({
|
293
|
+
find_or_initialize_by: ::Proc.new { |*args, &block|
|
294
|
+
source_record.send(:"#{reflection.name}=", ActsAsTable.adapter.find_or_initialize_by_for(target_record_model, reflection.klass, :find_by!, *args, &block))
|
295
|
+
},
|
296
|
+
new: ::Proc.new { |*args, &block|
|
297
|
+
source_record.send(:"#{reflection.name}=", nil)
|
298
|
+
|
299
|
+
ActsAsTable.adapter.new_for(target_record_model, source_record, :"build_#{reflection.name}", *args, &block)
|
300
|
+
},
|
301
|
+
}))
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
%i(has_many).each do |macro|
|
306
|
+
# @return [Array<ActsAsTable::HasMany>]
|
307
|
+
macro_reflection_models = source_record_model.persisted? ? source_record_model.send(:"#{macro.to_s.pluralize}_as_source").to_a : source_record_model.row_model.send(:"#{macro.to_s.pluralize}").to_a.select { |macro_reflection_model| macro_reflection_model.source_record_model == source_record_model }
|
308
|
+
|
309
|
+
macro_reflection_models.each do |macro_reflection_model|
|
310
|
+
# @return [ActiveRecord::Reflection::MacroReflection]
|
311
|
+
reflection = source_record.class.reflect_on_association(macro_reflection_model.method_name)
|
312
|
+
|
313
|
+
# @return [ActiveRecord::Relation<ActiveRecord::Base>]
|
314
|
+
relation = source_record.send(reflection.name)
|
315
|
+
|
316
|
+
# @return [Array<ActiveRecord::Base>]
|
317
|
+
target_records = relation.to_a
|
318
|
+
|
319
|
+
# @return [Array<ActsAsTable::HasManyTarget>]
|
320
|
+
macro_reflection_model_targets = macro_reflection_model.persisted? ? macro_reflection_model.send(:"#{macro.to_s.singularize}_targets").to_a : macro_reflection_model.send(:"#{macro.to_s.singularize}_targets").to_a.sort_by(&:position)
|
321
|
+
|
322
|
+
macro_reflection_model_targets.each do |macro_reflection_model_target|
|
323
|
+
# @return [ActsAsTable::RecordModel]
|
324
|
+
target_record_model = macro_reflection_model_target.record_model
|
325
|
+
|
326
|
+
# @return [Integer]
|
327
|
+
index = macro_reflection_model_target.position - 1
|
328
|
+
|
329
|
+
target_record = \
|
330
|
+
if (index >= 0) && (index < target_records.size)
|
331
|
+
target_records[index]
|
332
|
+
else
|
333
|
+
nil
|
334
|
+
end
|
335
|
+
|
336
|
+
if target_record.nil?
|
337
|
+
target_record = _at(new_acc, target_record_model)
|
338
|
+
|
339
|
+
unless target_record.nil? || target_records.include?(target_record)
|
340
|
+
relation.proxy_association.add_to_target(target_record)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# @return [ActsAsTable::Path]
|
345
|
+
new_path = orig_path.send(macro_reflection_model.macro, macro_reflection_model.method_name, index, data: {
|
346
|
+
source_record_model: source_record_model,
|
347
|
+
source_record: source_record,
|
348
|
+
target_record_model: target_record_model,
|
349
|
+
target_record: target_record,
|
350
|
+
})
|
351
|
+
|
352
|
+
new_acc = _inject(new_acc, new_path, **options.merge({
|
353
|
+
find_or_initialize_by: ::Proc.new { |*args, &block|
|
354
|
+
ActsAsTable.adapter.find_or_initialize_by_for(target_record_model, relation, :find_by!, *args, &block)
|
355
|
+
},
|
356
|
+
new: ::Proc.new { |*args, &block|
|
357
|
+
ActsAsTable.adapter.new_for(target_record_model, relation, :build, *args, &block)
|
358
|
+
},
|
359
|
+
}))
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
new_acc
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Get the value for the given record using the given options.
|
371
|
+
#
|
372
|
+
# @!attribute [r] options
|
373
|
+
# The options for this ActsAsTable "destructive" traversal.
|
374
|
+
#
|
375
|
+
# @return [Hash<Symbol, Object>]
|
376
|
+
class GetValue < FindOrInitializeBy
|
377
|
+
attr_reader :options
|
378
|
+
|
379
|
+
# Returns a new ActsAsTable "destructive" traversal.
|
380
|
+
#
|
381
|
+
# @param [Hash<Symbol, Object>] options
|
382
|
+
# @option options [Boolean] :default
|
383
|
+
# @return [ActsAsTable::RecordModelClassMethods::GetValue]
|
384
|
+
def initialize(**options)
|
385
|
+
super()
|
386
|
+
|
387
|
+
options.assert_valid_keys(:default)
|
388
|
+
|
389
|
+
@options = options.dup
|
390
|
+
end
|
391
|
+
|
392
|
+
# Invokes the traversal.
|
393
|
+
#
|
394
|
+
# @param [ActsAsTable::RecordModel] record_model
|
395
|
+
# @param [ActiveRecord::Base, nil] base
|
396
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
397
|
+
# @raise [ArgumentError] If the name of a class for a given record does not match the class name for the corresponding ActsAsTable record model.
|
398
|
+
def call(record_model, base = nil)
|
399
|
+
ActsAsTable.adapter.wrap_value_for(record_model, base, nil, self.inject({}, record_model, base))
|
400
|
+
end
|
401
|
+
|
402
|
+
protected
|
403
|
+
|
404
|
+
# @param [Hash<ActsAsTable::RecordModel, ActsAsTable::ValueProvider::WrappedValue>] acc
|
405
|
+
# @param [ActsAsTable::Path] path
|
406
|
+
# @yieldparam [Hash<ActsAsTable::RecordModel, ActsAsTable::ValueProvider::WrappedValue>] acc
|
407
|
+
# @yieldparam [ActsAsTable::Path] path
|
408
|
+
# @yieldreturn [Hash<ActsAsTable::RecordModel, ActsAsTable::ValueProvider::WrappedValue>]
|
409
|
+
# @return [Hash<ActsAsTable::RecordModel, ActsAsTable::ValueProvider::WrappedValue>]
|
410
|
+
# @raise [ArgumentError]
|
411
|
+
def _around_inject(acc, path, &block)
|
412
|
+
acc.key?(path.options.dig(:data, :target_record_model)) ? acc : block.call(acc)
|
413
|
+
end
|
414
|
+
|
415
|
+
# @param [Hash<ActsAsTable::RecordModel, ActsAsTable::ValueProvider::WrappedValue>] acc
|
416
|
+
# @param [ActiveRecord::Base, nil] base
|
417
|
+
# @param [Hash<Symbol, Object>] options
|
418
|
+
# @option options [#call] :find_or_initialize_by
|
419
|
+
# @option options [#call] :new
|
420
|
+
# @return [Array<Object>]
|
421
|
+
# @raise [ArgumentError]
|
422
|
+
def _find_or_initialize(acc, record_model, base = nil, **options)
|
423
|
+
acc[record_model] ||= ActsAsTable.adapter.get_value_for(record_model, base, **@options)
|
424
|
+
|
425
|
+
[acc, base]
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Set the new value for the given record using the given options.
|
430
|
+
#
|
431
|
+
# @!attribute [r] new_value_by_record_model_and_value_provider
|
432
|
+
# The new values by ActsAsTable record model and value provider for this ActsAsTable "destructive" traversal.
|
433
|
+
#
|
434
|
+
# @return [Hash<ActsAsTable::RecordModel, Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>>]
|
435
|
+
# @!attribute [r] options
|
436
|
+
# The options for this ActsAsTable "destructive" traversal.
|
437
|
+
#
|
438
|
+
# @return [Hash<Symbol, Object>]
|
439
|
+
class SetValue < FindOrInitializeBy
|
440
|
+
attr_reader :new_value_by_record_model_and_value_provider, :options
|
441
|
+
|
442
|
+
# Returns a new ActsAsTable "destructive" traversal.
|
443
|
+
#
|
444
|
+
# @param [Hash<ActsAsTable::RecordModel, Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>>] new_value_by_record_model_and_value_provider
|
445
|
+
# @param [Hash<Symbol, Object>] options
|
446
|
+
# @option options [Boolean] :default
|
447
|
+
# @return [ActsAsTable::RecordModelClassMethods::SetValue]
|
448
|
+
def initialize(new_value_by_record_model_and_value_provider = {}, **options)
|
449
|
+
super()
|
450
|
+
|
451
|
+
options.assert_valid_keys(:default)
|
452
|
+
|
453
|
+
@new_value_by_record_model_and_value_provider, @options = new_value_by_record_model_and_value_provider, options.dup
|
454
|
+
end
|
455
|
+
|
456
|
+
# Invokes the traversal.
|
457
|
+
#
|
458
|
+
# @param [ActsAsTable::RecordModel] record_model
|
459
|
+
# @param [ActiveRecord::Base, nil] base
|
460
|
+
# @return [ActsAsTable::ValueProvider::WrappedValue]
|
461
|
+
# @raise [ArgumentError] If the name of a class for a given record does not match the class name for the corresponding ActsAsTable record model.
|
462
|
+
def call(record_model, base = nil)
|
463
|
+
ActsAsTable.adapter.wrap_value_for(record_model, base, nil, self.inject({}, record_model, base))
|
464
|
+
end
|
465
|
+
|
466
|
+
protected
|
467
|
+
|
468
|
+
# @param [Hash<ActsAsTable::RecordModel, Array<Object>>] acc
|
469
|
+
# @param [ActsAsTable::Path] path
|
470
|
+
# @yieldparam [Hash<ActsAsTable::RecordModel, Array<Object>>] acc
|
471
|
+
# @yieldparam [ActsAsTable::Path] path
|
472
|
+
# @yieldreturn [Hash<ActsAsTable::RecordModel, Array<Object>>]
|
473
|
+
# @return [Hash<ActsAsTable::RecordModel, Array<Object>>]
|
474
|
+
# @raise [ArgumentError]
|
475
|
+
def _around_inject(acc, path, &block)
|
476
|
+
# @return [ActsAsTable::RecordModel]
|
477
|
+
source_record_model = path.options.dig(:data, :source_record_model)
|
478
|
+
|
479
|
+
# @return [ActsAsTable::RecordModel]
|
480
|
+
target_record_model = path.options.dig(:data, :target_record_model)
|
481
|
+
|
482
|
+
if path.collect(&:options).any? { |options| options.dig(:data, :source_record_model) == target_record_model }
|
483
|
+
acc
|
484
|
+
else
|
485
|
+
# @return [Hash<ActsAsTable::RecordModel, Array<Object>>]
|
486
|
+
new_acc = block.call(acc)
|
487
|
+
|
488
|
+
unless source_record_model.nil?
|
489
|
+
if new_acc[target_record_model][1].changed?
|
490
|
+
new_acc[source_record_model][1].changed!
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
new_acc
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# @param [Hash<ActsAsTable::RecordModel, Array<Object>>] acc
|
499
|
+
# @param [ActsAsTable::RecordModel] record_model
|
500
|
+
# @return [ActiveRecord::Base, nil]
|
501
|
+
def _at(acc, record_model)
|
502
|
+
acc.key?(record_model) ? acc[record_model][0] : nil
|
503
|
+
end
|
504
|
+
|
505
|
+
# @param [Hash<ActsAsTable::RecordModel, Array<Object>>] acc
|
506
|
+
# @param [ActsAsTable::RecordModel] record_model
|
507
|
+
# @param [ActiveRecord::Base, nil] base
|
508
|
+
# @param [Hash<Symbol, Object>] options
|
509
|
+
# @option options [#call] :find_or_initialize_by
|
510
|
+
# @option options [#call] :new
|
511
|
+
# @return [Array<Object>]
|
512
|
+
# @raise [ArgumentError]
|
513
|
+
def _find_or_initialize(acc, record_model, base = nil, **options)
|
514
|
+
acc[record_model] ||= begin
|
515
|
+
# @return [Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>, nil]
|
516
|
+
orig_value_by_value_provider = @new_value_by_record_model_and_value_provider.try(:[], record_model)
|
517
|
+
|
518
|
+
unless (primary_key = record_model.primary_keys.first).nil? || (base_id = orig_value_by_value_provider.try(:[], primary_key)).nil?
|
519
|
+
# @return [ActiveRecord::Base]
|
520
|
+
# @raise [ActiveRecord::RecordNotFound]
|
521
|
+
base = options[:find_or_initialize_by].call({
|
522
|
+
primary_key.method_name => base_id,
|
523
|
+
})
|
524
|
+
|
525
|
+
# @return [Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>, nil]
|
526
|
+
new_value_by_value_provider = orig_value_by_value_provider.try(:each_pair).try(:all?) { |pair|
|
527
|
+
(pair[0] == primary_key) || pair[0].column_model.nil? || pair[1].nil?
|
528
|
+
} ? orig_value_by_value_provider.try(:delete_if) { |value_provider, value|
|
529
|
+
value_provider != primary_key
|
530
|
+
} : orig_value_by_value_provider.try(:delete_if) { |value_provider, value|
|
531
|
+
value_provider.column_model.nil?
|
532
|
+
}
|
533
|
+
|
534
|
+
[
|
535
|
+
base,
|
536
|
+
ActsAsTable.adapter.set_value_for(record_model, base, new_value_by_value_provider, **@options),
|
537
|
+
]
|
538
|
+
else
|
539
|
+
if base.nil?
|
540
|
+
base = options[:new].call
|
541
|
+
end
|
542
|
+
|
543
|
+
[
|
544
|
+
base,
|
545
|
+
ActsAsTable.adapter.set_value_for(record_model, base, orig_value_by_value_provider, **@options),
|
546
|
+
]
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
[acc, base]
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|