Class: Container

Overview

A container localizes the proximity of one ore more physical things, at this point in the TW UI this is restricted to a number of collection objects.

Objects are placed in containers by reference to a ContainerItem.

Defined Under Namespace

Classes: Aisle, Box, Building, Cabinet, Collection, Drawer, Envelope, Folder, Jar, PillBox, Pin, Room, Shelf, Site, Slide, SlideBox, UnitTray, Vial, VialRack, Virtual, WellPlate

Constant Summary

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::Tags

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

Methods included from Shared::Loanable

#all_loan_items, #all_loans, #container_loan_items, #container_loaned?, #container_loans, #container_times_loaned, #current_loan, #current_loan_item, #has_been_loaned?, #is_loanable?, #loan_return_date, #loaned_in_container, #on_loan?, #times_loaned

Methods included from Shared::Labels

#labeled?

Methods included from Shared::Identifiers

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

Methods included from Shared::Depictions

#has_depictions?, #image_array=, #reject_depictions, #reject_images

Methods included from Shared::Containable

#contain, #containable?, #contained?, #contained_by?, #contained_siblings, #enclosing_containers, #put_in_container

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#dispositionString

Returns a free text description of the position of this container.

Returns:

  • (String)

    a free text description of the position of this container



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

class Container < ApplicationRecord

  attr_accessor :empty_container

  include Housekeeping

  # !! Must come before Shared::Containable
  before_destroy :empty_contents, if: -> { empty_container }
  before_destroy :check_for_contents

  include Shared::Containable
  include Shared::Depictions
  include Shared::Identifiers
  include Shared::Labels
  include Shared::Loanable
  include Shared::Tags
  include Shared::IsData
  include SoftValidation

  has_many :collection_profiles, inverse_of: :container, dependent: :restrict_with_error

  validates :type, presence: true
  validate :type_is_valid

  # @return [ContainerItem Scope]
  #    return all ContainerItems contained in this container (non recursive)
  # TODO: fix Please call `reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
  #
  def container_items
    container_item&.children || ContainerItem.none
  end

  # @return [ContainerItem Scope]
  #   return all ContainerItems contained in this container (recursive)
  def all_container_items
    reload_container_item.try(:descendants) || ContainerItem.none
  end

  # @return [Array]
  #   return all #contained_object(s) (non-recursive)
  def contained_objects
    return [] if !reload_container_item
    container_item.children.map(&:contained_object)
  end

  # @return [Array]
  #   return all #contained_object(s) (recursive)
  def all_contained_objects
    return [] if !reload_container_item
    container_item.descendants.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's CollectionObjects only (with recursion)
  def collection_objects
    all_container_items.containing_collection_objects.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's contents (no recursion)
  def collection_object_ids
    container_items.containing_collection_objects.pluck(:id)
  end

  # @return [Array] of CollectionObject#id of this container's contents (recursive)
  def all_collection_object_ids
    # all_container_items.containing_collection_objects.pluck(:id)
    collection_objects.map(&:id)
  end

  # @return [Boolean]
  #   regardless whether size is defined, whether there is anything in this container (non-recursive)
  def is_empty?
    !container_items.any?
  end

  # @return [Boolean]
  #   whether this container is nested in other containers
  def is_nested?
    container_item && container_item.ancestors.any?
  end

  # @return [Boolean]
  #   true if size is defined, and there is no space left in this container (non-recursive)
  def is_full?
    available_space == 0
  end

  # @return [Integer]
  #   the free space in this container (non-recursive)
  def available_space
    in_container = container_items.count
    if size
      size - in_container
    else
      nil
    end
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  # TODO: reserved word?
  def size
    return nil if size_x.blank? && size_y.blank? && size_z.blank?
    if size_x
      if size_y
        if size_z
          size_x * size_y * size_z
        else
          size_x * size_y
        end
      else
        size_x
      end
    end
  end

  # @return [String]
  #   the "common name" of this class
  def self.class_name
    self.name.demodulize.underscore.humanize.downcase
  end

  # @return [Array of Strings]
  #   valid containers class names that this container can fit in, by default none
  def self.valid_parents
    []
  end

  def self.dimensions
    {}
  end

  # @return [Container]
  #   places all objects in a new, parent-less container, saves it off,
  #   None of the objects are permitted to be new_records.
  #   !! If an object is in another container it is moved to the new container created here.
  def self.containerize(objects, klass = Container::Virtual)
    new_container = nil
    begin
      Container.transaction do
        new_container = klass.create()
        ci_parent     = ContainerItem.create(contained_object: new_container)

        objects.each do |o|
          raise ActiveRecord::RecordInvalid if o.new_record?
          if o.container_item.nil? # contain an uncontained objet
            ContainerItem.create(parent: ci_parent, contained_object: o)
          else # move the object if it's in a container already
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    new_container
  end

  # @return [Boolean]
  # add the objects to this container
  def add_container_items(objects)
    return false if new_record?

    # TODO: Figure out why this reload is required.
    self.reload # this seems to be required under some (as yet undefined) circumstances.
    begin
      Container.transaction do
        ci_parent = container_item
        ci_parent ||= ContainerItem.create!(contained_object: self)

        objects.each do |o|
          return false if o.new_record? || !o.containable? # does this roll back transaction
          if o.container_item.nil?
            ContainerItem.create!(parent: ci_parent, contained_object: o)
          else # move the object to a new container
            # this triggers the closure_tree parenting/re-parenting
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    true
  end

  protected

  def empty_contents
    container_items.delete_all
  end

  def type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if !is_empty?
      errors.add(:base, 'is not empty, empty it before destroying it')
      throw :abort
    end
  end

