Class: ActsAsTable::RowModel

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
RecordModelClassMethods
Defined in:
app/models/acts_as_table/row_model.rb

Overview

ActsAsTable row model (value provider).

Instance Attribute Summary collapse

Belongs to collapse

Has many collapse

Instance Method Summary collapse

Methods included from RecordModelClassMethods

get_value_for, reachable_record_model_for?, reachable_record_models_for, set_value_for

Instance Attribute Details

#nameString

Returns the name of this ActsAsTable row model.

Returns:

  • (String)


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'app/models/acts_as_table/row_model.rb', line 8

class RowModel < ::ActiveRecord::Base
  # @!parse
  #   include ActsAsTable::ValueProvider
  #   include ActsAsTable::ValueProvider::InstanceMethods
  #   include ActsAsTable::ValueProviderAssociationMethods

  self.table_name = ActsAsTable.row_models_table

  acts_as_table_value_provider

  # Returns the root ActsAsTable record model for this ActsAsTable row model.
  belongs_to :root_record_model, **{
    class_name: 'ActsAsTable::RecordModel',
    inverse_of: :row_models_as_root,
    required: true,
  }

  # Returns the ActsAsTable column models for this ActsAsTable row model.
  has_many :column_models, -> { order(position: :asc) }, **{
    autosave: true,
    class_name: 'ActsAsTable::ColumnModel',
    dependent: :destroy,
    foreign_key: 'row_model_id',
    inverse_of: :row_model,
    validate: true,
  }

  # Returns the ActsAsTable singular macro associations for this ActsAsTable row model.
  has_many :belongs_tos, **{
    autosave: true,
    class_name: 'ActsAsTable::BelongsTo',
    dependent: :destroy,
    foreign_key: 'row_model_id',
    inverse_of: :row_model,
    validate: true,
  }

  # Returns the ActsAsTable collection macro associations for this ActsAsTable row model.
  has_many :has_manies, **{
    autosave: true,
    class_name: 'ActsAsTable::HasMany',
    dependent: :destroy,
    foreign_key: 'row_model_id',
    inverse_of: :row_model,
    validate: true,
  }

  # Returns the ActsAsTable record models for this ActsAsTable row model.
  has_many :record_models, **{
    autosave: true,
    class_name: 'ActsAsTable::RecordModel',
    dependent: :destroy,
    foreign_key: 'row_model_id',
    inverse_of: :row_model,
    validate: true,
  }

  # Returns the ActsAsTable tables that have been provided by this ActsAsTable row model.
  has_many :tables, **{
    class_name: 'ActsAsTable::Table',
    dependent: :restrict_with_exception,
    foreign_key: 'row_model_id',
    inverse_of: :row_model,
  }

  validates :name, **{
    presence: true,
  }

  validate :record_models_includes_root_record_model, **{
    if: ::Proc.new { |row_model| row_model.root_record_model.present? },
  }

  # Draw an ActsAsTable row model.
  #
  # @yieldparam [ActsAsTable::Mapper::RowModel] row_model_mapper
  # @yieldreturn [void]
  # @return [void]
  #
  # @see ActsAsTable::Mapper::RowModel
  def draw(&block)
    ActsAsTable::Mapper::RowModel.new(self, &block)

    return
  end

  # Returns the ActsAsTable headers array object for the ActsAsTable column models for this ActsAsTable row model.
  #
  # @return [ActsAsTable::Headers::Array]
  def to_headers
    ActsAsTable::Headers::Array.new(self.column_models)
  end

  # Returns the ActsAsTable records for the given row.
  #
  # @param [Array<String, nil>, nil] row
  # @param [ActiveRecord::Relation<ActsAsTable::Record>] records
  # @return [Array<ActsAsTable::Record>]
  # @raise [ArgumentError] If the name of a class for a given record does not match the class name for the corresponding ActsAsTable record model.
  def from_row(row = [], records = ActsAsTable::Record.all)
    # @return [Hash<ActsAsTable::RecordModel, Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>>]
    value_by_record_model_and_value_provider = self.record_models.inject({}) { |acc_for_record_model, record_model|
      acc_for_record_model[record_model] ||= record_model.each_acts_as_table_value_provider(nil, except: [:row_model, :row_models_as_root]).inject({}) { |acc_for_value_provider, value_provider|
        acc_for_value_provider[value_provider] ||= value_provider.column_model.try { |column_model|
          # @return [Integer]
          index = column_model.position - 1

          if (index >= 0) && (index < row.size)
            row[index]
          else
            nil
          end
        }

        acc_for_value_provider
      }

      acc_for_record_model
    }

    # @return [ActsAsTable::ValueProvider::WrappedValue]
    hash = ActsAsTable.adapter.set_value_for(self, nil, value_by_record_model_and_value_provider, default: true)

    hash.target_value.each_pair.collect { |pair|
      record_model, pair = *pair

      new_record_or_persisted, pair_by_value_provider = *pair

      records.build(record_model_changed: pair_by_value_provider.changed?) do |record|
        record.base = new_record_or_persisted

        record.record_model = record_model

        # @note {ActiveRecord::Validations#validate} is an alias for {ActiveRecord::Validations#valid?} that does not raise an exception when the record is invalid.
        new_record_or_persisted.validate

        # @return [Array<String>]
        attribute_names = []

        pair_by_value_provider.target_value.each do |value_provider, target_value|
          # @return [String]
          attribute_name = \
            case value_provider
            when ActsAsTable::ForeignKey
              klass = record_model.class_name.constantize

              reflection = klass.reflect_on_association(value_provider.method_name)

              reflection.foreign_key
            else
              value_provider.method_name
            end

          attribute_names << attribute_name

          record.values.build(target_value: target_value.target_value, value_provider_changed: target_value.changed?) do |value|
            value.value_provider = value_provider

            value_provider.column_model.try { |column_model|
              value.column_model = column_model

              value.position = column_model.position

              # @return [Integer]
              index = column_model.position - 1

              value.source_value = \
                if (index >= 0) && (index < row.size)
                  row[index]
                else
                  nil
                end
            }

            new_record_or_persisted.errors[attribute_name].each do |message|
              value.record_errors.build(attribute_name: attribute_name, message: message) do |record_error|
                record.record_errors.proxy_association.add_to_target(record_error)
              end
            end
          end
        end

        # new_record_or_persisted.errors.each do |attribute_name, message|
        #   unless attribute_names.include?(attribute_name.to_s)
        #     record.record_errors.build(attribute_name: attribute_name, message: message)
        #   end
        # end
      end
    }
  end

  # Returns the row for the given record.
  #
  # @param [ActiveRecord::Base, nil] base
  # @return [Array<String, nil>, nil]
  # @raise [ArgumentError]
  def to_row(base = nil)
    # @return [ActsAsTable::ValueProvider::WrappedValue]
    value_by_record_model_and_value_provider = ActsAsTable.adapter.get_value_for(self, base, default: false)

    # @return [Integer]
    column_models_maximum_position = (self.persisted? ? self.column_models.maximum(:position) : self.column_models.to_a.collect(&:position).max) || 0

    # @return [Array<String, nil>]
    row = ::Array.new(column_models_maximum_position) { nil }

    self.record_models.each do |record_model|
      # @return [ActsAsTable::ValueProvider::WrappedValue, nil]
      value_by_value_provider = value_by_record_model_and_value_provider.target_value.try(:[], record_model)

      record_model.each_acts_as_table_value_provider(nil, except: [:row_model, :row_models_as_root]) do |value_provider|
        value_provider.column_model.try { |column_model|
          # @return [Integer]
          index = column_model.position - 1

          if (index >= 0) && (index < column_models_maximum_position)
            # @return [ActsAsTable::ValueProvider::WrappedValue, nil]
            value = value_by_value_provider.try(:target_value).try(:[], value_provider)

            row[index] = value.try(:target_value)
          end
        }
      end
    end

    row.all?(&:nil?) ? nil : row
  end

  include ActsAsTable::RecordModelClassMethods

  # Returns `true` if the given ActsAsTable record model is reachable from the root ActsAsTable record model for this ActsAsTable row model. Otherwise, returns `false`.
  #
  # @param [ActsAsTable::RecordModel] record_model
  # @return [Boolean]
  def reachable_record_model?(record_model)
    self.class.reachable_record_model_for?(self.root_record_model, record_model)
  end

  # Returns the ActsAsTable record models that are reachable from the root ActsAsTable record model for this ActsAsTable row model (in topological order).
  #
  # @return [Array<ActsAsTable::RecordModel>]
  def reachable_record_models
    self.class.reachable_record_models_for(self.root_record_model)
  end

  # Get the value for the given record using the given options.
  #
  # @param [ActiveRecord::Base, nil] base
  # @param [Hash<Symbol, Object>] options
  # @option options [Boolean] :default
  # @return [ActsAsTable::ValueProvider::WrappedValue]
  # @raise [ArgumentError]
  def get_value(base = nil, **options)
    self.class.get_value_for(self.root_record_model, base, **options)
  end

  # Set the new value for the given record using the given options.
  #
  # @param [ActiveRecord::Base, nil] base
  # @param [Hash<ActsAsTable::RecordModel, Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>>] new_value_by_record_model_and_value_provider
  # @param [Hash<Symbol, Object>] options
  # @option options [Boolean] :default
  # @return [ActsAsTable::ValueProvider::WrappedValue]
  # @raise [ArgumentError]
  def set_value(base = nil, new_value_by_record_model_and_value_provider = {}, **options)
    self.class.set_value_for(self.root_record_model, base, new_value_by_record_model_and_value_provider, **options)
  end

  private

  # @return [void]
  def record_models_includes_root_record_model
    unless self.record_models.include?(self.root_record_model)
      self.errors.add('root_record_model_id', :inclusion)
    end

    return
  end
