Class: ObservationMatrixRowItem

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

Overview

Each ObservationMatrixRowItem is set of Otus or Collection Objects (1 or more)

Direct Known Subclasses

Dynamic, Single

Defined Under Namespace

Classes: Dynamic, Single

Constant Summary collapse

ALL_STI_ATTRIBUTES =

readded

[:otu_id, :collection_object_id, :controlled_vocabulary_term_id, :taxon_name_id].freeze

Constants included from SoftValidation

SoftValidation::ANCESTORS_WITH_SOFT_VALIDATIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SoftValidation

#clear_soft_validations, #fix_for, #fix_soft_validations, #soft_fixed?, #soft_valid?, #soft_validate, #soft_validated?, #soft_validations, #soft_validators

Methods included from Shared::IsData

#errors_excepting, #full_error_messages_excepting, #identical, #is_community?, #is_destroyable?, #is_editable?, #is_in_use?, #is_in_users_projects?, #metamorphosize, #similar

Methods included from Shared::Notes

#concatenated_notes_string, #reject_notes

Methods included from Shared::Tags

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

Methods included from Shared::Identifiers

#dwc_occurrence_id, #identified?, #next_by_identifier, #previous_by_identifier, #reject_identifiers

Methods included from Shared::Citations

#cited?, #mark_citations_for_destruction, #nomenclature_date, #origin_citation_source_id, #reject_citations, #requires_citation?, #sources_by_topic_id

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#collection_object_idInteger

Returns id of a (single) CollectObject based row.

Returns:

  • (Integer)

    id of a (single) CollectObject based row



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

class ObservationMatrixRowItem < ApplicationRecord
  include Housekeeping
  include Shared::Citations
  include Shared::Identifiers
  include Shared::Tags
  include Shared::Notes
  include Shared::IsData
  include SoftValidation

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:otu_id, :collection_object_id, :controlled_vocabulary_term_id, :taxon_name_id].freeze # readded

  belongs_to :observation_matrix, inverse_of: :observation_matrix_row_items

  # In subclasses?!  Validation vould have to be on _id?
  # belongs_to :otu, inverse_of: :observation_matrix_row_items
  # belongs_to :collection_object, inverse_of: :observation_matrix_row_items

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

  after_save :update_matrix_rows
  after_destroy :cleanup_matrix_rows

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

    objects.push *otus if otus
    objects.push *collection_objects if collection_objects
    objects
  end 

  def update_matrix_rows
     row_objects.each do |o|
      update_single_matrix_row o
    end
  end

  def cleanup_matrix_rows
    return true if otus.count == 0 && collection_objects.count == 0
    rows = []
    rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: otus.map(&:id))
    rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: collection_objects.map(&:id))

    rows.each do |mr|
      decrement_matrix_row_reference_count(mr)
    end
    true
  end

  def find_or_build_row(object)
    if object.is_a? Otu
      ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, otu: object )
    elsif object.is_a? CollectionObject
      ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, collection_object: object)
    end
  end

  def update_single_matrix_row(object)
    mr = find_or_build_row(object)
    mr.save! if !mr.persisted?
    increment_matrix_row_reference_count(mr)
  end

  # Not names destroy because it doesn't always delete row
  def cleanup_single_matrix_row(object)
    mr = nil

    if object.is_a? Otu
      mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: object.id).first
    elsif object.is_a? CollectionObject
      mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: object.id).first
    end
    decrement_matrix_row_reference_count(mr) if !mr.nil?
  end

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

  # @return [Array]
  #   the otus "defined" by this matrix row item
  # override
  def otus
    [] 
  end

  # @return [Array]
  #   the collection objects "defined" by this matrix row item
  # override
  def collection_objects
    [] 
  end

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

  # @return [Object]
  #   the object used to define the set of matrix rows
  # override
  def matrix_row_item_object
    nil
  end

  # @return [matrix_row_item_object, nil]
  def object_is?(object_type)
    matrix_row_item_object.class.name == object_type ? matrix_row_item_object : nil
  end

  protected

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

  # @return [Array] of ObservationMatrixRowItems
  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])
    end
  end

  # @params klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixRowItem.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: ['Otu', 'CollectionObject']).all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @params 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 = []
    ObservationMatrixRow.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: ['Otu', 'CollectionObject']).all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise
       # return false
      end
    end
    return created
  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 row 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)
    p = { observation_matrix_id: observation_matrix_id }
    k = nil
    case object.class.base_class.name
    when 'Otu'
      p[:otu] = object
      k = ObservationMatrixRowItem::Single::Otu
    when 'CollectionObject'
      p[:collection_object] = object
      k = ObservationMatrixRowItem::Single::CollectionObject
    else
      raise
    end
    k.create!(p)
  end

  def decrement_matrix_row_reference_count(mr)
    current = mr.reference_count - 1

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

  # TODO: Should change behaviour of cached_
  # to only populate with id when reference count == 1
  # that way we could delete rows  
  def increment_matrix_row_reference_count(mr)
    mr.update_columns(reference_count: (mr.reference_count || 0) +  1)
    mr.update_columns(cached_observation_matrix_row_item_id: id) if type =~ /Single/
  end