end

#empty_containerObject

Returns the value of attribute empty_container.



38
39
40
# File 'app/models/container.rb', line 38

def empty_container
  @empty_container
end

#nameString

Returns abitrary name of this container.

Returns:

  • (String)

    abitrary name of this container



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

class Container < ApplicationRecord

  attr_accessor :empty_container

  include Housekeeping

  # !! Must come before Shared::Containable
  before_destroy :empty_contents, if: -> { empty_container }
  before_destroy :check_for_contents

  include Shared::Containable
  include Shared::Depictions
  include Shared::Identifiers
  include Shared::Labels
  include Shared::Loanable
  include Shared::Tags
  include Shared::IsData
  include SoftValidation

  has_many :collection_profiles, inverse_of: :container, dependent: :restrict_with_error

  validates :type, presence: true
  validate :type_is_valid

  # @return [ContainerItem Scope]
  #    return all ContainerItems contained in this container (non recursive)
  # TODO: fix Please call `reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
  #
  def container_items
    container_item&.children || ContainerItem.none
  end

  # @return [ContainerItem Scope]
  #   return all ContainerItems contained in this container (recursive)
  def all_container_items
    reload_container_item.try(:descendants) || ContainerItem.none
  end

  # @return [Array]
  #   return all #contained_object(s) (non-recursive)
  def contained_objects
    return [] if !reload_container_item
    container_item.children.map(&:contained_object)
  end

  # @return [Array]
  #   return all #contained_object(s) (recursive)
  def all_contained_objects
    return [] if !reload_container_item
    container_item.descendants.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's CollectionObjects only (with recursion)
  def collection_objects
    all_container_items.containing_collection_objects.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's contents (no recursion)
  def collection_object_ids
    container_items.containing_collection_objects.pluck(:id)
  end

  # @return [Array] of CollectionObject#id of this container's contents (recursive)
  def all_collection_object_ids
    # all_container_items.containing_collection_objects.pluck(:id)
    collection_objects.map(&:id)
  end

  # @return [Boolean]
  #   regardless whether size is defined, whether there is anything in this container (non-recursive)
  def is_empty?
    !container_items.any?
  end

  # @return [Boolean]
  #   whether this container is nested in other containers
  def is_nested?
    container_item && container_item.ancestors.any?
  end

  # @return [Boolean]
  #   true if size is defined, and there is no space left in this container (non-recursive)
  def is_full?
    available_space == 0
  end

  # @return [Integer]
  #   the free space in this container (non-recursive)
  def available_space
    in_container = container_items.count
    if size
      size - in_container
    else
      nil
    end
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  # TODO: reserved word?
  def size
    return nil if size_x.blank? && size_y.blank? && size_z.blank?
    if size_x
      if size_y
        if size_z
          size_x * size_y * size_z
        else
          size_x * size_y
        end
      else
        size_x
      end
    end
  end

  # @return [String]
  #   the "common name" of this class
  def self.class_name
    self.name.demodulize.underscore.humanize.downcase
  end

  # @return [Array of Strings]
  #   valid containers class names that this container can fit in, by default none
  def self.valid_parents
    []
  end

  def self.dimensions
    {}
  end

  # @return [Container]
  #   places all objects in a new, parent-less container, saves it off,
  #   None of the objects are permitted to be new_records.
  #   !! If an object is in another container it is moved to the new container created here.
  def self.containerize(objects, klass = Container::Virtual)
    new_container = nil
    begin
      Container.transaction do
        new_container = klass.create()
        ci_parent     = ContainerItem.create(contained_object: new_container)

        objects.each do |o|
          raise ActiveRecord::RecordInvalid if o.new_record?
          if o.container_item.nil? # contain an uncontained objet
            ContainerItem.create(parent: ci_parent, contained_object: o)
          else # move the object if it's in a container already
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    new_container
  end

  # @return [Boolean]
  # add the objects to this container
  def add_container_items(objects)
    return false if new_record?

    # TODO: Figure out why this reload is required.
    self.reload # this seems to be required under some (as yet undefined) circumstances.
    begin
      Container.transaction do
        ci_parent = container_item
        ci_parent ||= ContainerItem.create!(contained_object: self)

        objects.each do |o|
          return false if o.new_record? || !o.containable? # does this roll back transaction
          if o.container_item.nil?
            ContainerItem.create!(parent: ci_parent, contained_object: o)
          else # move the object to a new container
            # this triggers the closure_tree parenting/re-parenting
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    true
  end

  protected

  def empty_contents
    container_items.delete_all
  end

  def type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if !is_empty?
      errors.add(:base, 'is not empty, empty it before destroying it')
      throw :abort
    end
  end

end

Returns text of a label to print for this container.

Returns:

  • (String)

    text of a label to print for this container



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