end

Instance Method Details

#belongs_tosActiveRecord::Relation<ActsAsTable::BelongsTo>

Returns the ActsAsTable singular macro associations for this ActsAsTable row model.

Returns:

See Also:



36
37
38
39
40
41
42
43
# File 'app/models/acts_as_table/row_model.rb', line 36

has_many :belongs_tos, **{
  autosave: true,
  class_name: 'ActsAsTable::BelongsTo',
  dependent: :destroy,
  foreign_key: 'row_model_id',
  inverse_of: :row_model,
  validate: true,
}

#column_modelsActiveRecord::Relation<ActsAsTable::ColumnModel>

Returns the ActsAsTable column models for this ActsAsTable row model.

Returns:

See Also:



26
27
28
29
30
31
32
33
# File 'app/models/acts_as_table/row_model.rb', line 26

has_many :column_models, -> { order(position: :asc) }, **{
  autosave: true,
  class_name: 'ActsAsTable::ColumnModel',
  dependent: :destroy,
  foreign_key: 'row_model_id',
  inverse_of: :row_model,
  validate: true,
}

#draw {|row_model_mapper| ... } ⇒ void

This method returns an undefined value.

Draw an ActsAsTable row model.

Yield Parameters:

Yield Returns:

  • (void)

See Also:



88
89
90
91
92
# File 'app/models/acts_as_table/row_model.rb', line 88

def draw(&block)
  ActsAsTable::Mapper::RowModel.new(self, &block)

  return
end

#from_row(row = [], records = ActsAsTable::Record.all) ⇒ Array<ActsAsTable::Record>

Returns the ActsAsTable records for the given row.

Parameters:

  • row (Array<String, nil>, nil) (defaults to: [])
  • records (ActiveRecord::Relation<ActsAsTable::Record>) (defaults to: ActsAsTable::Record.all)

Returns:

Raises:

  • (ArgumentError)

    If the name of a class for a given record does not match the class name for the corresponding ActsAsTable record model.



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'app/models/acts_as_table/row_model.rb', line 107

def from_row(row = [], records = ActsAsTable::Record.all)
  # @return [Hash<ActsAsTable::RecordModel, Hash<ActsAsTable::ValueProvider::InstanceMethods, Object>>]
  value_by_record_model_and_value_provider = self.record_models.inject({}) { |acc_for_record_model, record_model|
    acc_for_record_model[record_model] ||= record_model.each_acts_as_table_value_provider(nil, except: [:row_model, :row_models_as_root]).inject({}) { |acc_for_value_provider, value_provider|
      acc_for_value_provider[value_provider] ||= value_provider.column_model.try { |column_model|
        # @return [Integer]
        index = column_model.position - 1

        if (index >= 0) && (index < row.size)
          row[index]
        else
          nil
        end
      }

      acc_for_value_provider
    }

    acc_for_record_model
  }

  # @return [ActsAsTable::ValueProvider::WrappedValue]
  hash = ActsAsTable.adapter.set_value_for(self, nil, value_by_record_model_and_value_provider, default: true)

  hash.target_value.each_pair.collect { |pair|
    record_model, pair = *pair

    new_record_or_persisted, pair_by_value_provider = *pair

    records.build(record_model_changed: pair_by_value_provider.changed?) do |record|
      record.base = new_record_or_persisted

      record.record_model = record_model

      # @note {ActiveRecord::Validations#validate} is an alias for {ActiveRecord::Validations#valid?} that does not raise an exception when the record is invalid.
      new_record_or_persisted.validate

      # @return [Array<String>]
      attribute_names = []

      pair_by_value_provider.target_value.each do |value_provider, target_value|
        # @return [String]
        attribute_name = \
          case value_provider
          when ActsAsTable::ForeignKey
            klass = record_model.class_name.constantize

            reflection = klass.reflect_on_association(value_provider.method_name)

            reflection.foreign_key
          else
            value_provider.method_name
          end

        attribute_names << attribute_name

        record.values.build(target_value: target_value.target_value, value_provider_changed: target_value.changed?) do |value|
          value.value_provider = value_provider

          value_provider.column_model.try { |column_model|
            value.column_model = column_model

            value.position = column_model.position

            # @return [Integer]
            index = column_model.position - 1

            value.source_value = \
              if (index >= 0) && (index < row.size)
                row[index]
              else
                nil
              end
          }

          new_record_or_persisted.errors[attribute_name].each do |message|
            value.record_errors.build(attribute_name: attribute_name, message: message) do |record_error|
              record.record_errors.proxy_association.add_to_target(record_error)
            end
          end
        end
      end

      # new_record_or_persisted.errors.each do |attribute_name, message|
      #   unless attribute_names.include?(attribute_name.to_s)
      #     record.record_errors.build(attribute_name: attribute_name, message: message)
      #   end
      # end
    end
  }
