Class: Container
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Container
- Includes:
- Housekeeping, Shared::Containable, Shared::Depictions, Shared::Identifiers, Shared::IsData, Shared::Labels, Shared::Loanable, Shared::Tags, SoftValidation
- Defined in:
- app/models/container.rb
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.
Direct Known Subclasses
Aisle, Box, Building, Cabinet, Collection, Drawer, Envelope, Folder, Jar, PillBox, Pin, Room, Shelf, Site, Slide, SlideBox, UnitTray, Vial, VialRack, Virtual, WellPlate
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
-
#disposition ⇒ String
A free text description of the position of this container.
-
#empty_container ⇒ Object
Returns the value of attribute empty_container.
-
#name ⇒ String
Abitrary name of this container.
-
#print_label ⇒ String
Text of a label to print for this container.
- #project_id ⇒ Integer
-
#size_x ⇒ Int
The number of slots in the x dimension.
-
#size_y ⇒ Int
The number of slots in the y dimension.
-
#size_z ⇒ Int
The number of slots in the z dimension.
-
#type ⇒ String
STI, the type of container.
Class Method Summary collapse
-
.class_name ⇒ String
The "common name" of this class.
-
.containerize(objects, klass = Container::Virtual) ⇒ Container
Places all objects in a new, parent-less container, saves it off, None of the objects are permitted to be new_records.
- .dimensions ⇒ Object
-
.scaffold(params) ⇒ Array<Container::Room>
The created Room containers, or false on failure.
-
.valid_parents ⇒ Array of Strings
Valid containers class names that this container can fit in, by default none.
Instance Method Summary collapse
-
#add_container_items(objects) ⇒ Boolean
add the objects to this container.
-
#all_collection_object_ids ⇒ Array
Of CollectionObject#id of this container's contents (recursive).
-
#all_contained_objects ⇒ Array
Return all #contained_object(s) (recursive).
-
#all_container_items ⇒ ContainerItem Scope
Return all ContainerItems contained in this container (recursive).
-
#available_space ⇒ Integer
The free space in this container (non-recursive).
- #check_for_contents ⇒ Object protected
-
#collection_object_ids ⇒ Array
Of CollectionObject#id of this container's contents (no recursion).
-
#collection_objects ⇒ Array
Of CollectionObject#id of this container's CollectionObjects only (with recursion).
-
#contained_objects ⇒ Array
Return all #contained_object(s) (non-recursive).
-
#container_items ⇒ ContainerItem Scope
TODO: fix Please call
reload_container_iteminstead. - #earmarked_requires_sufficient_empty ⇒ Object protected
- #empty_contents ⇒ Object protected
-
#is_empty? ⇒ Boolean
Regardless whether size is defined, whether there is anything in this container (non-recursive).
-
#is_full? ⇒ Boolean
True if size is defined, and there is no space left in this container (non-recursive).
-
#is_nested? ⇒ Boolean
Whether this container is nested in other containers.
-
#size ⇒ Integer?
TODO: reserved word?.
-
#size_does_not_exclude_placed_items ⇒ Object
protected
Prevent shrinking a dimension when placed container items would fall outside the new boundary on that axis.
- #type_is_valid ⇒ Object protected
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_in_use?, #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
Methods included from Shared::Identifiers
#dwc_occurrence_id, #identified?, #next_by_identifier, #previous_by_identifier, #reject_identifiers, #uri, #uuid, #visible_identifiers_for
Methods included from Shared::Depictions
#has_depictions?, #image_array=, #reject_depictions, #reject_images
Methods included from Shared::Containable
#contain, #containable?, containable_types, #contained?, #contained_by?, #contained_siblings, #enclosing_containers, #put_in_container
Methods included from Housekeeping
#has_polymorphic_relationship?
Methods inherited from ApplicationRecord
Instance Attribute Details
#disposition ⇒ String
Returns 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# 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 validates :asserted_percent_empty, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validates :asserted_percent_earmarked, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validate :earmarked_requires_sufficient_empty validate :size_does_not_exclude_placed_items, if: :persisted? # @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 # @param params [ActionController::Parameters, Hash] with keys: # building_id [Integer] id of the parent Container::Building # drawer_type [String] full STI type name of the drawer, e.g. 'Container::Drawer::CalAcademy' # rooms [Integer] number of Room containers to create # cabinets [Integer] number of Cabinet containers per Room # drawers [Integer] number of Drawer containers per Cabinet # cabinet_size_x [Integer, nil] default size_x applied to each created cabinet # cabinet_size_y [Integer, nil] default size_y applied to each created cabinet # cabinet_size_z [Integer, nil] default size_z applied to each created cabinet # asserted_percent_empty [Float, nil] default value applied to each created drawer # asserted_percent_earmarked [Float, nil] default value applied to each created drawer # @return [Array<Container::Room>] the created Room containers, or false on failure def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms 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 # Prevent shrinking a dimension when placed container items would fall # outside the new boundary on that axis. def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end 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_container ⇒ Object
Returns the value of attribute empty_container.
38 39 40 |
# File 'app/models/container.rb', line 38 def empty_container @empty_container end |
#name ⇒ String
Returns 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# 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 validates :asserted_percent_empty, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validates :asserted_percent_earmarked, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validate :earmarked_requires_sufficient_empty validate :size_does_not_exclude_placed_items, if: :persisted? # @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 # @param params [ActionController::Parameters, Hash] with keys: # building_id [Integer] id of the parent Container::Building # drawer_type [String] full STI type name of the drawer, e.g. 'Container::Drawer::CalAcademy' # rooms [Integer] number of Room containers to create # cabinets [Integer] number of Cabinet containers per Room # drawers [Integer] number of Drawer containers per Cabinet # cabinet_size_x [Integer, nil] default size_x applied to each created cabinet # cabinet_size_y [Integer, nil] default size_y applied to each created cabinet # cabinet_size_z [Integer, nil] default size_z applied to each created cabinet # asserted_percent_empty [Float, nil] default value applied to each created drawer # asserted_percent_earmarked [Float, nil] default value applied to each created drawer # @return [Array<Container::Room>] the created Room containers, or false on failure def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms 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 # Prevent shrinking a dimension when placed container items would fall # outside the new boundary on that axis. def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end end def check_for_contents if !is_empty? errors.add(:base, 'is not empty, empty it before destroying it') throw :abort end end end |
#print_label ⇒ String
Returns 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# 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 validates :asserted_percent_empty, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validates :asserted_percent_earmarked, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validate :earmarked_requires_sufficient_empty validate :size_does_not_exclude_placed_items, if: :persisted? # @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 # @param params [ActionController::Parameters, Hash] with keys: # building_id [Integer] id of the parent Container::Building # drawer_type [String] full STI type name of the drawer, e.g. 'Container::Drawer::CalAcademy' # rooms [Integer] number of Room containers to create # cabinets [Integer] number of Cabinet containers per Room # drawers [Integer] number of Drawer containers per Cabinet # cabinet_size_x [Integer, nil] default size_x applied to each created cabinet # cabinet_size_y [Integer, nil] default size_y applied to each created cabinet # cabinet_size_z [Integer, nil] default size_z applied to each created cabinet # asserted_percent_empty [Float, nil] default value applied to each created drawer # asserted_percent_earmarked [Float, nil] default value applied to each created drawer # @return [Array<Container::Room>] the created Room containers, or false on failure def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms 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 # Prevent shrinking a dimension when placed container items would fall # outside the new boundary on that axis. def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end 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_id ⇒ 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# 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 validates :asserted_percent_empty, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validates :asserted_percent_earmarked, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validate :earmarked_requires_sufficient_empty validate :size_does_not_exclude_placed_items, if: :persisted? # @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 # @param params [ActionController::Parameters, Hash] with keys: # building_id [Integer] id of the parent Container::Building # drawer_type [String] full STI type name of the drawer, e.g. 'Container::Drawer::CalAcademy' # rooms [Integer] number of Room containers to create # cabinets [Integer] number of Cabinet containers per Room # drawers [Integer] number of Drawer containers per Cabinet # cabinet_size_x [Integer, nil] default size_x applied to each created cabinet # cabinet_size_y [Integer, nil] default size_y applied to each created cabinet # cabinet_size_z [Integer, nil] default size_z applied to each created cabinet # asserted_percent_empty [Float, nil] default value applied to each created drawer # asserted_percent_earmarked [Float, nil] default value applied to each created drawer # @return [Array<Container::Room>] the created Room containers, or false on failure def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms 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 # Prevent shrinking a dimension when placed container items would fall # outside the new boundary on that axis. def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end 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_x ⇒ Int
Returns 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# 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 validates :asserted_percent_empty, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validates :asserted_percent_earmarked, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validate :earmarked_requires_sufficient_empty validate :size_does_not_exclude_placed_items, if: :persisted? # @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 # @param params [ActionController::Parameters, Hash] with keys: # building_id [Integer] id of the parent Container::Building # drawer_type [String] full STI type name of the drawer, e.g. 'Container::Drawer::CalAcademy' # rooms [Integer] number of Room containers to create # cabinets [Integer] number of Cabinet containers per Room # drawers [Integer] number of Drawer containers per Cabinet # cabinet_size_x [Integer, nil] default size_x applied to each created cabinet # cabinet_size_y [Integer, nil] default size_y applied to each created cabinet # cabinet_size_z [Integer, nil] default size_z applied to each created cabinet # asserted_percent_empty [Float, nil] default value applied to each created drawer # asserted_percent_earmarked [Float, nil] default value applied to each created drawer # @return [Array<Container::Room>] the created Room containers, or false on failure def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms 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 # Prevent shrinking a dimension when placed container items would fall # outside the new boundary on that axis. def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end 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_y ⇒ Int
Returns 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# 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 validates :asserted_percent_empty, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validates :asserted_percent_earmarked, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validate :earmarked_requires_sufficient_empty validate :size_does_not_exclude_placed_items, if: :persisted? # @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 # @param params [ActionController::Parameters, Hash] with keys: # building_id [Integer] id of the parent Container::Building # drawer_type [String] full STI type name of the drawer, e.g. 'Container::Drawer::CalAcademy' # rooms [Integer] number of Room containers to create # cabinets [Integer] number of Cabinet containers per Room # drawers [Integer] number of Drawer containers per Cabinet # cabinet_size_x [Integer, nil] default size_x applied to each created cabinet # cabinet_size_y [Integer, nil] default size_y applied to each created cabinet # cabinet_size_z [Integer, nil] default size_z applied to each created cabinet # asserted_percent_empty [Float, nil] default value applied to each created drawer # asserted_percent_earmarked [Float, nil] default value applied to each created drawer # @return [Array<Container::Room>] the created Room containers, or false on failure def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms 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 # Prevent shrinking a dimension when placed container items would fall # outside the new boundary on that axis. def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end 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_z ⇒ Int
Returns 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# 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 validates :asserted_percent_empty, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validates :asserted_percent_earmarked, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validate :earmarked_requires_sufficient_empty validate :size_does_not_exclude_placed_items, if: :persisted? # @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 # @param params [ActionController::Parameters, Hash] with keys: # building_id [Integer] id of the parent Container::Building # drawer_type [String] full STI type name of the drawer, e.g. 'Container::Drawer::CalAcademy' # rooms [Integer] number of Room containers to create # cabinets [Integer] number of Cabinet containers per Room # drawers [Integer] number of Drawer containers per Cabinet # cabinet_size_x [Integer, nil] default size_x applied to each created cabinet # cabinet_size_y [Integer, nil] default size_y applied to each created cabinet # cabinet_size_z [Integer, nil] default size_z applied to each created cabinet # asserted_percent_empty [Float, nil] default value applied to each created drawer # asserted_percent_earmarked [Float, nil] default value applied to each created drawer # @return [Array<Container::Room>] the created Room containers, or false on failure def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms 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 # Prevent shrinking a dimension when placed container items would fall # outside the new boundary on that axis. def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end end def check_for_contents if !is_empty? errors.add(:base, 'is not empty, empty it before destroying it') throw :abort end end end |
#type ⇒ String
Returns 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# 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 validates :asserted_percent_empty, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validates :asserted_percent_earmarked, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }, allow_nil: true validate :earmarked_requires_sufficient_empty validate :size_does_not_exclude_placed_items, if: :persisted? # @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 # @param params [ActionController::Parameters, Hash] with keys: # building_id [Integer] id of the parent Container::Building # drawer_type [String] full STI type name of the drawer, e.g. 'Container::Drawer::CalAcademy' # rooms [Integer] number of Room containers to create # cabinets [Integer] number of Cabinet containers per Room # drawers [Integer] number of Drawer containers per Cabinet # cabinet_size_x [Integer, nil] default size_x applied to each created cabinet # cabinet_size_y [Integer, nil] default size_y applied to each created cabinet # cabinet_size_z [Integer, nil] default size_z applied to each created cabinet # asserted_percent_empty [Float, nil] default value applied to each created drawer # asserted_percent_earmarked [Float, nil] default value applied to each created drawer # @return [Array<Container::Room>] the created Room containers, or false on failure def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms 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 # Prevent shrinking a dimension when placed container items would fall # outside the new boundary on that axis. def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end 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_name ⇒ String
Returns the "common name" of this class.
164 165 166 |
# File 'app/models/container.rb', line 164 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.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'app/models/container.rb', line 182 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 |
.dimensions ⇒ Object
174 175 176 |
# File 'app/models/container.rb', line 174 def self.dimensions {} end |
.scaffold(params) ⇒ Array<Container::Room>
Returns the created Room containers, or false on failure.
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'app/models/container.rb', line 216 def self.scaffold(params) building_id = params[:building_id].to_i drawer_type = params[:drawer_type].to_s.presence || 'Container::Drawer' room_count = params[:rooms].to_i cabinet_count = params[:cabinets].to_i drawer_count = params[:drawers].to_i cabinet_defaults = {} cabinet_defaults[:size_x] = params[:cabinet_size_x].presence&.to_i unless params[:cabinet_size_x].nil? cabinet_defaults[:size_y] = params[:cabinet_size_y].presence&.to_i unless params[:cabinet_size_y].nil? cabinet_defaults[:size_z] = params[:cabinet_size_z].presence&.to_i unless params[:cabinet_size_z].nil? drawer_defaults = {} drawer_defaults[:asserted_percent_empty] = params[:asserted_percent_empty].presence&.to_f unless params[:asserted_percent_empty].nil? drawer_defaults[:asserted_percent_earmarked] = params[:asserted_percent_earmarked].presence&.to_f unless params[:asserted_percent_earmarked].nil? return false if building_id == 0 || room_count < 1 || cabinet_count < 1 || drawer_count < 1 # The drawer class must exist; if not, fail early. begin drawer_klass = drawer_type.constantize rescue NameError return false end # Not customizable yet cabinet_klass = Container::Cabinet created_rooms = [] begin Container.transaction do building = Container.find(building_id) room_count.times do room = Container::Room.create! building.add_container_items([room]) cabinet_count.times do cabinet = cabinet_klass.create!(cabinet_defaults) room.add_container_items([cabinet]) drawers = drawer_count.times.map { drawer_klass.create!(drawer_defaults) } cabinet.add_container_items(drawers) end created_rooms << room end end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound return false end created_rooms end |
.valid_parents ⇒ Array of Strings
Returns valid containers class names that this container can fit in, by default none.
170 171 172 |
# File 'app/models/container.rb', line 170 def self.valid_parents [] end |
Instance Method Details
#add_container_items(objects) ⇒ Boolean
add the objects to this container
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/container.rb', line 277 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_ids ⇒ Array
Returns of CollectionObject#id of this container's contents (recursive).
110 111 112 113 |
# File 'app/models/container.rb', line 110 def all_collection_object_ids # all_container_items.containing_collection_objects.pluck(:id) collection_objects.map(&:id) end |
#all_contained_objects ⇒ Array
Return all #contained_object(s) (recursive)
94 95 96 97 |
# File 'app/models/container.rb', line 94 def all_contained_objects return [] if !reload_container_item container_item.descendants.map(&:contained_object) end |
#all_container_items ⇒ ContainerItem Scope
Return all ContainerItems contained in this container (recursive)
81 82 83 |
# File 'app/models/container.rb', line 81 def all_container_items reload_container_item.try(:descendants) || ContainerItem.none end |
#available_space ⇒ Integer
Returns the free space in this container (non-recursive).
135 136 137 138 139 140 141 142 |
# File 'app/models/container.rb', line 135 def available_space in_container = container_items.count if size size - in_container else nil end end |
#check_for_contents ⇒ Object (protected)
342 343 344 345 346 347 |
# File 'app/models/container.rb', line 342 def check_for_contents if !is_empty? errors.add(:base, 'is not empty, empty it before destroying it') throw :abort end end |
#collection_object_ids ⇒ Array
Returns of CollectionObject#id of this container's contents (no recursion).
105 106 107 |
# File 'app/models/container.rb', line 105 def collection_object_ids container_items.containing_collection_objects.pluck(:id) end |
#collection_objects ⇒ Array
Returns of CollectionObject#id of this container's CollectionObjects only (with recursion).
100 101 102 |
# File 'app/models/container.rb', line 100 def collection_objects all_container_items.containing_collection_objects.map(&:contained_object) end |
#contained_objects ⇒ Array
Return all #contained_object(s) (non-recursive)
87 88 89 90 |
# File 'app/models/container.rb', line 87 def contained_objects return [] if !reload_container_item container_item.children.map(&:contained_object) end |
#container_items ⇒ ContainerItem Scope
TODO: fix Please call reload_container_item instead. (called from container_items at /Users/jrflood/src/taxonworks/app/models/container.rb:43)
75 76 77 |
# File 'app/models/container.rb', line 75 def container_items container_item&.children || ContainerItem.none end |
#earmarked_requires_sufficient_empty ⇒ Object (protected)
333 334 335 336 337 338 339 340 |
# File 'app/models/container.rb', line 333 def earmarked_requires_sufficient_empty return if asserted_percent_earmarked.nil? if asserted_percent_empty.nil? errors.add(:asserted_percent_empty, 'must be present when earmarked is set') elsif asserted_percent_empty < asserted_percent_earmarked errors.add(:asserted_percent_empty, 'must be greater than or equal to asserted percent earmarked') end end |
#empty_contents ⇒ Object (protected)
306 307 308 |
# File 'app/models/container.rb', line 306 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).
117 118 119 |
# File 'app/models/container.rb', line 117 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).
129 130 131 |
# File 'app/models/container.rb', line 129 def is_full? available_space == 0 end |
#is_nested? ⇒ Boolean
Returns whether this container is nested in other containers.
123 124 125 |
# File 'app/models/container.rb', line 123 def is_nested? container_item && container_item.ancestors.any? end |
#size ⇒ Integer?
TODO: reserved word?
147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'app/models/container.rb', line 147 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 |
#size_does_not_exclude_placed_items ⇒ Object (protected)
Prevent shrinking a dimension when placed container items would fall outside the new boundary on that axis.
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'app/models/container.rb', line 316 def size_does_not_exclude_placed_items { size_x: :disposition_x, size_y: :disposition_y, size_z: :disposition_z }.each do |size_attr, disp_attr| next unless send(:"#{size_attr}_changed?") new_val = send(size_attr) old_val = send(:"#{size_attr}_was") next unless new_val.present? && old_val.present? && new_val < old_val if container_items.where("#{disp_attr} > ?", new_val).exists? errors.add(:base, 'Resize would impact placed containers') return end end end |
#type_is_valid ⇒ Object (protected)
310 311 312 |
# File 'app/models/container.rb', line 310 def type_is_valid raise ActiveRecord::SubclassNotFound, 'Invalid subclass' if type && !CONTAINER_TYPES.include?(type) end |