class Container < ApplicationRecord

  attr_accessor :empty_container

  include Housekeeping

  # !! Must come before Shared::Containable
  before_destroy :empty_contents, if: -> { empty_container }
  before_destroy :check_for_contents

  include Shared::Containable
  include Shared::Depictions
  include Shared::Identifiers
  include Shared::Labels
  include Shared::Loanable
  include Shared::Tags
  include Shared::IsData
  include SoftValidation

  has_many :collection_profiles, inverse_of: :container, dependent: :restrict_with_error

  validates :type, presence: true
  validate :type_is_valid

  # @return [ContainerItem Scope]
  #    return all ContainerItems contained in this container (non recursive)
  # TODO: fix Please call `reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
  #
  def container_items
    container_item&.children || ContainerItem.none
  end

  # @return [ContainerItem Scope]
  #   return all ContainerItems contained in this container (recursive)
  def all_container_items
    reload_container_item.try(:descendants) || ContainerItem.none
  end

  # @return [Array]
  #   return all #contained_object(s) (non-recursive)
  def contained_objects
    return [] if !reload_container_item
    container_item.children.map(&:contained_object)
  end

  # @return [Array]
  #   return all #contained_object(s) (recursive)
  def all_contained_objects
    return [] if !reload_container_item
    container_item.descendants.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's CollectionObjects only (with recursion)
  def collection_objects
    all_container_items.containing_collection_objects.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's contents (no recursion)
  def collection_object_ids
    container_items.containing_collection_objects.pluck(:id)
  end

  # @return [Array] of CollectionObject#id of this container's contents (recursive)
  def all_collection_object_ids
    # all_container_items.containing_collection_objects.pluck(:id)
    collection_objects.map(&:id)
  end

  # @return [Boolean]
  #   regardless whether size is defined, whether there is anything in this container (non-recursive)
  def is_empty?
    !container_items.any?
  end

  # @return [Boolean]
  #   whether this container is nested in other containers
  def is_nested?
    container_item && container_item.ancestors.any?
  end

  # @return [Boolean]
  #   true if size is defined, and there is no space left in this container (non-recursive)
  def is_full?
    available_space == 0
  end

  # @return [Integer]
  #   the free space in this container (non-recursive)
  def available_space
    in_container = container_items.count
    if size
      size - in_container
    else
      nil
    end
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  # TODO: reserved word?
  def size
    return nil if size_x.blank? && size_y.blank? && size_z.blank?
    if size_x
      if size_y
        if size_z
          size_x * size_y * size_z
        else
          size_x * size_y
        end
      else
        size_x
      end
    end
  end

  # @return [String]
  #   the "common name" of this class
  def self.class_name
    self.name.demodulize.underscore.humanize.downcase
  end

  # @return [Array of Strings]
  #   valid containers class names that this container can fit in, by default none
  def self.valid_parents
    []
  end

  def self.dimensions
    {}
  end

  # @return [Container]
  #   places all objects in a new, parent-less container, saves it off,
  #   None of the objects are permitted to be new_records.
  #   !! If an object is in another container it is moved to the new container created here.
  def self.containerize(objects, klass = Container::Virtual)
    new_container = nil
    begin
      Container.transaction do
        new_container = klass.create()
        ci_parent     = ContainerItem.create(contained_object: new_container)

        objects.each do |o|
          raise ActiveRecord::RecordInvalid if o.new_record?
          if o.container_item.nil? # contain an uncontained objet
            ContainerItem.create(parent: ci_parent, contained_object: o)
          else # move the object if it's in a container already
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    new_container
  end

  # @return [Boolean]
  # add the objects to this container
  def add_container_items(objects)
    return false if new_record?

    # TODO: Figure out why this reload is required.
    self.reload # this seems to be required under some (as yet undefined) circumstances.
    begin
      Container.transaction do
        ci_parent = container_item
        ci_parent ||= ContainerItem.create!(contained_object: self)

        objects.each do |o|
          return false if o.new_record? || !o.containable? # does this roll back transaction
          if o.container_item.nil?
            ContainerItem.create!(parent: ci_parent, contained_object: o)
          else # move the object to a new container
            # this triggers the closure_tree parenting/re-parenting
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    true
  end

  protected

  def empty_contents
    container_items.delete_all
  end

  def type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if !is_empty?
      errors.add(:base, 'is not empty, empty it before destroying it')
      throw :abort
    end
  end

end

#project_idInteger

Returns:

  • (Integer)


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