end

#observation_matrix_idInteger

Returns id of the matrix.

Returns:

  • (Integer)

    id of the matrix



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

class ObservationMatrixRowItem < ApplicationRecord
  include Housekeeping
  include Shared::Citations
  include Shared::Identifiers
  include Shared::Tags
  include Shared::Notes
  include Shared::IsData
  include SoftValidation

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:otu_id, :collection_object_id, :controlled_vocabulary_term_id, :taxon_name_id].freeze # readded

  belongs_to :observation_matrix, inverse_of: :observation_matrix_row_items

  # In subclasses?!  Validation vould have to be on _id?
  # belongs_to :otu, inverse_of: :observation_matrix_row_items
  # belongs_to :collection_object, inverse_of: :observation_matrix_row_items

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

  after_save :update_matrix_rows
  after_destroy :cleanup_matrix_rows

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

    objects.push *otus if otus
    objects.push *collection_objects if collection_objects
    objects
  end 

  def update_matrix_rows
     row_objects.each do |o|
      update_single_matrix_row o
    end
  end

  def cleanup_matrix_rows
    return true if otus.count == 0 && collection_objects.count == 0
    rows = []
    rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: otus.map(&:id))
    rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: collection_objects.map(&:id))

    rows.each do |mr|
      decrement_matrix_row_reference_count(mr)
    end
    true
  end

  def find_or_build_row(object)
    if object.is_a? Otu
      ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, otu: object )
    elsif object.is_a? CollectionObject
      ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, collection_object: object)
    end
  end

  def update_single_matrix_row(object)
    mr = find_or_build_row(object)
    mr.save! if !mr.persisted?
    increment_matrix_row_reference_count(mr)
  end

  # Not names destroy because it doesn't always delete row
  def cleanup_single_matrix_row(object)
    mr = nil

    if object.is_a? Otu
      mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: object.id).first
    elsif object.is_a? CollectionObject
      mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: object.id).first
    end
    decrement_matrix_row_reference_count(mr) if !mr.nil?
  end

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

  # @return [Array]
  #   the otus "defined" by this matrix row item
  # override
  def otus
    [] 
  end

  # @return [Array]
  #   the collection objects "defined" by this matrix row item
  # override
  def collection_objects
    [] 
  end

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

  # @return [Object]
  #   the object used to define the set of matrix rows
  # override
  def matrix_row_item_object
    nil
  end

  # @return [matrix_row_item_object, nil]
  def object_is?(object_type)
    matrix_row_item_object.class.name == object_type ? matrix_row_item_object : nil
  end

  protected

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

  # @return [Array] of ObservationMatrixRowItems
  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])
    end
  end

  # @params klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixRowItem.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: ['Otu', 'CollectionObject']).all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @params 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 = []
    ObservationMatrixRow.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: ['Otu', 'CollectionObject']).all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise
       # return false
      end
    end
    return created
  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 row 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)
    p = { observation_matrix_id: observation_matrix_id }
    k = nil
    case object.class.base_class.name
    when 'Otu'
      p[:otu] = object
      k = ObservationMatrixRowItem::Single::Otu
    when 'CollectionObject'
      p[:collection_object] = object
      k = ObservationMatrixRowItem::Single::CollectionObject
    else
      raise
    end
    k.create!(p)
  end

  def decrement_matrix_row_reference_count(mr)
    current = mr.reference_count - 1

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

  # TODO: Should change behaviour of cached_
  # to only populate with id when reference count == 1
  # that way we could delete rows  
  def increment_matrix_row_reference_count(mr)
    mr.update_columns(reference_count: (mr.reference_count || 0) +  1)
    mr.update_columns(cached_observation_matrix_row_item_id: id) if type =~ /Single/
  end
end

#otu_idInteger

Returns id of an (single) Otu based row.

Returns:

  • (Integer)

    id of an (single) Otu based row



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