end

#get_value(base = nil, **options) ⇒ ActsAsTable::ValueProvider::WrappedValue

Get the value for the given record using the given options.

Parameters:

  • base (ActiveRecord::Base, nil) (defaults to: nil)
  • options (Hash<Symbol, Object>)

Options Hash (**options):

  • :default (Boolean)

Returns:

Raises:

  • (ArgumentError)


260
261
262
# File 'app/models/acts_as_table/row_model.rb', line 260

def get_value(base = nil, **options)
  self.class.get_value_for(self.root_record_model, base, **options)
end

#has_maniesActiveRecord::Relation<ActsAsTable::HasMany>

Returns the ActsAsTable collection macro associations for this ActsAsTable row model.

Returns:

See Also:



46
47
48
49
50
51
52
53
# File 'app/models/acts_as_table/row_model.rb', line 46

has_many :has_manies, **{
  autosave: true,
  class_name: 'ActsAsTable::HasMany',
  dependent: :destroy,
  foreign_key: 'row_model_id',
  inverse_of: :row_model,
  validate: true,
}

#reachable_record_model?(record_model) ⇒ Boolean

Returns true if the given ActsAsTable record model is reachable from the root ActsAsTable record model for this ActsAsTable row model. Otherwise, returns false.

Parameters:

Returns:

  • (Boolean)


242
243
244
# File 'app/models/acts_as_table/row_model.rb', line 242

def reachable_record_model?(record_model)
  self.class.reachable_record_model_for?(self.root_record_model, record_model)
end

#reachable_record_modelsArray<ActsAsTable::RecordModel>