class Container < ApplicationRecord

  attr_accessor :empty_container

  include Housekeeping

  # !! Must come before Shared::Containable
  before_destroy :empty_contents, if: -> { empty_container }
  before_destroy :check_for_contents

  include Shared::Containable
  include Shared::Depictions
  include Shared::Identifiers
  include Shared::Labels
  include Shared::Loanable
  include Shared::Tags
  include Shared::IsData
  include SoftValidation

  has_many :collection_profiles, inverse_of: :container, dependent: :restrict_with_error

  validates :type, presence: true
  validate :type_is_valid

  # @return [ContainerItem Scope]
  #    return all ContainerItems contained in this container (non recursive)
  # TODO: fix Please call `reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
  #
  def container_items
    container_item&.children || ContainerItem.none
  end

  # @return [ContainerItem Scope]
  #   return all ContainerItems contained in this container (recursive)
  def all_container_items
    reload_container_item.try(:descendants) || ContainerItem.none
  end

  # @return [Array]
  #   return all #contained_object(s) (non-recursive)
  def contained_objects
    return [] if !reload_container_item
    container_item.children.map(&:contained_object)
  end

  # @return [Array]
  #   return all #contained_object(s) (recursive)
  def all_contained_objects
    return [] if !reload_container_item
    container_item.descendants.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's CollectionObjects only (with recursion)
  def collection_objects
    all_container_items.containing_collection_objects.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's contents (no recursion)
  def collection_object_ids
    container_items.containing_collection_objects.pluck(:id)
  end

  # @return [Array] of CollectionObject#id of this container's contents (recursive)
  def all_collection_object_ids
    # all_container_items.containing_collection_objects.pluck(:id)
    collection_objects.map(&:id)
  end

  # @return [Boolean]
  #   regardless whether size is defined, whether there is anything in this container (non-recursive)
  def is_empty?
    !container_items.any?
  end

  # @return [Boolean]
  #   whether this container is nested in other containers
  def is_nested?
    container_item && container_item.ancestors.any?
  end

  # @return [Boolean]
  #   true if size is defined, and there is no space left in this container (non-recursive)
  def is_full?
    available_space == 0
  end

  # @return [Integer]
  #   the free space in this container (non-recursive)
  def available_space
    in_container = container_items.count
    if size
      size - in_container
    else
      nil
    end
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  # TODO: reserved word?
  def size
    return nil if size_x.blank? && size_y.blank? && size_z.blank?
    if size_x
      if size_y
        if size_z
          size_x * size_y * size_z
        else
          size_x * size_y
        end
      else
        size_x
      end
    end
  end

  # @return [String]
  #   the "common name" of this class
  def self.class_name
    self.name.demodulize.underscore.humanize.downcase
  end

  # @return [Array of Strings]
  #   valid containers class names that this container can fit in, by default none
  def self.valid_parents
    []
  end

  def self.dimensions
    {}
  end

  # @return [Container]
  #   places all objects in a new, parent-less container, saves it off,
  #   None of the objects are permitted to be new_records.
  #   !! If an object is in another container it is moved to the new container created here.
  def self.containerize(objects, klass = Container::Virtual)
    new_container = nil
    begin
      Container.transaction do
        new_container = klass.create()
        ci_parent     = ContainerItem.create(contained_object: new_container)

        objects.each do |o|
          raise ActiveRecord::RecordInvalid if o.new_record?
          if o.container_item.nil? # contain an uncontained objet
            ContainerItem.create(parent: ci_parent, contained_object: o)
          else # move the object if it's in a container already
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    new_container
  end

  # @return [Boolean]
  # add the objects to this container
  def add_container_items(objects)
    return false if new_record?

    # TODO: Figure out why this reload is required.
    self.reload # this seems to be required under some (as yet undefined) circumstances.
    begin
      Container.transaction do
        ci_parent = container_item
        ci_parent ||= ContainerItem.create!(contained_object: self)

        objects.each do |o|
          return false if o.new_record? || !o.containable? # does this roll back transaction
          if o.container_item.nil?
            ContainerItem.create!(parent: ci_parent, contained_object: o)
          else # move the object to a new container
            # this triggers the closure_tree parenting/re-parenting
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    true
  end

  protected

  def empty_contents
    container_items.delete_all
  end

  def type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if !is_empty?
      errors.add(:base, 'is not empty, empty it before destroying it')
      throw :abort
    end
  end

end

#size_xInt

Returns the number of slots in the x dimension.

Returns:

  • (Int)

    the number of slots in the x dimension



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

class Container < ApplicationRecord

  attr_accessor :empty_container

  include Housekeeping

  # !! Must come before Shared::Containable
  before_destroy :empty_contents, if: -> { empty_container }
  before_destroy :check_for_contents

  include Shared::Containable
  include Shared::Depictions
  include Shared::Identifiers
  include Shared::Labels
  include Shared::Loanable
  include Shared::Tags
  include Shared::IsData
  include SoftValidation

  has_many :collection_profiles, inverse_of: :container, dependent: :restrict_with_error

  validates :type, presence: true
  validate :type_is_valid

  # @return [ContainerItem Scope]
  #    return all ContainerItems contained in this container (non recursive)
  # TODO: fix Please call `reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
  #
  def container_items
    container_item&.children || ContainerItem.none
  end

  # @return [ContainerItem Scope]
  #   return all ContainerItems contained in this container (recursive)
  def all_container_items
    reload_container_item.try(:descendants) || ContainerItem.none
  end

  # @return [Array]
  #   return all #contained_object(s) (non-recursive)
  def contained_objects
    return [] if !reload_container_item
    container_item.children.map(&:contained_object)
  end

  # @return [Array]
  #   return all #contained_object(s) (recursive)
  def all_contained_objects
    return [] if !reload_container_item
    container_item.descendants.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's CollectionObjects only (with recursion)
  def collection_objects
    all_container_items.containing_collection_objects.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's contents (no recursion)
  def collection_object_ids
    container_items.containing_collection_objects.pluck(:id)
  end

  # @return [Array] of CollectionObject#id of this container's contents (recursive)
  def all_collection_object_ids
    # all_container_items.containing_collection_objects.pluck(:id)
    collection_objects.map(&:id)
  end

  # @return [Boolean]
  #   regardless whether size is defined, whether there is anything in this container (non-recursive)
  def is_empty?
    !container_items.any?
  end

  # @return [Boolean]
  #   whether this container is nested in other containers
  def is_nested?
    container_item && container_item.ancestors.any?
  end

  # @return [Boolean]
  #   true if size is defined, and there is no space left in this container (non-recursive)
  def is_full?
    available_space == 0
  end

  # @return [Integer]
  #   the free space in this container (non-recursive)
  def available_space
    in_container = container_items.count
    if size
      size - in_container
    else
      nil
    end
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  # TODO: reserved word?
  def size
    return nil if size_x.blank? && size_y.blank? && size_z.blank?
    if size_x
      if size_y
        if size_z
          size_x * size_y * size_z
        else
          size_x * size_y
        end
      else
        size_x
      end
    end
  end

  # @return [String]
  #   the "common name" of this class
  def self.class_name
    self.name.demodulize.underscore.humanize.downcase
  end

  # @return [Array of Strings]
  #   valid containers class names that this container can fit in, by default none
  def self.valid_parents
    []
  end

  def self.dimensions
    {}
  end

  # @return [Container]
  #   places all objects in a new, parent-less container, saves it off,
  #   None of the objects are permitted to be new_records.
  #   !! If an object is in another container it is moved to the new container created here.
  def self.containerize(objects, klass = Container::Virtual)
    new_container = nil
    begin
      Container.transaction do
        new_container = klass.create()
        ci_parent     = ContainerItem.create(contained_object: new_container)

        objects.each do |o|
          raise ActiveRecord::RecordInvalid if o.new_record?
          if o.container_item.nil? # contain an uncontained objet
            ContainerItem.create(parent: ci_parent, contained_object: o)
          else # move the object if it's in a container already
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    new_container
  end

  # @return [Boolean]
  # add the objects to this container
  def add_container_items(objects)
    return false if new_record?

    # TODO: Figure out why this reload is required.
    self.reload # this seems to be required under some (as yet undefined) circumstances.
    begin
      Container.transaction do
        ci_parent = container_item
        ci_parent ||= ContainerItem.create!(contained_object: self)

        objects.each do |o|
          return false if o.new_record? || !o.containable? # does this roll back transaction
          if o.container_item.nil?
            ContainerItem.create!(parent: ci_parent, contained_object: o)
          else # move the object to a new container
            # this triggers the closure_tree parenting/re-parenting
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    true
  end

  protected

  def empty_contents
    container_items.delete_all
  end

  def type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if !is_empty?
      errors.add(:base, 'is not empty, empty it before destroying it')
      throw :abort
    end
  end

