Class: Container

Inherits:
ApplicationRecord show all
Includes:
Housekeeping, Shared::Containable, Shared::Identifiable, Shared::IsData, Shared::Loanable, Shared::Taggable, SoftValidation
Defined in:
app/models/container.rb

Overview

A container localizes the proximity of one ore more physical things, at this point in TW this is restricted to a number of collection objects. Objects are placed in containers by reference through a ContainerItem.

Direct Known Subclasses

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

Defined Under Namespace

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

Constant Summary

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_soft_validations, #soft_fixed?, #soft_valid?, #soft_validate, #soft_validated?, #soft_validations

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods included from ActiverecordUtilities

#trim_attributes

Instance Attribute Details

- (String) disposition

Returns a free text description of the position of this container

Returns:

  • (String)

    a free text description of the position of this container



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

class Container < ApplicationRecord

  include Housekeeping
  include Shared::IsData
  include Shared::Identifiable

  include Shared::Containable

  include Shared::Taggable
  include SoftValidation
  include Shared::Loanable

  has_many :collection_profiles

  validates :type, presence: true
  validate :type_is_valid

  before_destroy :check_for_contents

  # @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
    reload_container_item.try(: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 (no recursion)
  # def collection_objects
  #   container_items.containing_collection_objects.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
    size - in_container
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  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

  def self.find_for_autocomplete(params)
    Queries::ContainerAutocompleteQuery.new(params[:term], project_id: params[:project_id]).result
  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

  # @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|
          return false 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 type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if container_items.any?
      errors.add(:base, 'is not empty, empty it before destroying it')
      # return false
      throw :abort
    end
  end
end

- (String) name

Returns abitrary name of this container

Returns:

  • (String)

    abitrary name of this container



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

class Container < ApplicationRecord

  include Housekeeping
  include Shared::IsData
  include Shared::Identifiable

  include Shared::Containable

  include Shared::Taggable
  include SoftValidation
  include Shared::Loanable

  has_many :collection_profiles

  validates :type, presence: true
  validate :type_is_valid

  before_destroy :check_for_contents

  # @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
    reload_container_item.try(: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 (no recursion)
  # def collection_objects
  #   container_items.containing_collection_objects.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
    size - in_container
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  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

  def self.find_for_autocomplete(params)
    Queries::ContainerAutocompleteQuery.new(params[:term], project_id: params[:project_id]).result
  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

  # @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|
          return false 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 type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if container_items.any?
      errors.add(:base, 'is not empty, empty it before destroying it')
      # return false
      throw :abort
    end
  end
end

- (Integer) project_id

the project ID

Returns:

  • (Integer)


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

class Container < ApplicationRecord

  include Housekeeping
  include Shared::IsData
  include Shared::Identifiable

  include Shared::Containable

  include Shared::Taggable
  include SoftValidation
  include Shared::Loanable

  has_many :collection_profiles

  validates :type, presence: true
  validate :type_is_valid

  before_destroy :check_for_contents

  # @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
    reload_container_item.try(: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 (no recursion)
  # def collection_objects
  #   container_items.containing_collection_objects.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
    size - in_container
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  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

  def self.find_for_autocomplete(params)
    Queries::ContainerAutocompleteQuery.new(params[:term], project_id: params[:project_id]).result
  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

  # @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|
          return false 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 type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if container_items.any?
      errors.add(:base, 'is not empty, empty it before destroying it')
      # return false
      throw :abort
    end
  end
end

- (String) type

Returns STI, the type of container

Returns:

  • (String)

    STI, the type of container



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

class Container < ApplicationRecord

  include Housekeeping
  include Shared::IsData
  include Shared::Identifiable

  include Shared::Containable

  include Shared::Taggable
  include SoftValidation
  include Shared::Loanable

  has_many :collection_profiles

  validates :type, presence: true
  validate :type_is_valid

  before_destroy :check_for_contents

  # @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
    reload_container_item.try(: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 (no recursion)
  # def collection_objects
  #   container_items.containing_collection_objects.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
    size - in_container
  end

  # @return [Integer, nil]
  #   the total number of "slots" or "spaces" this container has, it's size
  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

  def self.find_for_autocomplete(params)
    Queries::ContainerAutocompleteQuery.new(params[:term], project_id: params[:project_id]).result
  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

  # @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|
          return false 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 type_is_valid
    raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type)
  end

  def check_for_contents
    if container_items.any?
      errors.add(:base, 'is not empty, empty it before destroying it')
      # return false
      throw :abort
    end
  end
end

Class Method Details

+ (String) class_name

Returns the “common name” of this class

Returns:

  • (String)

    the “common name” of this class



135
136
137
# File 'app/models/container.rb', line 135

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

+ (Container) containerize(objects, klass = Container::Virtual)

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.



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

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|
        return false 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

+ (Object) find_for_autocomplete(params)



129
130
131
# File 'app/models/container.rb', line 129

def self.find_for_autocomplete(params)
  Queries::ContainerAutocompleteQuery.new(params[:term], project_id: params[:project_id]).result
end

+ (Array of Strings) valid_parents

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



141
142
143
# File 'app/models/container.rb', line 141

def self.valid_parents
  []
end

Instance Method Details

- (Boolean) add_container_items(objects)

Returns add the objects to this container

Returns:

  • (Boolean)

    add the objects to this container



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

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

- (Array) all_collection_object_ids

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

Returns:

  • (Array)

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



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

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

- (Array) all_contained_objects

Return all #contained_object(s) (recursive)

Returns:

  • (Array)

    return all #contained_object(s) (recursive)



61
62
63
64
# File 'app/models/container.rb', line 61

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

- (ContainerItem Scope) all_container_items

Return all ContainerItems contained in this container (recursive)

Returns:

  • (ContainerItem Scope)

    return all ContainerItems contained in this container (recursive)



48
49
50
# File 'app/models/container.rb', line 48

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

- (Integer) available_space

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

Returns:

  • (Integer)

    the free space in this container (non-recursive)



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

def available_space
  in_container = container_items.count
  size - in_container
end

- (Object) check_for_contents (protected)



205
206
207
208
209
210
211
# File 'app/models/container.rb', line 205

def check_for_contents
  if container_items.any?
    errors.add(:base, 'is not empty, empty it before destroying it')
    # return false
    throw :abort
  end
end

- (Array) collection_object_ids

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

Returns:

  • (Array)

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



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

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

- (Array) collection_objects

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)



72
73
74
# File 'app/models/container.rb', line 72

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

- (Array) contained_objects

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

Returns:

  • (Array)

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



54
55
56
57
# File 'app/models/container.rb', line 54

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

- (ContainerItem Scope) container_items

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)



42
43
44
# File 'app/models/container.rb', line 42

def container_items
  reload_container_item.try(:children) || ContainerItem.none
end

- (Boolean) is_empty?

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)



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

def is_empty?
  !container_items.any?
end

- (Boolean) is_full?

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)



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

def is_full?
  available_space == 0
end

- (Boolean) is_nested?

Returns whether this container is nested in other containers

Returns:

  • (Boolean)

    whether this container is nested in other containers



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

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

- (Integer?) size

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

Returns:

  • (Integer, nil)

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



114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'app/models/container.rb', line 114

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

- (Object) type_is_valid (protected)

Raises:

  • (ActiveRecord::SubclassNotFound)


201
202
203
# File 'app/models/container.rb', line 201

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