Class: ObservationMatrixColumnItem

Inherits:
ApplicationRecord show all
Includes:
Housekeeping, Shared::Identifiers, Shared::IsData, Shared::Notes, Shared::Tags
Defined in:
app/models/observation_matrix_column_item.rb

Overview

Each ObservationMatrixColumnItem is set containing 1 or more descriptors.

Direct Known Subclasses

Dynamic, Single

Defined Under Namespace

Classes: Dynamic, Single

Constant Summary collapse

ALL_STI_ATTRIBUTES =
[:descriptor_id, :controlled_vocabulary_term_id].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Shared::IsData

#errors_excepting, #full_error_messages_excepting, #identical, #is_community?, #is_in_use?, #similar

Methods included from Shared::Tags

#reject_tags, #tag_with, #tagged?, #tagged_with?

Methods included from Shared::Notes

#concatenated_notes_string, #reject_notes

Methods included from Shared::Identifiers

#dwc_occurrence_id, #identified?, #next_by_identifier, #previous_by_identifier, #reject_identifiers, #uri, #uuid, #visible_identifiers_for

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#controlled_vocabulary_term_id ⇒ Integer

Returns id of the Keyword (a dynamic subclass).

Returns:

  • (Integer) —

    id of the Keyword (a dynamic subclass)



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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'app/models/observation_matrix_column_item.rb', line 18