end

#size_yInt

Returns the number of slots in the y dimension.

Returns:

  • (Int)

    the number of slots in the y dimension



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

class Container < ApplicationRecord

  attr_accessor :empty_container

  include Housekeeping

  # !! Must come before Shared::Containable
  before_destroy :empty_contents, if: -> { empty_container }
  before_destroy :check_for_contents

  include Shared::Containable
  include Shared::Depictions
  include Shared::Identifiers
  include Shared::Labels
  include Shared::Loanable
  include Shared::Tags
  include Shared::IsData
  include SoftValidation

  has_many :collection_profiles, inverse_of: :container, dependent: :restrict_with_error

  validates :type, presence: true
  validate :type_is_valid

  # @return [ContainerItem Scope]
  #    return all ContainerItems contained in this container (non recursive)
  # TODO: fix Please call `reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
  #
  def container_items
    container_item&.children || ContainerItem.none
  end

  # @return [ContainerItem Scope]
  #   return all ContainerItems contained in this container (recursive)
  def all_container_items
    reload_container_item.try(:descendants) || ContainerItem.none
  end

  # @return [Array]
  #   return all #contained_object(s) (non-recursive)
  def contained_objects
    return [] if !reload_container_item
    container_item.children.map(&:contained_object)
  end

  # @return [Array]
  #   return all #contained_object(s) (recursive)
  def all_contained_objects
    return [] if !reload_container_item
    container_item.descendants.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's CollectionObjects only (with recursion)
  def collection_objects
    all_container_items.containing_collection_objects.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's contents (no recursion)
  def collection_object_ids
    container_items.containing_collection_objects.pluck(:id)
  end

  # @return [Array] of CollectionObject#id of this container's contents (recursive)
  def all_collection_object_ids
    # all_container_items.containing_collection_objects.pluck(:id)
    collection_objects.map(&:id)
  end

  # @return [Boolean]
  #   regardless whether size is defined, whether there is anything in this container (non-recursive)
  def is_empty?
    !container_items.any?
  end

  # @return [Boolean]
  #   whether this container is nested in other containers
  def is_nested?
    container_item && container_item.ancestors.any?
  end

  # @return [Boolean]
  #   true if size is defined, and there is no space left in this container (non-recursive)
  def is_full?
    available_space == 0
  end

  # @return [Integer]
  #   the free space in this container (non-recursive)
  def available_space
    in_container = container_items.count
    if size
      size - in_container
    else
      nil
    end
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  # TODO: reserved word?
  def size
    return nil if size_x.blank? && size_y.blank? && size_z.blank?
    if size_x
      if size_y
        if size_z
          size_x * size_y * size_z
        else
          size_x * size_y
        end
      else
        size_x
      end
    end
  end

  # @return [String]
  #   the "common name" of this class
  def self.class_name
    self.name.demodulize.underscore.humanize.downcase
  end

  # @return [Array of Strings]
  #   valid containers class names that this container can fit in, by default none
  def self.valid_parents
    []
  end

  def self.dimensions
    {}
  end

  # @return [Container]
  #   places all objects in a new, parent-less container, saves it off,
  #   None of the objects are permitted to be new_records.
  #   !! If an object is in another container it is moved to the new container created here.
  def self.containerize(objects, klass = Container::Virtual)
    new_container = nil
    begin
      Container.transaction do
        new_container = klass.create()
        ci_parent     = ContainerItem.create(contained_object: new_container)

        objects.each do |o|
          raise ActiveRecord::RecordInvalid if o.new_record?
          if o.container_item.nil? # contain an uncontained objet
            ContainerItem.create(parent: ci_parent, contained_object: o)
          else # move the object if it's in a container already
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    new_container
  end

  # @return [Boolean]
  # add the objects to this container
  def add_container_items(objects)
    return false if new_record?

    # TODO: Figure out why this reload is required.
    self.reload # this seems to be required under some (as yet undefined) circumstances.
    begin
      Container.transaction do
        ci_parent = container_item
        ci_parent ||= ContainerItem.create!(contained_object: self)

        objects.each do |o|
          return false if o.new_record? || !o.containable? # does this roll back transaction
          if o.container_item.nil?
            ContainerItem.create!(parent: ci_parent, contained_object: o)
          else # move the object to a new container
            # this triggers the closure_tree parenting/re-parenting
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    true
  end

  protected

  def empty_contents
    container_items.delete_all
  end

  def type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if !is_empty?
      errors.add(:base, 'is not empty, empty it before destroying it')
      throw :abort
    end
  end