class ObservationMatrixRowItem < ApplicationRecord
  include Housekeeping
  include Shared::Citations
  include Shared::Identifiers
  include Shared::Tags
  include Shared::Notes
  include Shared::IsData
  include SoftValidation

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:otu_id, :collection_object_id, :controlled_vocabulary_term_id, :taxon_name_id].freeze # readded

  belongs_to :observation_matrix, inverse_of: :observation_matrix_row_items

  # In subclasses?!  Validation vould have to be on _id?
  # belongs_to :otu, inverse_of: :observation_matrix_row_items
  # belongs_to :collection_object, inverse_of: :observation_matrix_row_items

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

  after_save :update_matrix_rows
  after_destroy :cleanup_matrix_rows

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

    objects.push *otus if otus
    objects.push *collection_objects if collection_objects
    objects
  end 

  def update_matrix_rows
     row_objects.each do |o|
      update_single_matrix_row o
    end
  end

  def cleanup_matrix_rows
    return true if otus.count == 0 && collection_objects.count == 0
    rows = []
    rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: otus.map(&:id))
    rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: collection_objects.map(&:id))

    rows.each do |mr|
      decrement_matrix_row_reference_count(mr)
    end
    true
  end

  def find_or_build_row(object)
    if object.is_a? Otu
      ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, otu: object )
    elsif object.is_a? CollectionObject
      ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, collection_object: object)
    end
  end

  def update_single_matrix_row(object)
    mr = find_or_build_row(object)
    mr.save! if !mr.persisted?
    increment_matrix_row_reference_count(mr)
  end

  # Not names destroy because it doesn't always delete row
  def cleanup_single_matrix_row(object)
    mr = nil

    if object.is_a? Otu
      mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: object.id).first
    elsif object.is_a? CollectionObject
      mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: object.id).first
    end
    decrement_matrix_row_reference_count(mr) if !mr.nil?
  end

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

  # @return [Array]
  #   the otus "defined" by this matrix row item
  # override
  def otus
    [] 
  end

  # @return [Array]
  #   the collection objects "defined" by this matrix row item
  # override
  def collection_objects
    [] 
  end

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

  # @return [Object]
  #   the object used to define the set of matrix rows
  # override
  def matrix_row_item_object
    nil
  end

  # @return [matrix_row_item_object, nil]
  def object_is?(object_type)
    matrix_row_item_object.class.name == object_type ? matrix_row_item_object : nil
  end

  protected

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

  # @return [Array] of ObservationMatrixRowItems
  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])
    end
  end

  # @params klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixRowItem.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: ['Otu', 'CollectionObject']).all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @params 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 = []
    ObservationMatrixRow.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: ['Otu', 'CollectionObject']).all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise
       # return false
      end
    end
    return created
  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 row 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)
    p = { observation_matrix_id: observation_matrix_id }
    k = nil
    case object.class.base_class.name
    when 'Otu'
      p[:otu] = object
      k = ObservationMatrixRowItem::Single::Otu
    when 'CollectionObject'
      p[:collection_object] = object
      k = ObservationMatrixRowItem::Single::CollectionObject
    else
      raise
    end
    k.create!(p)
  end

  def decrement_matrix_row_reference_count(mr)
    current = mr.reference_count - 1

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

  # TODO: Should change behaviour of cached_
  # to only populate with id when reference count == 1
  # that way we could delete rows  
  def increment_matrix_row_reference_count(mr)
    mr.update_columns(reference_count: (mr.reference_count || 0) +  1)
    mr.update_columns(cached_observation_matrix_row_item_id: id) if type =~ /Single/
  end
end

#positionInteger

Returns a sort order.

Returns:

  • (Integer)

    a sort order



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