class ObservationMatrixColumnItem < ApplicationRecord
  include Housekeeping
  include Shared::Identifiers
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:descriptor_id, :controlled_vocabulary_term_id].freeze

  belongs_to :observation_matrix, inverse_of: :observation_matrix_column_items

  # TODO: remove from subclasses WHY does this need to be here
  # belongs_to :descriptor, inverse_of: :observation_matrix_column_items

  # See above and corresponding questions
  # belongs_to :controlled_vocabulary_term, inverse_of: :observation_matrix_column_items

  validates_presence_of :observation_matrix
  validate :other_subclass_attributes_not_set, if: -> { !type.blank? }

  after_create :update_matrix_columns
  after_update :move_matrix_column, if: -> {
    saved_change_to_descriptor_id? ||
      saved_change_to_controlled_vocabulary_term_id? ||
      saved_change_to_observation_matrix_id?
  }
  after_destroy :cleanup_matrix_columns

  # @return [Array]
  #   of all objects this row references
  def column_objects
    objects = []

    objects.push *descriptors if descriptors
    objects
  end

  def cleanup_matrix_columns
    return true unless descriptors.size > 0
    ObservationMatrixColumn.where(descriptor_id: descriptors.map(&:id), observation_matrix: observation_matrix).each do |mc|
      cleanup_single_matrix_column(mc.descriptor_id, mc)
    end
    true
  end

  def update_matrix_columns
    descriptors.each do |d|
      update_single_matrix_column(d)
    end
  end

  # The item now covers other descriptors (e.g. unify moved it to the kept
  # descriptor or keyword) and/or is in another matrix: decrement the previous
  # descriptors' columns (in the previous matrix), increment the new ones
  def move_matrix_column
    previous_ids = descriptor_ids_before_last_save
    current = descriptors
    same_matrix =
      observation_matrix_id_before_last_save == observation_matrix_id

    if same_matrix # columns in both sets are left untouched
      unchanged_ids = previous_ids & current.map(&:id)
      previous_ids -= unchanged_ids
      current = current.reject { |d| unchanged_ids.include?(d.id) }
    end

    # One descriptor replaced by another: repoint a column that would
    # otherwise be deleted, so it keeps its position
    if same_matrix && previous_ids.size == 1 && current.size == 1
      mc = ObservationMatrixColumn.where(
        descriptor_id: previous_ids.first, observation_matrix_id:
      ).first

      if mc&.reference_count == 1 &&
          !ObservationMatrixColumn.where(
            descriptor_id: current.first.id, observation_matrix_id:
          ).exists?
        mc.update_columns(descriptor_id: current.first.id)
        return
      end
    end

    previous_ids.each do |id|
      mc = ObservationMatrixColumn.where(
        descriptor_id: id,
        observation_matrix_id: observation_matrix_id_before_last_save
      ).first
      decrement_matrix_column_reference_count(mc) if !mc.nil?
    end

    current.each do |d|
      update_single_matrix_column(d)
    end
  end

  def cleanup_single_matrix_column(descriptor_id, mc = nil)
    mc ||= ObservationMatrixColumn.where(
      descriptor_id: descriptor_id,
      observation_matrix: observation_matrix
    ).first
    decrement_matrix_column_reference_count(mc) if !mc.nil?
  end

  def decrement_matrix_column_reference_count(mc)
    current = mc.reference_count - 1
    if current == 0
      mc.delete
    else
      mc.update_columns(reference_count: current)
      mc.update_columns(cached_observation_matrix_column_item_id: nil) if current == 1 && type =~ /Single/ # we've deleted the only single, so the last must be a Dynamic/Tagged
    end
  end

  def find_or_build_column(descriptor)
    ObservationMatrixColumn.find_or_initialize_by(
      observation_matrix: observation_matrix,
      descriptor: descriptor)
  end

  # creates or finds and updates count, always
  def update_single_matrix_column(descriptor)
    mc = find_or_build_column(descriptor)
    mc.save! if !mc.persisted?

    increment_matrix_column_reference_count(mc)
  end

  def increment_matrix_column_reference_count(mc)
    mc.update_columns(reference_count: (mc.reference_count || 0) + 1)
    mc.update_columns(cached_observation_matrix_column_item_id: id) if type =~ /Single/
  end

  def self.human_name
    self.name.demodulize.humanize
  end

  # @return [Array]
  #    the required attributes for this subclass
  # override
  def self.subclass_attributes
    []
  end

  # @return [Array]
  #    the descriptors "defined" by this matrix column item
  # override
  def descriptors
    false
  end

  # @return [Array]
  #    ids of the descriptors "defined" by this matrix column item before its
  #    last save
  # override
  def descriptor_ids_before_last_save
    []
  end

  # @return [Array] of ObservationMatrixColumnItems
  def self.batch_create(params)
    case params[:batch_type]
    when 'tags'
      batch_create_from_tags(params[:keyword_id], params[:klass], params[:observation_matrix_id])
    when 'pinboard'
      batch_create_from_pinboard(params[:observation_matrix_id], params[:project_id], params[:user_id], params[:klass])
    when 'matrix_clone'
      batch_create_from_observation_matrix(params[:observation_matrix_id], params[:target_observation_matrix_id], params[:project_id], params[:user_id])
    when 'descriptor_id'
      batch_create_by_descriptor_id(params[:observation_matrix_id], params[:descriptor_id], params[:project_id], params[:user_id])

    when 'observation_matrix_column_item_id'
      batch_create_by_observation_matrix_column_item_id(params[:observation_matrix_id], params[:observation_matrix_column_item_id], params[:project_id], params[:user_id])
    end
  end

  # @return [Array, false]
  def self.batch_create_by_observation_matrix_column_item_id(observation_matrix_id, observation_matrix_column_item_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [observation_matrix_column_item_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          o = ObservationMatrixColumnItem.where(project_id: project_id).find(i)
          c = o.dup
          c.observation_matrix = matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_by_descriptor_id(observation_matrix_id, descriptor_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [descriptor_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          c = ObservationMatrixColumnItem::Single::Descriptor.create!(
            descriptor: Descriptor.where(project_id: project_id).find(i),
            observation_matrix_id: matrix.id,
            project_id: project_id,
            created_by_id: user_id)
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_from_observation_matrix(from_observation_matrix_id, to_observation_matrix_id, project_id, user_id )
    created = []

    from_matrix = ObservationMatrix.where(project_id: project_id).find(from_observation_matrix_id)
    to_matrix = ObservationMatrix.where(project_id: project_id).find(to_observation_matrix_id)

    return false if from_matrix.nil? || to_matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin

        from_matrix.observation_matrix_column_items.each do |oci|
          c = oci.dup
          c.observation_matrix = to_matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @param klass [String] the subclass of the Descriptor ike `Descriptor::Working` or `Descriptor::Continuous`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixColumnItem.transaction do
      begin
        if klass
          klass.constantize.joins(:tags).where(tags: {keyword_id: keyword_id } ).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_tags(
            Tag.where(
              keyword_id: keyword_id,
              tag_object_type: 'Descriptor').all,
             observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise # return false
      end
    end
    return created
  end

  # @param klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_pinboard(observation_matrix_id, project_id, user_id, klass)
    return false if observation_matrix_id.blank? || project_id.blank? || user_id.blank?
    created = []
    ObservationMatrixColumn.transaction do
      begin
        if klass
          klass.constantize.joins(:pinboard_items).where(pinboard_items: {user_id: user_id, project_id: project_id}).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_pinboard_items(
            PinboardItem.where(project_id: project_id, user_id: user_id, pinned_object_type: 'Descriptor').all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @return [Boolean]
  #   whether this is a dynamic or fixed class
  #   override in subclasses
  def is_dynamic?
    false
  end

  private

  # @return [Array]
  def self.create_for_tags(tag_scope, observation_matrix_id)
    a = []
    tag_scope.each do |o|
      a.push create_for(o.tag_object, observation_matrix_id)
    end
    a
  end

  # @param pinboard_item_scope [PinboardItem Scope]
  # @return [Array]
  #   create observation matrix column items for all scope items
  def self.create_for_pinboard_items(pinboard_item_scope, observation_matrix_id)
    a = []
    pinboard_item_scope.each do |o|
      a.push create_for(o.pinned_object, observation_matrix_id)
    end
    a
  end

  def self.create_for(object, observation_matrix_id)
    ObservationMatrixColumnItem::Single::Descriptor.create!(
      observation_matrix_id: observation_matrix_id,
      descriptor: object)
  end

  def other_subclass_attributes_not_set
    (ALL_STI_ATTRIBUTES - self.class.subclass_attributes).each do |atr|
      errors.add(atr, 'is not valid for this type of observation matrix column item') if !send(atr).blank?
    end
  end

end

#descriptor_id ⇒ Integer

Returns id of the descriptor (a single/static subclass).

Returns:

  • (Integer) —

    id of the descriptor (a single/static subclass)



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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'app/models/observation_matrix_column_item.rb', line 18

class ObservationMatrixColumnItem < ApplicationRecord
  include Housekeeping
  include Shared::Identifiers
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:descriptor_id, :controlled_vocabulary_term_id].freeze

  belongs_to :observation_matrix, inverse_of: :observation_matrix_column_items

  # TODO: remove from subclasses WHY does this need to be here
  # belongs_to :descriptor, inverse_of: :observation_matrix_column_items

  # See above and corresponding questions
  # belongs_to :controlled_vocabulary_term, inverse_of: :observation_matrix_column_items

  validates_presence_of :observation_matrix
  validate :other_subclass_attributes_not_set, if: -> { !type.blank? }

  after_create :update_matrix_columns
  after_update :move_matrix_column, if: -> {
    saved_change_to_descriptor_id? ||
      saved_change_to_controlled_vocabulary_term_id? ||
      saved_change_to_observation_matrix_id?
  }
  after_destroy :cleanup_matrix_columns

  # @return [Array]
  #   of all objects this row references
  def column_objects
    objects = []

    objects.push *descriptors if descriptors
    objects
  end

  def cleanup_matrix_columns
    return true unless descriptors.size > 0
    ObservationMatrixColumn.where(descriptor_id: descriptors.map(&:id), observation_matrix: observation_matrix).each do |mc|
      cleanup_single_matrix_column(mc.descriptor_id, mc)
    end
    true
  end

  def update_matrix_columns
    descriptors.each do |d|
      update_single_matrix_column(d)
    end
  end

  # The item now covers other descriptors (e.g. unify moved it to the kept
  # descriptor or keyword) and/or is in another matrix: decrement the previous
  # descriptors' columns (in the previous matrix), increment the new ones
  def move_matrix_column
    previous_ids = descriptor_ids_before_last_save
    current = descriptors
    same_matrix =
      observation_matrix_id_before_last_save == observation_matrix_id

    if same_matrix # columns in both sets are left untouched
      unchanged_ids = previous_ids & current.map(&:id)
      previous_ids -= unchanged_ids
      current = current.reject { |d| unchanged_ids.include?(d.id) }
    end

    # One descriptor replaced by another: repoint a column that would
    # otherwise be deleted, so it keeps its position
    if same_matrix && previous_ids.size == 1 && current.size == 1
      mc = ObservationMatrixColumn.where(
        descriptor_id: previous_ids.first, observation_matrix_id:
      ).first

      if mc&.reference_count == 1 &&
          !ObservationMatrixColumn.where(
            descriptor_id: current.first.id, observation_matrix_id:
          ).exists?
        mc.update_columns(descriptor_id: current.first.id)
        return
      end
    end

    previous_ids.each do |id|
      mc = ObservationMatrixColumn.where(
        descriptor_id: id,
        observation_matrix_id: observation_matrix_id_before_last_save
      ).first
      decrement_matrix_column_reference_count(mc) if !mc.nil?
    end

    current.each do |d|
      update_single_matrix_column(d)
    end
  end

  def cleanup_single_matrix_column(descriptor_id, mc = nil)
    mc ||= ObservationMatrixColumn.where(
      descriptor_id: descriptor_id,
      observation_matrix: observation_matrix
    ).first
    decrement_matrix_column_reference_count(mc) if !mc.nil?
  end

  def decrement_matrix_column_reference_count(mc)
    current = mc.reference_count - 1
    if current == 0
      mc.delete
    else
      mc.update_columns(reference_count: current)
      mc.update_columns(cached_observation_matrix_column_item_id: nil) if current == 1 && type =~ /Single/ # we've deleted the only single, so the last must be a Dynamic/Tagged
    end
  end

  def find_or_build_column(descriptor)
    ObservationMatrixColumn.find_or_initialize_by(
      observation_matrix: observation_matrix,
      descriptor: descriptor)
  end

  # creates or finds and updates count, always
  def update_single_matrix_column(descriptor)
    mc = find_or_build_column(descriptor)
    mc.save! if !mc.persisted?

    increment_matrix_column_reference_count(mc)
  end

  def increment_matrix_column_reference_count(mc)
    mc.update_columns(reference_count: (mc.reference_count || 0) + 1)
    mc.update_columns(cached_observation_matrix_column_item_id: id) if type =~ /Single/
  end

  def self.human_name
    self.name.demodulize.humanize
  end

  # @return [Array]
  #    the required attributes for this subclass
  # override
  def self.subclass_attributes
    []
  end

  # @return [Array]
  #    the descriptors "defined" by this matrix column item
  # override
  def descriptors
    false
  end

  # @return [Array]
  #    ids of the descriptors "defined" by this matrix column item before its
  #    last save
  # override
  def descriptor_ids_before_last_save
    []
  end

  # @return [Array] of ObservationMatrixColumnItems
  def self.batch_create(params)
    case params[:batch_type]
    when 'tags'
      batch_create_from_tags(params[:keyword_id], params[:klass], params[:observation_matrix_id])
    when 'pinboard'
      batch_create_from_pinboard(params[:observation_matrix_id], params[:project_id], params[:user_id], params[:klass])
    when 'matrix_clone'
      batch_create_from_observation_matrix(params[:observation_matrix_id], params[:target_observation_matrix_id], params[:project_id], params[:user_id])
    when 'descriptor_id'
      batch_create_by_descriptor_id(params[:observation_matrix_id], params[:descriptor_id], params[:project_id], params[:user_id])

    when 'observation_matrix_column_item_id'
      batch_create_by_observation_matrix_column_item_id(params[:observation_matrix_id], params[:observation_matrix_column_item_id], params[:project_id], params[:user_id])
    end
  end

  # @return [Array, false]
  def self.batch_create_by_observation_matrix_column_item_id(observation_matrix_id, observation_matrix_column_item_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [observation_matrix_column_item_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          o = ObservationMatrixColumnItem.where(project_id: project_id).find(i)
          c = o.dup
          c.observation_matrix = matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_by_descriptor_id(observation_matrix_id, descriptor_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [descriptor_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          c = ObservationMatrixColumnItem::Single::Descriptor.create!(
            descriptor: Descriptor.where(project_id: project_id).find(i),
            observation_matrix_id: matrix.id,
            project_id: project_id,
            created_by_id: user_id)
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_from_observation_matrix(from_observation_matrix_id, to_observation_matrix_id, project_id, user_id )
    created = []

    from_matrix = ObservationMatrix.where(project_id: project_id).find(from_observation_matrix_id)
    to_matrix = ObservationMatrix.where(project_id: project_id).find(to_observation_matrix_id)

    return false if from_matrix.nil? || to_matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin

        from_matrix.observation_matrix_column_items.each do |oci|
          c = oci.dup
          c.observation_matrix = to_matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @param klass [String] the subclass of the Descriptor ike `Descriptor::Working` or `Descriptor::Continuous`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixColumnItem.transaction do
      begin
        if klass
          klass.constantize.joins(:tags).where(tags: {keyword_id: keyword_id } ).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_tags(
            Tag.where(
              keyword_id: keyword_id,
              tag_object_type: 'Descriptor').all,
             observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise # return false
      end
    end
    return created
  end

  # @param klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_pinboard(observation_matrix_id, project_id, user_id, klass)
    return false if observation_matrix_id.blank? || project_id.blank? || user_id.blank?
    created = []
    ObservationMatrixColumn.transaction do
      begin
        if klass
          klass.constantize.joins(:pinboard_items).where(pinboard_items: {user_id: user_id, project_id: project_id}).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_pinboard_items(
            PinboardItem.where(project_id: project_id, user_id: user_id, pinned_object_type: 'Descriptor').all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @return [Boolean]
  #   whether this is a dynamic or fixed class
  #   override in subclasses
  def is_dynamic?
    false
  end

  private

  # @return [Array]
  def self.create_for_tags(tag_scope, observation_matrix_id)
    a = []
    tag_scope.each do |o|
      a.push create_for(o.tag_object, observation_matrix_id)
    end
    a
  end

  # @param pinboard_item_scope [PinboardItem Scope]
  # @return [Array]
  #   create observation matrix column items for all scope items
  def self.create_for_pinboard_items(pinboard_item_scope, observation_matrix_id)
    a = []
    pinboard_item_scope.each do |o|
      a.push create_for(o.pinned_object, observation_matrix_id)
    end
    a
  end

  def self.create_for(object, observation_matrix_id)
    ObservationMatrixColumnItem::Single::Descriptor.create!(
      observation_matrix_id: observation_matrix_id,
      descriptor: object)
  end

  def other_subclass_attributes_not_set
    (ALL_STI_ATTRIBUTES - self.class.subclass_attributes).each do |atr|
      errors.add(atr, 'is not valid for this type of observation matrix column item') if !send(atr).blank?
    end
  end

end

#observation_matrix_id ⇒ Integer

Returns id of the matrix.

Returns:

  • (Integer) —

    id of the matrix



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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'app/models/observation_matrix_column_item.rb', line 18

class ObservationMatrixColumnItem < ApplicationRecord
  include Housekeeping
  include Shared::Identifiers
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:descriptor_id, :controlled_vocabulary_term_id].freeze

  belongs_to :observation_matrix, inverse_of: :observation_matrix_column_items

  # TODO: remove from subclasses WHY does this need to be here
  # belongs_to :descriptor, inverse_of: :observation_matrix_column_items

  # See above and corresponding questions
  # belongs_to :controlled_vocabulary_term, inverse_of: :observation_matrix_column_items

  validates_presence_of :observation_matrix
  validate :other_subclass_attributes_not_set, if: -> { !type.blank? }

  after_create :update_matrix_columns
  after_update :move_matrix_column, if: -> {
    saved_change_to_descriptor_id? ||
      saved_change_to_controlled_vocabulary_term_id? ||
      saved_change_to_observation_matrix_id?
  }
  after_destroy :cleanup_matrix_columns

  # @return [Array]
  #   of all objects this row references
  def column_objects
    objects = []

    objects.push *descriptors if descriptors
    objects
  end

  def cleanup_matrix_columns
    return true unless descriptors.size > 0
    ObservationMatrixColumn.where(descriptor_id: descriptors.map(&:id), observation_matrix: observation_matrix).each do |mc|
      cleanup_single_matrix_column(mc.descriptor_id, mc)
    end
    true
  end

  def update_matrix_columns
    descriptors.each do |d|
      update_single_matrix_column(d)
    end
  end

  # The item now covers other descriptors (e.g. unify moved it to the kept
  # descriptor or keyword) and/or is in another matrix: decrement the previous
  # descriptors' columns (in the previous matrix), increment the new ones
  def move_matrix_column
    previous_ids = descriptor_ids_before_last_save
    current = descriptors
    same_matrix =
      observation_matrix_id_before_last_save == observation_matrix_id

    if same_matrix # columns in both sets are left untouched
      unchanged_ids = previous_ids & current.map(&:id)
      previous_ids -= unchanged_ids
      current = current.reject { |d| unchanged_ids.include?(d.id) }
    end

    # One descriptor replaced by another: repoint a column that would
    # otherwise be deleted, so it keeps its position
    if same_matrix && previous_ids.size == 1 && current.size == 1
      mc = ObservationMatrixColumn.where(
        descriptor_id: previous_ids.first, observation_matrix_id:
      ).first

      if mc&.reference_count == 1 &&
          !ObservationMatrixColumn.where(
            descriptor_id: current.first.id, observation_matrix_id:
          ).exists?
        mc.update_columns(descriptor_id: current.first.id)
        return
      end
    end

    previous_ids.each do |id|
      mc = ObservationMatrixColumn.where(
        descriptor_id: id,
        observation_matrix_id: observation_matrix_id_before_last_save
      ).first
      decrement_matrix_column_reference_count(mc) if !mc.nil?
    end

    current.each do |d|
      update_single_matrix_column(d)
    end
  end

  def cleanup_single_matrix_column(descriptor_id, mc = nil)
    mc ||= ObservationMatrixColumn.where(
      descriptor_id: descriptor_id,
      observation_matrix: observation_matrix
    ).first
    decrement_matrix_column_reference_count(mc) if !mc.nil?
  end

  def decrement_matrix_column_reference_count(mc)
    current = mc.reference_count - 1
    if current == 0
      mc.delete
    else
      mc.update_columns(reference_count: current)
      mc.update_columns(cached_observation_matrix_column_item_id: nil) if current == 1 && type =~ /Single/ # we've deleted the only single, so the last must be a Dynamic/Tagged
    end
  end

  def find_or_build_column(descriptor)
    ObservationMatrixColumn.find_or_initialize_by(
      observation_matrix: observation_matrix,
      descriptor: descriptor)
  end

  # creates or finds and updates count, always
  def update_single_matrix_column(descriptor)
    mc = find_or_build_column(descriptor)
    mc.save! if !mc.persisted?

    increment_matrix_column_reference_count(mc)
  end

  def increment_matrix_column_reference_count(mc)
    mc.update_columns(reference_count: (mc.reference_count || 0) + 1)
    mc.update_columns(cached_observation_matrix_column_item_id: id) if type =~ /Single/
  end

  def self.human_name
    self.name.demodulize.humanize
  end

  # @return [Array]
  #    the required attributes for this subclass
  # override
  def self.subclass_attributes
    []
  end

  # @return [Array]
  #    the descriptors "defined" by this matrix column item
  # override
  def descriptors
    false
  end

  # @return [Array]
  #    ids of the descriptors "defined" by this matrix column item before its
  #    last save
  # override
  def descriptor_ids_before_last_save
    []
  end

  # @return [Array] of ObservationMatrixColumnItems
  def self.batch_create(params)
    case params[:batch_type]
    when 'tags'
      batch_create_from_tags(params[:keyword_id], params[:klass], params[:observation_matrix_id])
    when 'pinboard'
      batch_create_from_pinboard(params[:observation_matrix_id], params[:project_id], params[:user_id], params[:klass])
    when 'matrix_clone'
      batch_create_from_observation_matrix(params[:observation_matrix_id], params[:target_observation_matrix_id], params[:project_id], params[:user_id])
    when 'descriptor_id'
      batch_create_by_descriptor_id(params[:observation_matrix_id], params[:descriptor_id], params[:project_id], params[:user_id])

    when 'observation_matrix_column_item_id'
      batch_create_by_observation_matrix_column_item_id(params[:observation_matrix_id], params[:observation_matrix_column_item_id], params[:project_id], params[:user_id])
    end
  end

  # @return [Array, false]
  def self.batch_create_by_observation_matrix_column_item_id(observation_matrix_id, observation_matrix_column_item_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [observation_matrix_column_item_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          o = ObservationMatrixColumnItem.where(project_id: project_id).find(i)
          c = o.dup
          c.observation_matrix = matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_by_descriptor_id(observation_matrix_id, descriptor_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [descriptor_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          c = ObservationMatrixColumnItem::Single::Descriptor.create!(
            descriptor: Descriptor.where(project_id: project_id).find(i),
            observation_matrix_id: matrix.id,
            project_id: project_id,
            created_by_id: user_id)
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_from_observation_matrix(from_observation_matrix_id, to_observation_matrix_id, project_id, user_id )
    created = []

    from_matrix = ObservationMatrix.where(project_id: project_id).find(from_observation_matrix_id)
    to_matrix = ObservationMatrix.where(project_id: project_id).find(to_observation_matrix_id)

    return false if from_matrix.nil? || to_matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin

        from_matrix.observation_matrix_column_items.each do |oci|
          c = oci.dup
          c.observation_matrix = to_matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @param klass [String] the subclass of the Descriptor ike `Descriptor::Working` or `Descriptor::Continuous`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixColumnItem.transaction do
      begin
        if klass
          klass.constantize.joins(:tags).where(tags: {keyword_id: keyword_id } ).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_tags(
            Tag.where(
              keyword_id: keyword_id,
              tag_object_type: 'Descriptor').all,
             observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise # return false
      end
    end
    return created
  end

  # @param klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_pinboard(observation_matrix_id, project_id, user_id, klass)
    return false if observation_matrix_id.blank? || project_id.blank? || user_id.blank?
    created = []
    ObservationMatrixColumn.transaction do
      begin
        if klass
          klass.constantize.joins(:pinboard_items).where(pinboard_items: {user_id: user_id, project_id: project_id}).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_pinboard_items(
            PinboardItem.where(project_id: project_id, user_id: user_id, pinned_object_type: 'Descriptor').all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @return [Boolean]
  #   whether this is a dynamic or fixed class
  #   override in subclasses
  def is_dynamic?
    false
  end

  private

  # @return [Array]
  def self.create_for_tags(tag_scope, observation_matrix_id)
    a = []
    tag_scope.each do |o|
      a.push create_for(o.tag_object, observation_matrix_id)
    end
    a
  end

  # @param pinboard_item_scope [PinboardItem Scope]
  # @return [Array]
  #   create observation matrix column items for all scope items
  def self.create_for_pinboard_items(pinboard_item_scope, observation_matrix_id)
    a = []
    pinboard_item_scope.each do |o|
      a.push create_for(o.pinned_object, observation_matrix_id)
    end
    a
  end

  def self.create_for(object, observation_matrix_id)
    ObservationMatrixColumnItem::Single::Descriptor.create!(
      observation_matrix_id: observation_matrix_id,
      descriptor: object)
  end

  def other_subclass_attributes_not_set
    (ALL_STI_ATTRIBUTES - self.class.subclass_attributes).each do |atr|
      errors.add(atr, 'is not valid for this type of observation matrix column item') if !send(atr).blank?
    end
  end

end

#position ⇒ Integer

Returns a sort order.

Returns:

  • (Integer) —

    a sort order



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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'app/models/observation_matrix_column_item.rb', line 18

class ObservationMatrixColumnItem < ApplicationRecord
  include Housekeeping
  include Shared::Identifiers
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:descriptor_id, :controlled_vocabulary_term_id].freeze

  belongs_to :observation_matrix, inverse_of: :observation_matrix_column_items

  # TODO: remove from subclasses WHY does this need to be here
  # belongs_to :descriptor, inverse_of: :observation_matrix_column_items

  # See above and corresponding questions
  # belongs_to :controlled_vocabulary_term, inverse_of: :observation_matrix_column_items

  validates_presence_of :observation_matrix
  validate :other_subclass_attributes_not_set, if: -> { !type.blank? }

  after_create :update_matrix_columns
  after_update :move_matrix_column, if: -> {
    saved_change_to_descriptor_id? ||
      saved_change_to_controlled_vocabulary_term_id? ||
      saved_change_to_observation_matrix_id?
  }
  after_destroy :cleanup_matrix_columns

  # @return [Array]
  #   of all objects this row references
  def column_objects
    objects = []

    objects.push *descriptors if descriptors
    objects
  end

  def cleanup_matrix_columns
    return true unless descriptors.size > 0
    ObservationMatrixColumn.where(descriptor_id: descriptors.map(&:id), observation_matrix: observation_matrix).each do |mc|
      cleanup_single_matrix_column(mc.descriptor_id, mc)
    end
    true
  end

  def update_matrix_columns
    descriptors.each do |d|
      update_single_matrix_column(d)
    end
  end

  # The item now covers other descriptors (e.g. unify moved it to the kept
  # descriptor or keyword) and/or is in another matrix: decrement the previous
  # descriptors' columns (in the previous matrix), increment the new ones
  def move_matrix_column
    previous_ids = descriptor_ids_before_last_save
    current = descriptors
    same_matrix =
      observation_matrix_id_before_last_save == observation_matrix_id

    if same_matrix # columns in both sets are left untouched
      unchanged_ids = previous_ids & current.map(&:id)
      previous_ids -= unchanged_ids
      current = current.reject { |d| unchanged_ids.include?(d.id) }
    end

    # One descriptor replaced by another: repoint a column that would
    # otherwise be deleted, so it keeps its position
    if same_matrix && previous_ids.size == 1 && current.size == 1
      mc = ObservationMatrixColumn.where(
        descriptor_id: previous_ids.first, observation_matrix_id:
      ).first

      if mc&.reference_count == 1 &&
          !ObservationMatrixColumn.where(
            descriptor_id: current.first.id, observation_matrix_id:
          ).exists?
        mc.update_columns(descriptor_id: current.first.id)
        return
      end
    end

    previous_ids.each do |id|
      mc = ObservationMatrixColumn.where(
        descriptor_id: id,
        observation_matrix_id: observation_matrix_id_before_last_save
      ).first
      decrement_matrix_column_reference_count(mc) if !mc.nil?
    end

    current.each do |d|
      update_single_matrix_column(d)
    end
  end

  def cleanup_single_matrix_column(descriptor_id, mc = nil)
    mc ||= ObservationMatrixColumn.where(
      descriptor_id: descriptor_id,
      observation_matrix: observation_matrix
    ).first
    decrement_matrix_column_reference_count(mc) if !mc.nil?
  end

  def decrement_matrix_column_reference_count(mc)
    current = mc.reference_count - 1
    if current == 0
      mc.delete
    else
      mc.update_columns(reference_count: current)
      mc.update_columns(cached_observation_matrix_column_item_id: nil) if current == 1 && type =~ /Single/ # we've deleted the only single, so the last must be a Dynamic/Tagged
    end
  end

  def find_or_build_column(descriptor)
    ObservationMatrixColumn.find_or_initialize_by(
      observation_matrix: observation_matrix,
      descriptor: descriptor)
  end

  # creates or finds and updates count, always
  def update_single_matrix_column(descriptor)
    mc = find_or_build_column(descriptor)
    mc.save! if !mc.persisted?

    increment_matrix_column_reference_count(mc)
  end

  def increment_matrix_column_reference_count(mc)
    mc.update_columns(reference_count: (mc.reference_count || 0) + 1)
    mc.update_columns(cached_observation_matrix_column_item_id: id) if type =~ /Single/
  end

  def self.human_name
    self.name.demodulize.humanize
  end

  # @return [Array]
  #    the required attributes for this subclass
  # override
  def self.subclass_attributes
    []
  end

  # @return [Array]
  #    the descriptors "defined" by this matrix column item
  # override
  def descriptors
    false
  end

  # @return [Array]
  #    ids of the descriptors "defined" by this matrix column item before its
  #    last save
  # override
  def descriptor_ids_before_last_save
    []
  end

  # @return [Array] of ObservationMatrixColumnItems
  def self.batch_create(params)
    case params[:batch_type]
    when 'tags'
      batch_create_from_tags(params[:keyword_id], params[:klass], params[:observation_matrix_id])
    when 'pinboard'
      batch_create_from_pinboard(params[:observation_matrix_id], params[:project_id], params[:user_id], params[:klass])
    when 'matrix_clone'
      batch_create_from_observation_matrix(params[:observation_matrix_id], params[:target_observation_matrix_id], params[:project_id], params[:user_id])
    when 'descriptor_id'
      batch_create_by_descriptor_id(params[:observation_matrix_id], params[:descriptor_id], params[:project_id], params[:user_id])

    when 'observation_matrix_column_item_id'
      batch_create_by_observation_matrix_column_item_id(params[:observation_matrix_id], params[:observation_matrix_column_item_id], params[:project_id], params[:user_id])
    end
  end

  # @return [Array, false]
  def self.batch_create_by_observation_matrix_column_item_id(observation_matrix_id, observation_matrix_column_item_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [observation_matrix_column_item_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          o = ObservationMatrixColumnItem.where(project_id: project_id).find(i)
          c = o.dup
          c.observation_matrix = matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_by_descriptor_id(observation_matrix_id, descriptor_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [descriptor_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          c = ObservationMatrixColumnItem::Single::Descriptor.create!(
            descriptor: Descriptor.where(project_id: project_id).find(i),
            observation_matrix_id: matrix.id,
            project_id: project_id,
            created_by_id: user_id)
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_from_observation_matrix(from_observation_matrix_id, to_observation_matrix_id, project_id, user_id )
    created = []

    from_matrix = ObservationMatrix.where(project_id: project_id).find(from_observation_matrix_id)
    to_matrix = ObservationMatrix.where(project_id: project_id).find(to_observation_matrix_id)

    return false if from_matrix.nil? || to_matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin

        from_matrix.observation_matrix_column_items.each do |oci|
          c = oci.dup
          c.observation_matrix = to_matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @param klass [String] the subclass of the Descriptor ike `Descriptor::Working` or `Descriptor::Continuous`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixColumnItem.transaction do
      begin
        if klass
          klass.constantize.joins(:tags).where(tags: {keyword_id: keyword_id } ).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_tags(
            Tag.where(
              keyword_id: keyword_id,
              tag_object_type: 'Descriptor').all,
             observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise # return false
      end
    end
    return created
  end

  # @param klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_pinboard(observation_matrix_id, project_id, user_id, klass)
    return false if observation_matrix_id.blank? || project_id.blank? || user_id.blank?
    created = []
    ObservationMatrixColumn.transaction do
      begin
        if klass
          klass.constantize.joins(:pinboard_items).where(pinboard_items: {user_id: user_id, project_id: project_id}).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_pinboard_items(
            PinboardItem.where(project_id: project_id, user_id: user_id, pinned_object_type: 'Descriptor').all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @return [Boolean]
  #   whether this is a dynamic or fixed class
  #   override in subclasses
  def is_dynamic?
    false
  end

  private

  # @return [Array]
  def self.create_for_tags(tag_scope, observation_matrix_id)
    a = []
    tag_scope.each do |o|
      a.push create_for(o.tag_object, observation_matrix_id)
    end
    a
  end

  # @param pinboard_item_scope [PinboardItem Scope]
  # @return [Array]
  #   create observation matrix column items for all scope items
  def self.create_for_pinboard_items(pinboard_item_scope, observation_matrix_id)
    a = []
    pinboard_item_scope.each do |o|
      a.push create_for(o.pinned_object, observation_matrix_id)
    end
    a
  end

  def self.create_for(object, observation_matrix_id)
    ObservationMatrixColumnItem::Single::Descriptor.create!(
      observation_matrix_id: observation_matrix_id,
      descriptor: object)
  end

  def other_subclass_attributes_not_set
    (ALL_STI_ATTRIBUTES - self.class.subclass_attributes).each do |atr|
      errors.add(atr, 'is not valid for this type of observation matrix column item') if !send(atr).blank?
    end
  end

end

#type ⇒ String

Returns the column type.

Returns:

  • (String) —

    the column type



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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'app/models/observation_matrix_column_item.rb', line 18

class ObservationMatrixColumnItem < ApplicationRecord
  include Housekeeping
  include Shared::Identifiers
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:descriptor_id, :controlled_vocabulary_term_id].freeze

  belongs_to :observation_matrix, inverse_of: :observation_matrix_column_items

  # TODO: remove from subclasses WHY does this need to be here
  # belongs_to :descriptor, inverse_of: :observation_matrix_column_items

  # See above and corresponding questions
  # belongs_to :controlled_vocabulary_term, inverse_of: :observation_matrix_column_items

  validates_presence_of :observation_matrix
  validate :other_subclass_attributes_not_set, if: -> { !type.blank? }

  after_create :update_matrix_columns
  after_update :move_matrix_column, if: -> {
    saved_change_to_descriptor_id? ||
      saved_change_to_controlled_vocabulary_term_id? ||
      saved_change_to_observation_matrix_id?
  }
  after_destroy :cleanup_matrix_columns

  # @return [Array]
  #   of all objects this row references
  def column_objects
    objects = []

    objects.push *descriptors if descriptors
    objects
  end

  def cleanup_matrix_columns
    return true unless descriptors.size > 0
    ObservationMatrixColumn.where(descriptor_id: descriptors.map(&:id), observation_matrix: observation_matrix).each do |mc|
      cleanup_single_matrix_column(mc.descriptor_id, mc)
    end
    true
  end

  def update_matrix_columns
    descriptors.each do |d|
      update_single_matrix_column(d)
    end
  end

  # The item now covers other descriptors (e.g. unify moved it to the kept
  # descriptor or keyword) and/or is in another matrix: decrement the previous
  # descriptors' columns (in the previous matrix), increment the new ones
  def move_matrix_column
    previous_ids = descriptor_ids_before_last_save
    current = descriptors
    same_matrix =
      observation_matrix_id_before_last_save == observation_matrix_id

    if same_matrix # columns in both sets are left untouched
      unchanged_ids = previous_ids & current.map(&:id)
      previous_ids -= unchanged_ids
      current = current.reject { |d| unchanged_ids.include?(d.id) }
    end

    # One descriptor replaced by another: repoint a column that would
    # otherwise be deleted, so it keeps its position
    if same_matrix && previous_ids.size == 1 && current.size == 1
      mc = ObservationMatrixColumn.where(
        descriptor_id: previous_ids.first, observation_matrix_id:
      ).first

      if mc&.reference_count == 1 &&
          !ObservationMatrixColumn.where(
            descriptor_id: current.first.id, observation_matrix_id:
          ).exists?
        mc.update_columns(descriptor_id: current.first.id)
        return
      end
    end

    previous_ids.each do |id|
      mc = ObservationMatrixColumn.where(
        descriptor_id: id,
        observation_matrix_id: observation_matrix_id_before_last_save
      ).first
      decrement_matrix_column_reference_count(mc) if !mc.nil?
    end

    current.each do |d|
      update_single_matrix_column(d)
    end
  end

  def cleanup_single_matrix_column(descriptor_id, mc = nil)
    mc ||= ObservationMatrixColumn.where(
      descriptor_id: descriptor_id,
      observation_matrix: observation_matrix
    ).first
    decrement_matrix_column_reference_count(mc) if !mc.nil?
  end

  def decrement_matrix_column_reference_count(mc)
    current = mc.reference_count - 1
    if current == 0
      mc.delete
    else
      mc.update_columns(reference_count: current)
      mc.update_columns(cached_observation_matrix_column_item_id: nil) if current == 1 && type =~ /Single/ # we've deleted the only single, so the last must be a Dynamic/Tagged
    end
  end

  def find_or_build_column(descriptor)
    ObservationMatrixColumn.find_or_initialize_by(
      observation_matrix: observation_matrix,
      descriptor: descriptor)
  end

  # creates or finds and updates count, always
  def update_single_matrix_column(descriptor)
    mc = find_or_build_column(descriptor)
    mc.save! if !mc.persisted?

    increment_matrix_column_reference_count(mc)
  end

  def increment_matrix_column_reference_count(mc)
    mc.update_columns(reference_count: (mc.reference_count || 0) + 1)
    mc.update_columns(cached_observation_matrix_column_item_id: id) if type =~ /Single/
  end

  def self.human_name
    self.name.demodulize.humanize
  end

  # @return [Array]
  #    the required attributes for this subclass
  # override
  def self.subclass_attributes
    []
  end

  # @return [Array]
  #    the descriptors "defined" by this matrix column item
  # override
  def descriptors
    false
  end

  # @return [Array]
  #    ids of the descriptors "defined" by this matrix column item before its
  #    last save
  # override
  def descriptor_ids_before_last_save
    []
  end

  # @return [Array] of ObservationMatrixColumnItems
  def self.batch_create(params)
    case params[:batch_type]
    when 'tags'
      batch_create_from_tags(params[:keyword_id], params[:klass], params[:observation_matrix_id])
    when 'pinboard'
      batch_create_from_pinboard(params[:observation_matrix_id], params[:project_id], params[:user_id], params[:klass])
    when 'matrix_clone'
      batch_create_from_observation_matrix(params[:observation_matrix_id], params[:target_observation_matrix_id], params[:project_id], params[:user_id])
    when 'descriptor_id'
      batch_create_by_descriptor_id(params[:observation_matrix_id], params[:descriptor_id], params[:project_id], params[:user_id])

    when 'observation_matrix_column_item_id'
      batch_create_by_observation_matrix_column_item_id(params[:observation_matrix_id], params[:observation_matrix_column_item_id], params[:project_id], params[:user_id])
    end
  end

  # @return [Array, false]
  def self.batch_create_by_observation_matrix_column_item_id(observation_matrix_id, observation_matrix_column_item_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [observation_matrix_column_item_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          o = ObservationMatrixColumnItem.where(project_id: project_id).find(i)
          c = o.dup
          c.observation_matrix = matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_by_descriptor_id(observation_matrix_id, descriptor_id, project_id, user_id )
    created = []
    matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
    ids = [descriptor_id].flatten.compact
    return false if matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin
        ids.each do |i|
          c = ObservationMatrixColumnItem::Single::Descriptor.create!(
            descriptor: Descriptor.where(project_id: project_id).find(i),
            observation_matrix_id: matrix.id,
            project_id: project_id,
            created_by_id: user_id)
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @return [Array, false]
  def self.batch_create_from_observation_matrix(from_observation_matrix_id, to_observation_matrix_id, project_id, user_id )
    created = []

    from_matrix = ObservationMatrix.where(project_id: project_id).find(from_observation_matrix_id)
    to_matrix = ObservationMatrix.where(project_id: project_id).find(to_observation_matrix_id)

    return false if from_matrix.nil? || to_matrix.nil?

    ObservationMatrixColumnItem.transaction do
      begin

        from_matrix.observation_matrix_column_items.each do |oci|
          c = oci.dup
          c.observation_matrix = to_matrix
          c.created_by_id = user_id
          c.save!
          created.push c
        end
      rescue ActiveRecord::RecordInvalid => e
        next
      end
    end
    return created
  end

  # @param klass [String] the subclass of the Descriptor ike `Descriptor::Working` or `Descriptor::Continuous`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixColumnItem.transaction do
      begin
        if klass
          klass.constantize.joins(:tags).where(tags: {keyword_id: keyword_id } ).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_tags(
            Tag.where(
              keyword_id: keyword_id,
              tag_object_type: 'Descriptor').all,
             observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise # return false
      end
    end
    return created
  end

  # @param klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_pinboard(observation_matrix_id, project_id, user_id, klass)
    return false if observation_matrix_id.blank? || project_id.blank? || user_id.blank?
    created = []
    ObservationMatrixColumn.transaction do
      begin
        if klass
          klass.constantize.joins(:pinboard_items).where(pinboard_items: {user_id: user_id, project_id: project_id}).each do |o|
            created.push create_for(o, observation_matrix_id)
          end
        else
          created += create_for_pinboard_items(
            PinboardItem.where(project_id: project_id, user_id: user_id, pinned_object_type: 'Descriptor').all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @return [Boolean]
  #   whether this is a dynamic or fixed class
  #   override in subclasses
  def is_dynamic?
    false
  end

  private

  # @return [Array]
  def self.create_for_tags(tag_scope, observation_matrix_id)
    a = []
    tag_scope.each do |o|
      a.push create_for(o.tag_object, observation_matrix_id)
    end
    a
  end

  # @param pinboard_item_scope [PinboardItem Scope]
  # @return [Array]
  #   create observation matrix column items for all scope items
  def self.create_for_pinboard_items(pinboard_item_scope, observation_matrix_id)
    a = []
    pinboard_item_scope.each do |o|
      a.push create_for(o.pinned_object, observation_matrix_id)
    end
    a
  end

  def self.create_for(object, observation_matrix_id)
    ObservationMatrixColumnItem::Single::Descriptor.create!(
      observation_matrix_id: observation_matrix_id,
      descriptor: object)
  end

  def other_subclass_attributes_not_set
    (ALL_STI_ATTRIBUTES - self.class.subclass_attributes).each do |atr|
      errors.add(atr, 'is not valid for this type of observation matrix column item') if !send(atr).blank?
    end
  end

end

Class Method Details

.batch_create(params) ⇒ Array

Returns of ObservationMatrixColumnItems.

Returns:

  • (Array) —

    of ObservationMatrixColumnItems



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'app/models/observation_matrix_column_item.rb', line 179

def self.batch_create(params)
  case params[:batch_type]
  when 'tags'
    batch_create_from_tags(params[:keyword_id], params[:klass], params[:observation_matrix_id])
  when 'pinboard'
    batch_create_from_pinboard(params[:observation_matrix_id], params[:project_id], params[:user_id], params[:klass])
  when 'matrix_clone'
    batch_create_from_observation_matrix(params[:observation_matrix_id], params[:target_observation_matrix_id], params[:project_id], params[:user_id])
  when 'descriptor_id'
    batch_create_by_descriptor_id(params[:observation_matrix_id], params[:descriptor_id], params[:project_id], params[:user_id])

  when 'observation_matrix_column_item_id'
    batch_create_by_observation_matrix_column_item_id(params[:observation_matrix_id], params[:observation_matrix_column_item_id], params[:project_id], params[:user_id])
  end
end

.batch_create_by_descriptor_id(observation_matrix_id, descriptor_id, project_id, user_id) ⇒ Array, false

Returns:

  • (Array, false)


220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/observation_matrix_column_item.rb', line 220

def self.batch_create_by_descriptor_id(observation_matrix_id, descriptor_id, project_id, user_id )
  created = []
  matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
  ids = [descriptor_id].flatten.compact
  return false if matrix.nil?

  ObservationMatrixColumnItem.transaction do
    begin
      ids.each do |i|
        c = ObservationMatrixColumnItem::Single::Descriptor.create!(
          descriptor: Descriptor.where(project_id: project_id).find(i),
          observation_matrix_id: matrix.id,
          project_id: project_id,
          created_by_id: user_id)
        created.push c
      end
    rescue ActiveRecord::RecordInvalid => e
      next
    end
  end
  return created
end

.batch_create_by_observation_matrix_column_item_id(observation_matrix_id, observation_matrix_column_item_id, project_id, user_id) ⇒ Array, false

Returns:

  • (Array, false)


196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/observation_matrix_column_item.rb', line 196

def self.batch_create_by_observation_matrix_column_item_id(observation_matrix_id, observation_matrix_column_item_id, project_id, user_id )
  created = []
  matrix  = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
  ids = [observation_matrix_column_item_id].flatten.compact
  return false if matrix.nil?

  ObservationMatrixColumnItem.transaction do
    begin
      ids.each do |i|
        o = ObservationMatrixColumnItem.where(project_id: project_id).find(i)
        c = o.dup
        c.observation_matrix = matrix
        c.created_by_id = user_id
        c.save!
        created.push c
      end
    rescue ActiveRecord::RecordInvalid => e
      next
    end
  end
  return created
end

.batch_create_from_observation_matrix(from_observation_matrix_id, to_observation_matrix_id, project_id, user_id) ⇒ Array, false

Returns:

  • (Array, false)


244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'app/models/observation_matrix_column_item.rb', line 244

def self.batch_create_from_observation_matrix(from_observation_matrix_id, to_observation_matrix_id, project_id, user_id )
  created = []

  from_matrix = ObservationMatrix.where(project_id: project_id).find(from_observation_matrix_id)
  to_matrix = ObservationMatrix.where(project_id: project_id).find(to_observation_matrix_id)

  return false if from_matrix.nil? || to_matrix.nil?

  ObservationMatrixColumnItem.transaction do
    begin

      from_matrix.observation_matrix_column_items.each do |oci|
        c = oci.dup
        c.observation_matrix = to_matrix
        c.created_by_id = user_id
        c.save!
        created.push c
      end
    rescue ActiveRecord::RecordInvalid => e
      next
    end
  end
  return created
end

.batch_create_from_pinboard(observation_matrix_id, project_id, user_id, klass) ⇒ Array, false

Parameters:

  • klass (String) —

    the class name like Otu or CollectionObject

Returns:

  • (Array, false)


296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'app/models/observation_matrix_column_item.rb', line 296

def self.batch_create_from_pinboard(observation_matrix_id, project_id, user_id, klass)
  return false if observation_matrix_id.blank? || project_id.blank? || user_id.blank?
  created = []
  ObservationMatrixColumn.transaction do
    begin
      if klass
        klass.constantize.joins(:pinboard_items).where(pinboard_items: {user_id: user_id, project_id: project_id}).each do |o|
          created.push create_for(o, observation_matrix_id)
        end
      else
        created += create_for_pinboard_items(
          PinboardItem.where(project_id: project_id, user_id: user_id, pinned_object_type: 'Descriptor').all,
          observation_matrix_id
        )
      end
    rescue ActiveRecord::RecordInvalid => e
      return false
    end
  end
  return created
end

.batch_create_from_tags(keyword_id, klass, observation_matrix_id) ⇒ Array, false

Parameters:

  • klass (String) —

    the subclass of the Descriptor ike Descriptor::Working or Descriptor::Continuous

Returns:

  • (Array, false)


271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'app/models/observation_matrix_column_item.rb', line 271

def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
  created = []
  ObservationMatrixColumnItem.transaction do
    begin
      if klass
        klass.constantize.joins(:tags).where(tags: {keyword_id: keyword_id } ).each do |o|
          created.push create_for(o, observation_matrix_id)
        end
      else
        created += create_for_tags(
          Tag.where(
            keyword_id: keyword_id,
            tag_object_type: 'Descriptor').all,
           observation_matrix_id
        )
      end
    rescue ActiveRecord::RecordInvalid => e
      raise # return false
    end
  end
  return created
end

.create_for(object, observation_matrix_id) ⇒ Object



347
348
349
350
351
# File 'app/models/observation_matrix_column_item.rb', line 347

def self.create_for(object, observation_matrix_id)
  ObservationMatrixColumnItem::Single::Descriptor.create!(
    observation_matrix_id: observation_matrix_id,
    descriptor: object)
end

.create_for_pinboard_items(pinboard_item_scope, observation_matrix_id) ⇒ Array

Returns create observation matrix column items for all scope items.

Parameters:

Returns:

  • (Array) —

    create observation matrix column items for all scope items



339
340
341
342
343
344
345
# File 'app/models/observation_matrix_column_item.rb', line 339

def self.create_for_pinboard_items(pinboard_item_scope, observation_matrix_id)
  a = []
  pinboard_item_scope.each do |o|
    a.push create_for(o.pinned_object, observation_matrix_id)
  end
  a
end

.create_for_tags(tag_scope, observation_matrix_id) ⇒ Array

Returns:

  • (Array)


328
329
330
331
332
333
334
# File 'app/models/observation_matrix_column_item.rb', line 328

def self.create_for_tags(tag_scope, observation_matrix_id)
  a = []
  tag_scope.each do |o|
    a.push create_for(o.tag_object, observation_matrix_id)
  end
  a
end

.human_name ⇒ Object



152
153
154
# File 'app/models/observation_matrix_column_item.rb', line 152

def self.human_name
  self.name.demodulize.humanize
end

.subclass_attributes ⇒ Array

override

Returns:

  • (Array) —

    the required attributes for this subclass



159
160
161
# File 'app/models/observation_matrix_column_item.rb', line 159

def self.subclass_attributes
  []
end

Instance Method Details

#cleanup_matrix_columns ⇒ Object



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

def cleanup_matrix_columns
  return true unless descriptors.size > 0
  ObservationMatrixColumn.where(descriptor_id: descriptors.map(&:id), observation_matrix: observation_matrix).each do |mc|
    cleanup_single_matrix_column(mc.descriptor_id, mc)
  end
  true
end

#cleanup_single_matrix_column(descriptor_id, mc = nil) ⇒ Object



115
116
117
118
119
120
121
# File 'app/models/observation_matrix_column_item.rb', line 115

def cleanup_single_matrix_column(descriptor_id, mc = nil)
  mc ||= ObservationMatrixColumn.where(
    descriptor_id: descriptor_id,
    observation_matrix: observation_matrix
  ).first
  decrement_matrix_column_reference_count(mc) if !mc.nil?
end

#column_objects ⇒ Array

Returns of all objects this row references.

Returns:

  • (Array) —

    of all objects this row references



50
51
52
53
54
55
# File 'app/models/observation_matrix_column_item.rb', line 50

def column_objects
  objects = []

  objects.push *descriptors if descriptors
  objects
end

#decrement_matrix_column_reference_count(mc) ⇒ Object



123
124
125
126
127
128
129
130
131
# File 'app/models/observation_matrix_column_item.rb', line 123

def decrement_matrix_column_reference_count(mc)
  current = mc.reference_count - 1
  if current == 0
    mc.delete
  else
    mc.update_columns(reference_count: current)
    mc.update_columns(cached_observation_matrix_column_item_id: nil) if current == 1 && type =~ /Single/ # we've deleted the only single, so the last must be a Dynamic/Tagged
  end
end

#descriptor_ids_before_last_save ⇒ Array

override

Returns:

  • (Array) —

    ids of the descriptors "defined" by this matrix column item before its last save



174
175
176
# File 'app/models/observation_matrix_column_item.rb', line 174

def descriptor_ids_before_last_save
  []
end

#descriptors ⇒ Array

override

Returns:

  • (Array) —

    the descriptors "defined" by this matrix column item



166
167
168
# File 'app/models/observation_matrix_column_item.rb', line 166

def descriptors
  false
end

#find_or_build_column(descriptor) ⇒ Object



133
134
135
136
137
# File 'app/models/observation_matrix_column_item.rb', line 133

def find_or_build_column(descriptor)
  ObservationMatrixColumn.find_or_initialize_by(
    observation_matrix: observation_matrix,
    descriptor: descriptor)
end

#increment_matrix_column_reference_count(mc) ⇒ Object



147
148
149
150
# File 'app/models/observation_matrix_column_item.rb', line 147

def increment_matrix_column_reference_count(mc)
  mc.update_columns(reference_count: (mc.reference_count || 0) + 1)
  mc.update_columns(cached_observation_matrix_column_item_id: id) if type =~ /Single/
end

#is_dynamic? ⇒ Boolean

Returns whether this is a dynamic or fixed class override in subclasses.

Returns:

  • (Boolean) —

    whether this is a dynamic or fixed class override in subclasses



321
322
323
# File 'app/models/observation_matrix_column_item.rb', line 321

def is_dynamic?
  false
end

#move_matrix_column ⇒ Object

The item now covers other descriptors (e.g. unify moved it to the kept descriptor or keyword) and/or is in another matrix: decrement the previous descriptors' columns (in the previous matrix), increment the new ones



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
# File 'app/models/observation_matrix_column_item.rb', line 74

def move_matrix_column
  previous_ids = descriptor_ids_before_last_save
  current = descriptors
  same_matrix =
    observation_matrix_id_before_last_save == observation_matrix_id

  if same_matrix # columns in both sets are left untouched
    unchanged_ids = previous_ids & current.map(&:id)
    previous_ids -= unchanged_ids
    current = current.reject { |d| unchanged_ids.include?(d.id) }
  end

  # One descriptor replaced by another: repoint a column that would
  # otherwise be deleted, so it keeps its position
  if same_matrix && previous_ids.size == 1 && current.size == 1
    mc = ObservationMatrixColumn.where(
      descriptor_id: previous_ids.first, observation_matrix_id:
    ).first

    if mc&.reference_count == 1 &&
        !ObservationMatrixColumn.where(
          descriptor_id: current.first.id, observation_matrix_id:
        ).exists?
      mc.update_columns(descriptor_id: current.first.id)
      return
    end
  end

  previous_ids.each do |id|
    mc = ObservationMatrixColumn.where(
      descriptor_id: id,
      observation_matrix_id: observation_matrix_id_before_last_save
    ).first
    decrement_matrix_column_reference_count(mc) if !mc.nil?
  end

  current.each do |d|
    update_single_matrix_column(d)
  end
end

#other_subclass_attributes_not_set ⇒ Object (private)



353
354
355
356
357
# File 'app/models/observation_matrix_column_item.rb', line 353

def other_subclass_attributes_not_set
  (ALL_STI_ATTRIBUTES - self.class.subclass_attributes).each do |atr|
    errors.add(atr, 'is not valid for this type of observation matrix column item') if !send(atr).blank?
  end
end

#update_matrix_columns ⇒ Object



65
66
67
68
69
# File 'app/models/observation_matrix_column_item.rb', line 65

def update_matrix_columns
  descriptors.each do |d|
    update_single_matrix_column(d)
  end
end

#update_single_matrix_column(descriptor) ⇒ Object

creates or finds and updates count, always



140
141
142
143
144
145
# File 'app/models/observation_matrix_column_item.rb', line 140

def update_single_matrix_column(descriptor)
  mc = find_or_build_column(descriptor)
  mc.save! if !mc.persisted?

  increment_matrix_column_reference_count(mc)
end