end

#size_zInt

Returns the number of slots in the z dimension.

Returns:

  • (Int)

    the number of slots in the z dimension



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

class Container < ApplicationRecord

  attr_accessor :empty_container

  include Housekeeping

  # !! Must come before Shared::Containable
  before_destroy :empty_contents, if: -> { empty_container }
  before_destroy :check_for_contents

  include Shared::Containable
  include Shared::Depictions
  include Shared::Identifiers
  include Shared::Labels
  include Shared::Loanable
  include Shared::Tags
  include Shared::IsData
  include SoftValidation

  has_many :collection_profiles, inverse_of: :container, dependent: :restrict_with_error

  validates :type, presence: true
  validate :type_is_valid

  # @return [ContainerItem Scope]
  #    return all ContainerItems contained in this container (non recursive)
  # TODO: fix Please call `reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
  #
  def container_items
    container_item&.children || ContainerItem.none
  end

  # @return [ContainerItem Scope]
  #   return all ContainerItems contained in this container (recursive)
  def all_container_items
    reload_container_item.try(:descendants) || ContainerItem.none
  end

  # @return [Array]
  #   return all #contained_object(s) (non-recursive)
  def contained_objects
    return [] if !reload_container_item
    container_item.children.map(&:contained_object)
  end

  # @return [Array]
  #   return all #contained_object(s) (recursive)
  def all_contained_objects
    return [] if !reload_container_item
    container_item.descendants.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's CollectionObjects only (with recursion)
  def collection_objects
    all_container_items.containing_collection_objects.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's contents (no recursion)
  def collection_object_ids
    container_items.containing_collection_objects.pluck(:id)
  end

  # @return [Array] of CollectionObject#id of this container's contents (recursive)
  def all_collection_object_ids
    # all_container_items.containing_collection_objects.pluck(:id)
    collection_objects.map(&:id)
  end

  # @return [Boolean]
  #   regardless whether size is defined, whether there is anything in this container (non-recursive)
  def is_empty?
    !container_items.any?
  end

  # @return [Boolean]
  #   whether this container is nested in other containers
  def is_nested?
    container_item && container_item.ancestors.any?
  end

  # @return [Boolean]
  #   true if size is defined, and there is no space left in this container (non-recursive)
  def is_full?
    available_space == 0
  end

  # @return [Integer]
  #   the free space in this container (non-recursive)
  def available_space
    in_container = container_items.count
    if size
      size - in_container
    else
      nil
    end
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  # TODO: reserved word?
  def size
    return nil if size_x.blank? && size_y.blank? && size_z.blank?
    if size_x
      if size_y
        if size_z
          size_x * size_y * size_z
        else
          size_x * size_y
        end
      else
        size_x
      end
    end
  end

  # @return [String]
  #   the "common name" of this class
  def self.class_name
    self.name.demodulize.underscore.humanize.downcase
  end

  # @return [Array of Strings]
  #   valid containers class names that this container can fit in, by default none
  def self.valid_parents
    []
  end

  def self.dimensions
    {}
  end

  # @return [Container]
  #   places all objects in a new, parent-less container, saves it off,
  #   None of the objects are permitted to be new_records.
  #   !! If an object is in another container it is moved to the new container created here.
  def self.containerize(objects, klass = Container::Virtual)
    new_container = nil
    begin
      Container.transaction do
        new_container = klass.create()
        ci_parent     = ContainerItem.create(contained_object: new_container)

        objects.each do |o|
          raise ActiveRecord::RecordInvalid if o.new_record?
          if o.container_item.nil? # contain an uncontained objet
            ContainerItem.create(parent: ci_parent, contained_object: o)
          else # move the object if it's in a container already
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    new_container
  end

  # @return [Boolean]
  # add the objects to this container
  def add_container_items(objects)
    return false if new_record?

    # TODO: Figure out why this reload is required.
    self.reload # this seems to be required under some (as yet undefined) circumstances.
    begin
      Container.transaction do
        ci_parent = container_item
        ci_parent ||= ContainerItem.create!(contained_object: self)

        objects.each do |o|
          return false if o.new_record? || !o.containable? # does this roll back transaction
          if o.container_item.nil?
            ContainerItem.create!(parent: ci_parent, contained_object: o)
          else # move the object to a new container
            # this triggers the closure_tree parenting/re-parenting
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    true
  end

  protected

  def empty_contents
    container_items.delete_all
  end

  def type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if !is_empty?
      errors.add(:base, 'is not empty, empty it before destroying it')
      throw :abort
    end
  end

end

#typeString

Returns STI, the type of container.

Returns:

  • (String)

    STI, the type of container



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