class ObservationMatrixRowItem < ApplicationRecord
  include Housekeeping
  include Shared::Citations
  include Shared::Identifiers
  include Shared::Tags
  include Shared::Notes
  include Shared::IsData
  include SoftValidation

  acts_as_list scope: [:observation_matrix_id, :project_id]

  ALL_STI_ATTRIBUTES = [:otu_id, :collection_object_id, :controlled_vocabulary_term_id, :taxon_name_id].freeze # readded

  belongs_to :observation_matrix, inverse_of: :observation_matrix_row_items

  # In subclasses?!  Validation vould have to be on _id?
  # belongs_to :otu, inverse_of: :observation_matrix_row_items
  # belongs_to :collection_object, inverse_of: :observation_matrix_row_items

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

  after_save :update_matrix_rows
  after_destroy :cleanup_matrix_rows

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

    objects.push *otus if otus
    objects.push *collection_objects if collection_objects
    objects
  end 

  def update_matrix_rows
     row_objects.each do |o|
      update_single_matrix_row o
    end
  end

  def cleanup_matrix_rows
    return true if otus.count == 0 && collection_objects.count == 0
    rows = []
    rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: otus.map(&:id))
    rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: collection_objects.map(&:id))

    rows.each do |mr|
      decrement_matrix_row_reference_count(mr)
    end
    true
  end

  def find_or_build_row(object)
    if object.is_a? Otu
      ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, otu: object )
    elsif object.is_a? CollectionObject
      ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, collection_object: object)
    end
  end

  def update_single_matrix_row(object)
    mr = find_or_build_row(object)
    mr.save! if !mr.persisted?
    increment_matrix_row_reference_count(mr)
  end

  # Not names destroy because it doesn't always delete row
  def cleanup_single_matrix_row(object)
    mr = nil

    if object.is_a? Otu
      mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: object.id).first
    elsif object.is_a? CollectionObject
      mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: object.id).first
    end
    decrement_matrix_row_reference_count(mr) if !mr.nil?
  end

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

  # @return [Array]
  #   the otus "defined" by this matrix row item
  # override
  def otus
    [] 
  end

  # @return [Array]
  #   the collection objects "defined" by this matrix row item
  # override
  def collection_objects
    [] 
  end

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

  # @return [Object]
  #   the object used to define the set of matrix rows
  # override
  def matrix_row_item_object
    nil
  end

  # @return [matrix_row_item_object, nil]
  def object_is?(object_type)
    matrix_row_item_object.class.name == object_type ? matrix_row_item_object : nil
  end

  protected

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

  # @return [Array] of ObservationMatrixRowItems
  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])
    end
  end

  # @params klass [String] the class name like `Otu` or `CollectionObject`
  # @return [Array, false]
  def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
    created = []
    ObservationMatrixRowItem.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: ['Otu', 'CollectionObject']).all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        return false
      end
    end
    return created
  end

  # @params 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 = []
    ObservationMatrixRow.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: ['Otu', 'CollectionObject']).all,
            observation_matrix_id
          )
        end
      rescue ActiveRecord::RecordInvalid => e
        raise
       # return false
      end
    end
    return created
  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 row 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)
    p = { observation_matrix_id: observation_matrix_id }
    k = nil
    case object.class.base_class.name
    when 'Otu'
      p[:otu] = object
      k = ObservationMatrixRowItem::Single::Otu
    when 'CollectionObject'
      p[:collection_object] = object
      k = ObservationMatrixRowItem::Single::CollectionObject
    else
      raise
    end
    k.create!(p)
  end

  def decrement_matrix_row_reference_count(mr)
    current = mr.reference_count - 1

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

  # TODO: Should change behaviour of cached_
  # to only populate with id when reference count == 1
  # that way we could delete rows  
  def increment_matrix_row_reference_count(mr)
    mr.update_columns(reference_count: (mr.reference_count || 0) +  1)
    mr.update_columns(cached_observation_matrix_row_item_id: id) if type =~ /Single/
  end
end

Class Method Details

.batch_create(params) ⇒ Array (protected)

Returns of ObservationMatrixRowItems.

Returns:

  • (Array)

    of ObservationMatrixRowItems



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

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])
  end
end

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

Returns:

  • (Array, false)


174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'app/models/observation_matrix_row_item.rb', line 174

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 = []
  ObservationMatrixRow.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: ['Otu', 'CollectionObject']).all,
          observation_matrix_id
        )
      end
    rescue ActiveRecord::RecordInvalid => e
      raise
     # return false
    end
  end
  return created
end

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

Returns:

  • (Array, false)


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'app/models/observation_matrix_row_item.rb', line 151

def self.batch_create_from_tags(keyword_id, klass, observation_matrix_id)
  created = []
  ObservationMatrixRowItem.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: ['Otu', 'CollectionObject']).all,
          observation_matrix_id
        )
      end
    rescue ActiveRecord::RecordInvalid => e
      return false
    end
  end
  return created
end

.create_for(object, observation_matrix_id) ⇒ Object (private)



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'app/models/observation_matrix_row_item.rb', line 219

def self.create_for(object, observation_matrix_id)
  p = { observation_matrix_id: observation_matrix_id }
  k = nil
  case object.class.base_class.name
  when 'Otu'
    p[:otu] = object
    k = ObservationMatrixRowItem::Single::Otu
  when 'CollectionObject'
    p[:collection_object] = object
    k = ObservationMatrixRowItem::Single::CollectionObject
  else
    raise
  end
  k.create!(p)
end

.create_for_pinboard_items(pinboard_item_scope, observation_matrix_id) ⇒ Array (private)

Returns create observation matrix row items for all scope items.

Parameters:

Returns:

  • (Array)

    create observation matrix row items for all scope items



211
212
213
214
215
216
217
# File 'app/models/observation_matrix_row_item.rb', line 211

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 (private)

Returns:

  • (Array)


200
201
202
203
204
205
206
# File 'app/models/observation_matrix_row_item.rb', line 200

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_nameObject



94
95
96
# File 'app/models/observation_matrix_row_item.rb', line 94

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

.subclass_attributesArray

override

Returns:

  • (Array)

    the required attributes for this subclass



115
116
117
# File 'app/models/observation_matrix_row_item.rb', line 115

def self.subclass_attributes
  []
end

Instance Method Details

#cleanup_matrix_rowsObject



56
57
58
59
60
61
62
63
64
65
66
# File 'app/models/observation_matrix_row_item.rb', line 56

def cleanup_matrix_rows
  return true if otus.count == 0 && collection_objects.count == 0
  rows = []
  rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: otus.map(&:id))
  rows.push *ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: collection_objects.map(&:id))

  rows.each do |mr|
    decrement_matrix_row_reference_count(mr)
  end
  true
end

#cleanup_single_matrix_row(object) ⇒ Object

Not names destroy because it doesn't always delete row



83
84
85
86
87
88
89
90
91
92
# File 'app/models/observation_matrix_row_item.rb', line 83

def cleanup_single_matrix_row(object)
  mr = nil

  if object.is_a? Otu
    mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, otu_id: object.id).first
  elsif object.is_a? CollectionObject
    mr = ObservationMatrixRow.where(observation_matrix: observation_matrix, collection_object_id: object.id).first
  end
  decrement_matrix_row_reference_count(mr) if !mr.nil?
end

#collection_objectsArray

override

Returns:

  • (Array)

    the collection objects “defined” by this matrix row item



108
109
110
# File 'app/models/observation_matrix_row_item.rb', line 108

def collection_objects
  [] 
end

#decrement_matrix_row_reference_count(mr) ⇒ Object (private)



235
236
237
238
239
240
241
242
243
244
# File 'app/models/observation_matrix_row_item.rb', line 235

def decrement_matrix_row_reference_count(mr)
  current = mr.reference_count - 1

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

#find_or_build_row(object) ⇒ Object



68
69
70
71
72
73
74
# File 'app/models/observation_matrix_row_item.rb', line 68

def find_or_build_row(object)
  if object.is_a? Otu
    ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, otu: object )
  elsif object.is_a? CollectionObject
    ObservationMatrixRow.find_or_initialize_by(observation_matrix: observation_matrix, collection_object: object)
  end
end

#increment_matrix_row_reference_count(mr) ⇒ Object (private)

TODO: Should change behaviour of cached_ to only populate with id when reference count == 1 that way we could delete rows



249
250
251
252
# File 'app/models/observation_matrix_row_item.rb', line 249

def increment_matrix_row_reference_count(mr)
  mr.update_columns(reference_count: (mr.reference_count || 0) +  1)
  mr.update_columns(cached_observation_matrix_row_item_id: id) if type =~ /Single/
end

#matrix_row_item_objectObject

override

Returns:

  • (Object)

    the object used to define the set of matrix rows



122
123
124
# File 'app/models/observation_matrix_row_item.rb', line 122

def matrix_row_item_object
  nil
end

#object_is?(object_type) ⇒ matrix_row_item_object?

Returns:



127
128
129
# File 'app/models/observation_matrix_row_item.rb', line 127

def object_is?(object_type)
  matrix_row_item_object.class.name == object_type ? matrix_row_item_object : nil
end

#other_subclass_attributes_not_setObject (protected)



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

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

#otusArray

override

Returns:

  • (Array)

    the otus “defined” by this matrix row item



101
102
103
# File 'app/models/observation_matrix_row_item.rb', line 101

def otus
  [] 
end

#row_objectsArray

Returns of all objects this row references.

Returns:

  • (Array)

    of all objects this row references



42
43
44
45
46
47
48
# File 'app/models/observation_matrix_row_item.rb', line 42

def row_objects
  objects = []

  objects.push *otus if otus
  objects.push *collection_objects if collection_objects
  objects
end

#update_matrix_rowsObject



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

def update_matrix_rows
   row_objects.each do |o|
    update_single_matrix_row o
  end
end

#update_single_matrix_row(object) ⇒ Object



76
77
78
79
80
# File 'app/models/observation_matrix_row_item.rb', line 76

def update_single_matrix_row(object)
  mr = find_or_build_row(object)
  mr.save! if !mr.persisted?
  increment_matrix_row_reference_count(mr)
end