Returns the ActsAsTable record models that are reachable from the root ActsAsTable record model for this ActsAsTable row model (in topological order).

Returns:



249
250
251
# File 'app/models/acts_as_table/row_model.rb', line 249

def reachable_record_models
  self.class.reachable_record_models_for(self.root_record_model)
end

#record_modelsActiveRecord::Relation<ActsAsTable::RecordModel>

Returns the ActsAsTable record models for this ActsAsTable row model.

Returns:

See Also:



56
57
58
59
60
61
62
63
# File 'app/models/acts_as_table/row_model.rb', line 56

has_many :record_models, **{
  autosave: true,
  class_name: 'ActsAsTable::RecordModel',
  dependent: :destroy,
  foreign_key: 'row_model_id',
  inverse_of: :row_model,
  validate: true,
}

#root_record_modelActsAsTable::RecordModel

Returns the root ActsAsTable record model for this ActsAsTable row model.



19
20
21
22
23
# File 'app/models/acts_as_table/row_model.rb', line 19

belongs_to :root_record_model, **{
  class_name: 'ActsAsTable::RecordModel',
  inverse_of: :row_models_as_root,
  required: true,
}

#set_value(base = nil, new_value_by_record_model_and_value_provider = {}, **options) ⇒ ActsAsTable::ValueProvider::WrappedValue

Set the new value for the given record using the given options.

Parameters:

Options Hash (**options):

  • :default (Boolean)

Returns:

Raises:

  • (ArgumentError)


272
273
274
# File 'app/models/acts_as_table/row_model.rb', line 272

def set_value(base = nil, new_value_by_record_model_and_value_provider = {}, **options)
  self.class.set_value_for(self.root_record_model, base, new_value_by_record_model_and_value_provider, **options)
end

#tablesActiveRecord::Relation<ActsAsTable::Table>

Returns the ActsAsTable tables that have been provided by this ActsAsTable row model.

Returns:

See Also:



66
67
68
69
70
71
# File 'app/models/acts_as_table/row_model.rb', line 66

has_many :tables, **{
  class_name: 'ActsAsTable::Table',
  dependent: :restrict_with_exception,
  foreign_key: 'row_model_id',
  inverse_of: :row_model,
}

#to_headersActsAsTable::Headers::Array

Returns the ActsAsTable headers array object for the ActsAsTable column models for this ActsAsTable row model.



97
98
99
# File 'app/models/acts_as_table/row_model.rb', line 97

def to_headers
  ActsAsTable::Headers::Array.new(self.column_models)
end

#to_row(base = nil) ⇒ Array<String, nil>?

Returns the row for the given record.

Parameters:

  • base (ActiveRecord::Base, nil) (defaults to: nil)

Returns:

  • (Array<String, nil>, nil)

Raises:

  • (ArgumentError)


204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'app/models/acts_as_table/row_model.rb', line 204

def to_row(base = nil)
  # @return [ActsAsTable::ValueProvider::WrappedValue]
  value_by_record_model_and_value_provider = ActsAsTable.adapter.get_value_for(self, base, default: false)

  # @return [Integer]
  column_models_maximum_position = (self.persisted? ? self.column_models.maximum(:position) : self.column_models.to_a.collect(&:position).max) || 0

  # @return [Array<String, nil>]
  row = ::Array.new(column_models_maximum_position) { nil }

  self.record_models.each do |record_model|
    # @return [ActsAsTable::ValueProvider::WrappedValue, nil]
    value_by_value_provider = value_by_record_model_and_value_provider.target_value.try(:[], record_model)

    record_model.each_acts_as_table_value_provider(nil, except: [:row_model, :row_models_as_root]) do |value_provider|
      value_provider.column_model.try { |column_model|
        # @return [Integer]
        index = column_model.position - 1

        if (index >= 0) && (index < column_models_maximum_position)
          # @return [ActsAsTable::ValueProvider::WrappedValue, nil]
          value = value_by_value_provider.try(:target_value).try(:[], value_provider)

          row[index] = value.try(:target_value)
        end
      }
    end
  end

  row.all?(&:nil?) ? nil : row
end