class Container < ApplicationRecord

  attr_accessor :empty_container

  include Housekeeping

  # !! Must come before Shared::Containable
  before_destroy :empty_contents, if: -> { empty_container }
  before_destroy :check_for_contents

  include Shared::Containable
  include Shared::Depictions
  include Shared::Identifiers
  include Shared::Labels
  include Shared::Loanable
  include Shared::Tags
  include Shared::IsData
  include SoftValidation

  has_many :collection_profiles, inverse_of: :container, dependent: :restrict_with_error

  validates :type, presence: true
  validate :type_is_valid

  # @return [ContainerItem Scope]
  #    return all ContainerItems contained in this container (non recursive)
  # TODO: fix Please call `reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
  #
  def container_items
    container_item&.children || ContainerItem.none
  end

  # @return [ContainerItem Scope]
  #   return all ContainerItems contained in this container (recursive)
  def all_container_items
    reload_container_item.try(:descendants) || ContainerItem.none
  end

  # @return [Array]
  #   return all #contained_object(s) (non-recursive)
  def contained_objects
    return [] if !reload_container_item
    container_item.children.map(&:contained_object)
  end

  # @return [Array]
  #   return all #contained_object(s) (recursive)
  def all_contained_objects
    return [] if !reload_container_item
    container_item.descendants.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's CollectionObjects only (with recursion)
  def collection_objects
    all_container_items.containing_collection_objects.map(&:contained_object)
  end

  # @return [Array] of CollectionObject#id of this container's contents (no recursion)
  def collection_object_ids
    container_items.containing_collection_objects.pluck(:id)
  end

  # @return [Array] of CollectionObject#id of this container's contents (recursive)
  def all_collection_object_ids
    # all_container_items.containing_collection_objects.pluck(:id)
    collection_objects.map(&:id)
  end

  # @return [Boolean]
  #   regardless whether size is defined, whether there is anything in this container (non-recursive)
  def is_empty?
    !container_items.any?
  end

  # @return [Boolean]
  #   whether this container is nested in other containers
  def is_nested?
    container_item && container_item.ancestors.any?
  end

  # @return [Boolean]
  #   true if size is defined, and there is no space left in this container (non-recursive)
  def is_full?
    available_space == 0
  end

  # @return [Integer]
  #   the free space in this container (non-recursive)
  def available_space
    in_container = container_items.count
    if size
      size - in_container
    else
      nil
    end
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  # TODO: reserved word?
  def size
    return nil if size_x.blank? && size_y.blank? && size_z.blank?
    if size_x
      if size_y
        if size_z
          size_x * size_y * size_z
        else
          size_x * size_y
        end
      else
        size_x
      end
    end
  end

  # @return [String]
  #   the "common name" of this class
  def self.class_name
    self.name.demodulize.underscore.humanize.downcase
  end

  # @return [Array of Strings]
  #   valid containers class names that this container can fit in, by default none
  def self.valid_parents
    []
  end

  def self.dimensions
    {}
  end

  # @return [Container]
  #   places all objects in a new, parent-less container, saves it off,
  #   None of the objects are permitted to be new_records.
  #   !! If an object is in another container it is moved to the new container created here.
  def self.containerize(objects, klass = Container::Virtual)
    new_container = nil
    begin
      Container.transaction do
        new_container = klass.create()
        ci_parent     = ContainerItem.create(contained_object: new_container)

        objects.each do |o|
          raise ActiveRecord::RecordInvalid if o.new_record?
          if o.container_item.nil? # contain an uncontained objet
            ContainerItem.create(parent: ci_parent, contained_object: o)
          else # move the object if it's in a container already
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    new_container
  end

  # @return [Boolean]
  # add the objects to this container
  def add_container_items(objects)
    return false if new_record?

    # TODO: Figure out why this reload is required.
    self.reload # this seems to be required under some (as yet undefined) circumstances.
    begin
      Container.transaction do
        ci_parent = container_item
        ci_parent ||= ContainerItem.create!(contained_object: self)

        objects.each do |o|
          return false if o.new_record? || !o.containable? # does this roll back transaction
          if o.container_item.nil?
            ContainerItem.create!(parent: ci_parent, contained_object: o)
          else # move the object to a new container
            # this triggers the closure_tree parenting/re-parenting
            o.container_item.update(parent_id: ci_parent.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
    true
  end

  protected

  def empty_contents
    container_items.delete_all
  end

  def type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if !is_empty?
      errors.add(:base, 'is not empty, empty it before destroying it')
      throw :abort
    end
  end

end

Class Method Details

.class_nameString

Returns the “common name” of this class.

Returns:

  • (String)

    the “common name” of this class



153
154
155
# File 'app/models/container.rb', line 153

def self.class_name
  self.name.demodulize.underscore.humanize.downcase
end

.containerize(objects, klass = Container::Virtual) ⇒ Container

Returns places all objects in a new, parent-less container, saves it off, None of the objects are permitted to be new_records. !! If an object is in another container it is moved to the new container created here.

Returns:

  • (Container)

    places all objects in a new, parent-less container, saves it off, None of the objects are permitted to be new_records. !! If an object is in another container it is moved to the new container created here.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/models/container.rb', line 171

def self.containerize(objects, klass = Container::Virtual)
  new_container = nil
  begin
    Container.transaction do
      new_container = klass.create()
      ci_parent     = ContainerItem.create(contained_object: new_container)

      objects.each do |o|
        raise ActiveRecord::RecordInvalid if o.new_record?
        if o.container_item.nil? # contain an uncontained objet
          ContainerItem.create(parent: ci_parent, contained_object: o)
        else # move the object if it's in a container already
          o.container_item.update(parent_id: ci_parent.id)
        end
      end
    end
  rescue ActiveRecord::RecordInvalid
    return false
  end
  new_container
end

.dimensionsObject



163
164
165
# File 'app/models/container.rb', line 163

def self.dimensions
  {}
end

.valid_parentsArray of Strings

Returns valid containers class names that this container can fit in, by default none.

Returns:

  • (Array of Strings)

    valid containers class names that this container can fit in, by default none



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

def self.valid_parents
  []
end

Instance Method Details

#add_container_items(objects) ⇒ Boolean

add the objects to this container

Returns:

  • (Boolean)


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

def add_container_items(objects)
  return false if new_record?

  # TODO: Figure out why this reload is required.
  self.reload # this seems to be required under some (as yet undefined) circumstances.
  begin
    Container.transaction do
      ci_parent = container_item
      ci_parent ||= ContainerItem.create!(contained_object: self)

      objects.each do |o|
        return false if o.new_record? || !o.containable? # does this roll back transaction
        if o.container_item.nil?
          ContainerItem.create!(parent: ci_parent, contained_object: o)
        else # move the object to a new container
          # this triggers the closure_tree parenting/re-parenting
          o.container_item.update(parent_id: ci_parent.id)
        end
      end
    end
  rescue ActiveRecord::RecordInvalid
    return false
  end
  true
end

#all_collection_object_idsArray

Returns of CollectionObject#id of this container’s contents (recursive).

Returns:

  • (Array)

    of CollectionObject#id of this container’s contents (recursive)



99
100
101
102
# File 'app/models/container.rb', line 99

def all_collection_object_ids
  # all_container_items.containing_collection_objects.pluck(:id)
  collection_objects.map(&:id)
end

#all_contained_objectsArray

Return all #contained_object(s) (recursive)

Returns:

  • (Array)

    return all #contained_object(s) (recursive)



83
84
85
86
# File 'app/models/container.rb', line 83

def all_contained_objects
  return [] if !reload_container_item
  container_item.descendants.map(&:contained_object)
end

#all_container_itemsContainerItem Scope

Return all ContainerItems contained in this container (recursive)

Returns:

  • (ContainerItem Scope)

    return all ContainerItems contained in this container (recursive)



70
71
72
# File 'app/models/container.rb', line 70

def all_container_items
  reload_container_item.try(:descendants) || ContainerItem.none
end

#available_spaceInteger

Returns the free space in this container (non-recursive).

Returns:

  • (Integer)

    the free space in this container (non-recursive)



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

def available_space
  in_container = container_items.count
  if size
    size - in_container
  else
    nil
  end
end

#check_for_contentsObject (protected)



231
232
233
234
235
236
# File 'app/models/container.rb', line 231

def check_for_contents
  if !is_empty?
    errors.add(:base, 'is not empty, empty it before destroying it')
    throw :abort
  end
end

#collection_object_idsArray

Returns of CollectionObject#id of this container’s contents (no recursion).

Returns:

  • (Array)

    of CollectionObject#id of this container’s contents (no recursion)



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

def collection_object_ids
  container_items.containing_collection_objects.pluck(:id)
end

#collection_objectsArray

Returns of CollectionObject#id of this container’s CollectionObjects only (with recursion).

Returns:

  • (Array)

    of CollectionObject#id of this container’s CollectionObjects only (with recursion)



89
90
91
# File 'app/models/container.rb', line 89

def collection_objects
  all_container_items.containing_collection_objects.map(&:contained_object)
end

#contained_objectsArray

Return all #contained_object(s) (non-recursive)

Returns:

  • (Array)

    return all #contained_object(s) (non-recursive)



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

def contained_objects
  return [] if !reload_container_item
  container_item.children.map(&:contained_object)
end

#container_itemsContainerItem Scope

TODO: fix Please call ‘reload_container_item` instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)

Returns:

  • (ContainerItem Scope)

    return all ContainerItems contained in this container (non recursive)



64
65
66
# File 'app/models/container.rb', line 64

def container_items
  container_item&.children || ContainerItem.none
end

#empty_contentsObject (protected)



223
224
225
# File 'app/models/container.rb', line 223

def empty_contents
  container_items.delete_all
end

#is_empty?Boolean

Returns regardless whether size is defined, whether there is anything in this container (non-recursive).

Returns:

  • (Boolean)

    regardless whether size is defined, whether there is anything in this container (non-recursive)



106
107
108
# File 'app/models/container.rb', line 106

def is_empty?
  !container_items.any?
end

#is_full?Boolean

Returns true if size is defined, and there is no space left in this container (non-recursive).

Returns:

  • (Boolean)

    true if size is defined, and there is no space left in this container (non-recursive)



118
119
120
# File 'app/models/container.rb', line 118

def is_full?
  available_space == 0
end

#is_nested?Boolean

Returns whether this container is nested in other containers.

Returns:

  • (Boolean)

    whether this container is nested in other containers



112
113
114
# File 'app/models/container.rb', line 112

def is_nested?
  container_item && container_item.ancestors.any?
end

#sizeInteger?

TODO: reserved word?

Returns:

  • (Integer, nil)

    the total number of “slots” or “spaces” this container has, it’s size



136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'app/models/container.rb', line 136

def size
  return nil if size_x.blank? && size_y.blank? && size_z.blank?
  if size_x
    if size_y
      if size_z
        size_x * size_y * size_z
      else
        size_x * size_y
      end
    else
      size_x
    end
  end
end

#type_is_validObject (protected)

Raises:

  • (ActiveRecord::SubclassNotFound)


227
228
229
# File 'app/models/container.rb', line 227

def type_is_valid
  raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
end