Class: ContainerItem

Inherits:
ApplicationRecord show all
Includes:
Housekeeping, Shared::IsData
Defined in:
app/models/container_item.rb

Overview

A container item is something that has been “localized to” a container. We can’t say that it is “in” the container, because not all containers (e.g. a pin with three specimens) contain the object. By “localized to” we mean that if you can find the container, then its contents should also be locatable.

This concept is a graph edge defining the relationship to the container.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Shared::IsData

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

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#contained_object_idInteger

Returns the id of the object that is contained (Rails polymorphic).

Returns:

  • (Integer)

    the id of the object that is contained (Rails polymorphic)



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

class ContainerItem < ApplicationRecord

  # @return class
  #   this method calls Module#module_parent
  # TODO: This method can be placed elsewhere inside this class (or even removed if not used)
  #       when https://github.com/ClosureTree/closure_tree/issues/346 is fixed.
  # TODO: issue is closed, testing.
  # def self.parent
  #   self.module_parent
  # end

  has_closure_tree

  include Housekeeping
  include Shared::IsData

  attr_accessor :global_entity

  attr_accessor :container_id

  belongs_to :contained_object, polymorphic: true

  # !! this will prevent accepts_nested assignments if we add this
  validates_presence_of :contained_object_id

  validate :parent_contained_object_is_container
  validate :contained_object_is_container_when_parent_id_is_blank
  validate :contained_object_is_unique
  validate :object_fits_in_container
  validate :position_is_not_replicated
  validate :parent_is_provided_if_object_is_not_container

  scope :containers, -> { where(contained_object_type: 'Container') }
  scope :not_containers, -> { where.not(contained_object_type: 'Container') }
  scope :containing_collection_objects, -> {where(contained_object_type: 'CollectionObject')}

  # before_save :set_container, unless: Proc.new {|n| n.container_id.nil? || errors.any? }
 
  # @params object [Container]
  def container=(object)
    if object.metamorphosize.kind_of?(Container)
      if self.parent
        self.parent.contained_object = object
      else
        # This self required?!
        self.parent = ContainerItem.new(contained_object: object)
      end

      self.parent.save! if !self.parent.new_record?
      save! unless new_record?
    end
  end

  # @param value [a Container#id]
  def container_id=(value)
    @container_id = value
    set_container
  end

  # @return [Container, nil]
  #   the immediate container for this ContainerItem
  def container
    parent&.contained_object
  end

  # TODO: this is silly, type should be the same
  # @return [GlobalID]
  #   ! not a string
  def global_entity
    contained_object.to_global_id if contained_object.present?
  end

  # @params entity [String, a global id]
  def global_entity=(entity)
    self.contained_object = GlobalID::Locator.locate(entity)
  end

  protected

  def set_container
    c = Container.find(container_id)

    # Already in some container
    if parent && parent.persisted? 
      self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
    # Not in container
    else
      # In same container as something else
      if d = c.container_item
        self.parent = d
      # In a new container
      else
        self.parent = ContainerItem.create!(contained_object: c) 
      end
    end

    # self.parent.save! if !self.parent.new_record?
    # save! unless new_record?
  end

  def object_fits_in_container
    if parent
      %w{x y z}.each do |coord|
        c = send("disposition_#{coord}")
        errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
      end
    end
  end

  def position_is_not_replicated
    if parent && (disposition_x || disposition_y || disposition_z)
      if ContainerItem.where.not(id: id).
          where(parent: parent,
                disposition_x: disposition_x,
                disposition_y: disposition_y,
                disposition_z: disposition_z ).count > 0
        errors.add(:base, 'position is already taken in this container')
      end
    end
  end

  # If the contained_object is a CollectionObject, it must have a parent container reference
  def contained_object_is_container_when_parent_id_is_blank
    if parent_id.blank? && container_id.blank? && container.blank?
      errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
    end
  end

  # parent_id links an object to a container through container_item
  def parent_contained_object_is_container
    unless parent_id.blank? && parent.nil?
      errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
    end
  end

  def parent_is_provided_if_object_is_not_container
    if !(contained_object_type =~ /Container/) && !parent 
      errors.add(:parent, "must be set if contained object is not a container")
    end
  end

  def contained_object_is_unique
    if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
      errors.add(:contained_object, 'is already in a container_item')
    end
  end

end

#contained_object_typeString

Returns the type of the object that is contained (Rails polymorphic).

Returns:

  • (String)

    the type of the object that is contained (Rails polymorphic)



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

class ContainerItem < ApplicationRecord

  # @return class
  #   this method calls Module#module_parent
  # TODO: This method can be placed elsewhere inside this class (or even removed if not used)
  #       when https://github.com/ClosureTree/closure_tree/issues/346 is fixed.
  # TODO: issue is closed, testing.
  # def self.parent
  #   self.module_parent
  # end

  has_closure_tree

  include Housekeeping
  include Shared::IsData

  attr_accessor :global_entity

  attr_accessor :container_id

  belongs_to :contained_object, polymorphic: true

  # !! this will prevent accepts_nested assignments if we add this
  validates_presence_of :contained_object_id

  validate :parent_contained_object_is_container
  validate :contained_object_is_container_when_parent_id_is_blank
  validate :contained_object_is_unique
  validate :object_fits_in_container
  validate :position_is_not_replicated
  validate :parent_is_provided_if_object_is_not_container

  scope :containers, -> { where(contained_object_type: 'Container') }
  scope :not_containers, -> { where.not(contained_object_type: 'Container') }
  scope :containing_collection_objects, -> {where(contained_object_type: 'CollectionObject')}

  # before_save :set_container, unless: Proc.new {|n| n.container_id.nil? || errors.any? }
 
  # @params object [Container]
  def container=(object)
    if object.metamorphosize.kind_of?(Container)
      if self.parent
        self.parent.contained_object = object
      else
        # This self required?!
        self.parent = ContainerItem.new(contained_object: object)
      end

      self.parent.save! if !self.parent.new_record?
      save! unless new_record?
    end
  end

  # @param value [a Container#id]
  def container_id=(value)
    @container_id = value
    set_container
  end

  # @return [Container, nil]
  #   the immediate container for this ContainerItem
  def container
    parent&.contained_object
  end

  # TODO: this is silly, type should be the same
  # @return [GlobalID]
  #   ! not a string
  def global_entity
    contained_object.to_global_id if contained_object.present?
  end

  # @params entity [String, a global id]
  def global_entity=(entity)
    self.contained_object = GlobalID::Locator.locate(entity)
  end

  protected

  def set_container
    c = Container.find(container_id)

    # Already in some container
    if parent && parent.persisted? 
      self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
    # Not in container
    else
      # In same container as something else
      if d = c.container_item
        self.parent = d
      # In a new container
      else
        self.parent = ContainerItem.create!(contained_object: c) 
      end
    end

    # self.parent.save! if !self.parent.new_record?
    # save! unless new_record?
  end

  def object_fits_in_container
    if parent
      %w{x y z}.each do |coord|
        c = send("disposition_#{coord}")
        errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
      end
    end
  end

  def position_is_not_replicated
    if parent && (disposition_x || disposition_y || disposition_z)
      if ContainerItem.where.not(id: id).
          where(parent: parent,
                disposition_x: disposition_x,
                disposition_y: disposition_y,
                disposition_z: disposition_z ).count > 0
        errors.add(:base, 'position is already taken in this container')
      end
    end
  end

  # If the contained_object is a CollectionObject, it must have a parent container reference
  def contained_object_is_container_when_parent_id_is_blank
    if parent_id.blank? && container_id.blank? && container.blank?
      errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
    end
  end

  # parent_id links an object to a container through container_item
  def parent_contained_object_is_container
    unless parent_id.blank? && parent.nil?
      errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
    end
  end

  def parent_is_provided_if_object_is_not_container
    if !(contained_object_type =~ /Container/) && !parent 
      errors.add(:parent, "must be set if contained object is not a container")
    end
  end

  def contained_object_is_unique
    if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
      errors.add(:contained_object, 'is already in a container_item')
    end
  end

end

#container_idObject

Returns the value of attribute container_id.



57
58
59
# File 'app/models/container_item.rb', line 57

def container_id
  @container_id
end

#disposition_xInteger

Returns a x coordinate for this item in its container.

Returns:

  • (Integer)

    a x coordinate for this item in its container



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

class ContainerItem < ApplicationRecord

  # @return class
  #   this method calls Module#module_parent
  # TODO: This method can be placed elsewhere inside this class (or even removed if not used)
  #       when https://github.com/ClosureTree/closure_tree/issues/346 is fixed.
  # TODO: issue is closed, testing.
  # def self.parent
  #   self.module_parent
  # end

  has_closure_tree

  include Housekeeping
  include Shared::IsData

  attr_accessor :global_entity

  attr_accessor :container_id

  belongs_to :contained_object, polymorphic: true

  # !! this will prevent accepts_nested assignments if we add this
  validates_presence_of :contained_object_id

  validate :parent_contained_object_is_container
  validate :contained_object_is_container_when_parent_id_is_blank
  validate :contained_object_is_unique
  validate :object_fits_in_container
  validate :position_is_not_replicated
  validate :parent_is_provided_if_object_is_not_container

  scope :containers, -> { where(contained_object_type: 'Container') }
  scope :not_containers, -> { where.not(contained_object_type: 'Container') }
  scope :containing_collection_objects, -> {where(contained_object_type: 'CollectionObject')}

  # before_save :set_container, unless: Proc.new {|n| n.container_id.nil? || errors.any? }
 
  # @params object [Container]
  def container=(object)
    if object.metamorphosize.kind_of?(Container)
      if self.parent
        self.parent.contained_object = object
      else
        # This self required?!
        self.parent = ContainerItem.new(contained_object: object)
      end

      self.parent.save! if !self.parent.new_record?
      save! unless new_record?
    end
  end

  # @param value [a Container#id]
  def container_id=(value)
    @container_id = value
    set_container
  end

  # @return [Container, nil]
  #   the immediate container for this ContainerItem
  def container
    parent&.contained_object
  end

  # TODO: this is silly, type should be the same
  # @return [GlobalID]
  #   ! not a string
  def global_entity
    contained_object.to_global_id if contained_object.present?
  end

  # @params entity [String, a global id]
  def global_entity=(entity)
    self.contained_object = GlobalID::Locator.locate(entity)
  end

  protected

  def set_container
    c = Container.find(container_id)

    # Already in some container
    if parent && parent.persisted? 
      self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
    # Not in container
    else
      # In same container as something else
      if d = c.container_item
        self.parent = d
      # In a new container
      else
        self.parent = ContainerItem.create!(contained_object: c) 
      end
    end

    # self.parent.save! if !self.parent.new_record?
    # save! unless new_record?
  end

  def object_fits_in_container
    if parent
      %w{x y z}.each do |coord|
        c = send("disposition_#{coord}")
        errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
      end
    end
  end

  def position_is_not_replicated
    if parent && (disposition_x || disposition_y || disposition_z)
      if ContainerItem.where.not(id: id).
          where(parent: parent,
                disposition_x: disposition_x,
                disposition_y: disposition_y,
                disposition_z: disposition_z ).count > 0
        errors.add(:base, 'position is already taken in this container')
      end
    end
  end

  # If the contained_object is a CollectionObject, it must have a parent container reference
  def contained_object_is_container_when_parent_id_is_blank
    if parent_id.blank? && container_id.blank? && container.blank?
      errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
    end
  end

  # parent_id links an object to a container through container_item
  def parent_contained_object_is_container
    unless parent_id.blank? && parent.nil?
      errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
    end
  end

  def parent_is_provided_if_object_is_not_container
    if !(contained_object_type =~ /Container/) && !parent 
      errors.add(:parent, "must be set if contained object is not a container")
    end
  end

  def contained_object_is_unique
    if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
      errors.add(:contained_object, 'is already in a container_item')
    end
  end

end

#disposition_yInteger

Returns a y coordinate for this item in its container.

Returns:

  • (Integer)

    a y coordinate for this item in its container



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

class ContainerItem < ApplicationRecord

  # @return class
  #   this method calls Module#module_parent
  # TODO: This method can be placed elsewhere inside this class (or even removed if not used)
  #       when https://github.com/ClosureTree/closure_tree/issues/346 is fixed.
  # TODO: issue is closed, testing.
  # def self.parent
  #   self.module_parent
  # end

  has_closure_tree

  include Housekeeping
  include Shared::IsData

  attr_accessor :global_entity

  attr_accessor :container_id

  belongs_to :contained_object, polymorphic: true

  # !! this will prevent accepts_nested assignments if we add this
  validates_presence_of :contained_object_id

  validate :parent_contained_object_is_container
  validate :contained_object_is_container_when_parent_id_is_blank
  validate :contained_object_is_unique
  validate :object_fits_in_container
  validate :position_is_not_replicated
  validate :parent_is_provided_if_object_is_not_container

  scope :containers, -> { where(contained_object_type: 'Container') }
  scope :not_containers, -> { where.not(contained_object_type: 'Container') }
  scope :containing_collection_objects, -> {where(contained_object_type: 'CollectionObject')}

  # before_save :set_container, unless: Proc.new {|n| n.container_id.nil? || errors.any? }
 
  # @params object [Container]
  def container=(object)
    if object.metamorphosize.kind_of?(Container)
      if self.parent
        self.parent.contained_object = object
      else
        # This self required?!
        self.parent = ContainerItem.new(contained_object: object)
      end

      self.parent.save! if !self.parent.new_record?
      save! unless new_record?
    end
  end

  # @param value [a Container#id]
  def container_id=(value)
    @container_id = value
    set_container
  end

  # @return [Container, nil]
  #   the immediate container for this ContainerItem
  def container
    parent&.contained_object
  end

  # TODO: this is silly, type should be the same
  # @return [GlobalID]
  #   ! not a string
  def global_entity
    contained_object.to_global_id if contained_object.present?
  end

  # @params entity [String, a global id]
  def global_entity=(entity)
    self.contained_object = GlobalID::Locator.locate(entity)
  end

  protected

  def set_container
    c = Container.find(container_id)

    # Already in some container
    if parent && parent.persisted? 
      self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
    # Not in container
    else
      # In same container as something else
      if d = c.container_item
        self.parent = d
      # In a new container
      else
        self.parent = ContainerItem.create!(contained_object: c) 
      end
    end

    # self.parent.save! if !self.parent.new_record?
    # save! unless new_record?
  end

  def object_fits_in_container
    if parent
      %w{x y z}.each do |coord|
        c = send("disposition_#{coord}")
        errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
      end
    end
  end

  def position_is_not_replicated
    if parent && (disposition_x || disposition_y || disposition_z)
      if ContainerItem.where.not(id: id).
          where(parent: parent,
                disposition_x: disposition_x,
                disposition_y: disposition_y,
                disposition_z: disposition_z ).count > 0
        errors.add(:base, 'position is already taken in this container')
      end
    end
  end

  # If the contained_object is a CollectionObject, it must have a parent container reference
  def contained_object_is_container_when_parent_id_is_blank
    if parent_id.blank? && container_id.blank? && container.blank?
      errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
    end
  end

  # parent_id links an object to a container through container_item
  def parent_contained_object_is_container
    unless parent_id.blank? && parent.nil?
      errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
    end
  end

  def parent_is_provided_if_object_is_not_container
    if !(contained_object_type =~ /Container/) && !parent 
      errors.add(:parent, "must be set if contained object is not a container")
    end
  end

  def contained_object_is_unique
    if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
      errors.add(:contained_object, 'is already in a container_item')
    end
  end

end

#disposition_zInteger

Returns a z coordinate for this item in its container.

Returns:

  • (Integer)

    a z coordinate for this item in its container



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

class ContainerItem < ApplicationRecord

  # @return class
  #   this method calls Module#module_parent
  # TODO: This method can be placed elsewhere inside this class (or even removed if not used)
  #       when https://github.com/ClosureTree/closure_tree/issues/346 is fixed.
  # TODO: issue is closed, testing.
  # def self.parent
  #   self.module_parent
  # end

  has_closure_tree

  include Housekeeping
  include Shared::IsData

  attr_accessor :global_entity

  attr_accessor :container_id

  belongs_to :contained_object, polymorphic: true

  # !! this will prevent accepts_nested assignments if we add this
  validates_presence_of :contained_object_id

  validate :parent_contained_object_is_container
  validate :contained_object_is_container_when_parent_id_is_blank
  validate :contained_object_is_unique
  validate :object_fits_in_container
  validate :position_is_not_replicated
  validate :parent_is_provided_if_object_is_not_container

  scope :containers, -> { where(contained_object_type: 'Container') }
  scope :not_containers, -> { where.not(contained_object_type: 'Container') }
  scope :containing_collection_objects, -> {where(contained_object_type: 'CollectionObject')}

  # before_save :set_container, unless: Proc.new {|n| n.container_id.nil? || errors.any? }
 
  # @params object [Container]
  def container=(object)
    if object.metamorphosize.kind_of?(Container)
      if self.parent
        self.parent.contained_object = object
      else
        # This self required?!
        self.parent = ContainerItem.new(contained_object: object)
      end

      self.parent.save! if !self.parent.new_record?
      save! unless new_record?
    end
  end

  # @param value [a Container#id]
  def container_id=(value)
    @container_id = value
    set_container
  end

  # @return [Container, nil]
  #   the immediate container for this ContainerItem
  def container
    parent&.contained_object
  end

  # TODO: this is silly, type should be the same
  # @return [GlobalID]
  #   ! not a string
  def global_entity
    contained_object.to_global_id if contained_object.present?
  end

  # @params entity [String, a global id]
  def global_entity=(entity)
    self.contained_object = GlobalID::Locator.locate(entity)
  end

  protected

  def set_container
    c = Container.find(container_id)

    # Already in some container
    if parent && parent.persisted? 
      self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
    # Not in container
    else
      # In same container as something else
      if d = c.container_item
        self.parent = d
      # In a new container
      else
        self.parent = ContainerItem.create!(contained_object: c) 
      end
    end

    # self.parent.save! if !self.parent.new_record?
    # save! unless new_record?
  end

  def object_fits_in_container
    if parent
      %w{x y z}.each do |coord|
        c = send("disposition_#{coord}")
        errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
      end
    end
  end

  def position_is_not_replicated
    if parent && (disposition_x || disposition_y || disposition_z)
      if ContainerItem.where.not(id: id).
          where(parent: parent,
                disposition_x: disposition_x,
                disposition_y: disposition_y,
                disposition_z: disposition_z ).count > 0
        errors.add(:base, 'position is already taken in this container')
      end
    end
  end

  # If the contained_object is a CollectionObject, it must have a parent container reference
  def contained_object_is_container_when_parent_id_is_blank
    if parent_id.blank? && container_id.blank? && container.blank?
      errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
    end
  end

  # parent_id links an object to a container through container_item
  def parent_contained_object_is_container
    unless parent_id.blank? && parent.nil?
      errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
    end
  end

  def parent_is_provided_if_object_is_not_container
    if !(contained_object_type =~ /Container/) && !parent 
      errors.add(:parent, "must be set if contained object is not a container")
    end
  end

  def contained_object_is_unique
    if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
      errors.add(:contained_object, 'is already in a container_item')
    end
  end

end

#global_entityGlobalID

TODO: this is silly, type should be the same

Returns:

  • (GlobalID)

    ! not a string



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

def global_entity
  @global_entity
end

#localizationString

some additional modifier arbitrarily defining the position of this item, aka disposition, always relative to enclosing container

Returns:

  • (String)


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

class ContainerItem < ApplicationRecord

  # @return class
  #   this method calls Module#module_parent
  # TODO: This method can be placed elsewhere inside this class (or even removed if not used)
  #       when https://github.com/ClosureTree/closure_tree/issues/346 is fixed.
  # TODO: issue is closed, testing.
  # def self.parent
  #   self.module_parent
  # end

  has_closure_tree

  include Housekeeping
  include Shared::IsData

  attr_accessor :global_entity

  attr_accessor :container_id

  belongs_to :contained_object, polymorphic: true

  # !! this will prevent accepts_nested assignments if we add this
  validates_presence_of :contained_object_id

  validate :parent_contained_object_is_container
  validate :contained_object_is_container_when_parent_id_is_blank
  validate :contained_object_is_unique
  validate :object_fits_in_container
  validate :position_is_not_replicated
  validate :parent_is_provided_if_object_is_not_container

  scope :containers, -> { where(contained_object_type: 'Container') }
  scope :not_containers, -> { where.not(contained_object_type: 'Container') }
  scope :containing_collection_objects, -> {where(contained_object_type: 'CollectionObject')}

  # before_save :set_container, unless: Proc.new {|n| n.container_id.nil? || errors.any? }
 
  # @params object [Container]
  def container=(object)
    if object.metamorphosize.kind_of?(Container)
      if self.parent
        self.parent.contained_object = object
      else
        # This self required?!
        self.parent = ContainerItem.new(contained_object: object)
      end

      self.parent.save! if !self.parent.new_record?
      save! unless new_record?
    end
  end

  # @param value [a Container#id]
  def container_id=(value)
    @container_id = value
    set_container
  end

  # @return [Container, nil]
  #   the immediate container for this ContainerItem
  def container
    parent&.contained_object
  end

  # TODO: this is silly, type should be the same
  # @return [GlobalID]
  #   ! not a string
  def global_entity
    contained_object.to_global_id if contained_object.present?
  end

  # @params entity [String, a global id]
  def global_entity=(entity)
    self.contained_object = GlobalID::Locator.locate(entity)
  end

  protected

  def set_container
    c = Container.find(container_id)

    # Already in some container
    if parent && parent.persisted? 
      self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
    # Not in container
    else
      # In same container as something else
      if d = c.container_item
        self.parent = d
      # In a new container
      else
        self.parent = ContainerItem.create!(contained_object: c) 
      end
    end

    # self.parent.save! if !self.parent.new_record?
    # save! unless new_record?
  end

  def object_fits_in_container
    if parent
      %w{x y z}.each do |coord|
        c = send("disposition_#{coord}")
        errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
      end
    end
  end

  def position_is_not_replicated
    if parent && (disposition_x || disposition_y || disposition_z)
      if ContainerItem.where.not(id: id).
          where(parent: parent,
                disposition_x: disposition_x,
                disposition_y: disposition_y,
                disposition_z: disposition_z ).count > 0
        errors.add(:base, 'position is already taken in this container')
      end
    end
  end

  # If the contained_object is a CollectionObject, it must have a parent container reference
  def contained_object_is_container_when_parent_id_is_blank
    if parent_id.blank? && container_id.blank? && container.blank?
      errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
    end
  end

  # parent_id links an object to a container through container_item
  def parent_contained_object_is_container
    unless parent_id.blank? && parent.nil?
      errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
    end
  end

  def parent_is_provided_if_object_is_not_container
    if !(contained_object_type =~ /Container/) && !parent 
      errors.add(:parent, "must be set if contained object is not a container")
    end
  end

  def contained_object_is_unique
    if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
      errors.add(:contained_object, 'is already in a container_item')
    end
  end

end

#parent_idInteger

Returns id of the ContainerItem whose contained_object is a Container, i.e. the container of this ContainerItem.

Returns:

  • (Integer)

    id of the ContainerItem whose contained_object is a Container, i.e. the container of this ContainerItem



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

class ContainerItem < ApplicationRecord

  # @return class
  #   this method calls Module#module_parent
  # TODO: This method can be placed elsewhere inside this class (or even removed if not used)
  #       when https://github.com/ClosureTree/closure_tree/issues/346 is fixed.
  # TODO: issue is closed, testing.
  # def self.parent
  #   self.module_parent
  # end

  has_closure_tree

  include Housekeeping
  include Shared::IsData

  attr_accessor :global_entity

  attr_accessor :container_id

  belongs_to :contained_object, polymorphic: true

  # !! this will prevent accepts_nested assignments if we add this
  validates_presence_of :contained_object_id

  validate :parent_contained_object_is_container
  validate :contained_object_is_container_when_parent_id_is_blank
  validate :contained_object_is_unique
  validate :object_fits_in_container
  validate :position_is_not_replicated
  validate :parent_is_provided_if_object_is_not_container

  scope :containers, -> { where(contained_object_type: 'Container') }
  scope :not_containers, -> { where.not(contained_object_type: 'Container') }
  scope :containing_collection_objects, -> {where(contained_object_type: 'CollectionObject')}

  # before_save :set_container, unless: Proc.new {|n| n.container_id.nil? || errors.any? }
 
  # @params object [Container]
  def container=(object)
    if object.metamorphosize.kind_of?(Container)
      if self.parent
        self.parent.contained_object = object
      else
        # This self required?!
        self.parent = ContainerItem.new(contained_object: object)
      end

      self.parent.save! if !self.parent.new_record?
      save! unless new_record?
    end
  end

  # @param value [a Container#id]
  def container_id=(value)
    @container_id = value
    set_container
  end

  # @return [Container, nil]
  #   the immediate container for this ContainerItem
  def container
    parent&.contained_object
  end

  # TODO: this is silly, type should be the same
  # @return [GlobalID]
  #   ! not a string
  def global_entity
    contained_object.to_global_id if contained_object.present?
  end

  # @params entity [String, a global id]
  def global_entity=(entity)
    self.contained_object = GlobalID::Locator.locate(entity)
  end

  protected

  def set_container
    c = Container.find(container_id)

    # Already in some container
    if parent && parent.persisted? 
      self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
    # Not in container
    else
      # In same container as something else
      if d = c.container_item
        self.parent = d
      # In a new container
      else
        self.parent = ContainerItem.create!(contained_object: c) 
      end
    end

    # self.parent.save! if !self.parent.new_record?
    # save! unless new_record?
  end

  def object_fits_in_container
    if parent
      %w{x y z}.each do |coord|
        c = send("disposition_#{coord}")
        errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
      end
    end
  end

  def position_is_not_replicated
    if parent && (disposition_x || disposition_y || disposition_z)
      if ContainerItem.where.not(id: id).
          where(parent: parent,
                disposition_x: disposition_x,
                disposition_y: disposition_y,
                disposition_z: disposition_z ).count > 0
        errors.add(:base, 'position is already taken in this container')
      end
    end
  end

  # If the contained_object is a CollectionObject, it must have a parent container reference
  def contained_object_is_container_when_parent_id_is_blank
    if parent_id.blank? && container_id.blank? && container.blank?
      errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
    end
  end

  # parent_id links an object to a container through container_item
  def parent_contained_object_is_container
    unless parent_id.blank? && parent.nil?
      errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
    end
  end

  def parent_is_provided_if_object_is_not_container
    if !(contained_object_type =~ /Container/) && !parent 
      errors.add(:parent, "must be set if contained object is not a container")
    end
  end

  def contained_object_is_unique
    if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
      errors.add(:contained_object, 'is already in a container_item')
    end
  end

end

#project_idIntegers

the project ID

Returns:

  • (Integers)

    Integers



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

class ContainerItem < ApplicationRecord

  # @return class
  #   this method calls Module#module_parent
  # TODO: This method can be placed elsewhere inside this class (or even removed if not used)
  #       when https://github.com/ClosureTree/closure_tree/issues/346 is fixed.
  # TODO: issue is closed, testing.
  # def self.parent
  #   self.module_parent
  # end

  has_closure_tree

  include Housekeeping
  include Shared::IsData

  attr_accessor :global_entity

  attr_accessor :container_id

  belongs_to :contained_object, polymorphic: true

  # !! this will prevent accepts_nested assignments if we add this
  validates_presence_of :contained_object_id

  validate :parent_contained_object_is_container
  validate :contained_object_is_container_when_parent_id_is_blank
  validate :contained_object_is_unique
  validate :object_fits_in_container
  validate :position_is_not_replicated
  validate :parent_is_provided_if_object_is_not_container

  scope :containers, -> { where(contained_object_type: 'Container') }
  scope :not_containers, -> { where.not(contained_object_type: 'Container') }
  scope :containing_collection_objects, -> {where(contained_object_type: 'CollectionObject')}

  # before_save :set_container, unless: Proc.new {|n| n.container_id.nil? || errors.any? }
 
  # @params object [Container]
  def container=(object)
    if object.metamorphosize.kind_of?(Container)
      if self.parent
        self.parent.contained_object = object
      else
        # This self required?!
        self.parent = ContainerItem.new(contained_object: object)
      end

      self.parent.save! if !self.parent.new_record?
      save! unless new_record?
    end
  end

  # @param value [a Container#id]
  def container_id=(value)
    @container_id = value
    set_container
  end

  # @return [Container, nil]
  #   the immediate container for this ContainerItem
  def container
    parent&.contained_object
  end

  # TODO: this is silly, type should be the same
  # @return [GlobalID]
  #   ! not a string
  def global_entity
    contained_object.to_global_id if contained_object.present?
  end

  # @params entity [String, a global id]
  def global_entity=(entity)
    self.contained_object = GlobalID::Locator.locate(entity)
  end

  protected

  def set_container
    c = Container.find(container_id)

    # Already in some container
    if parent && parent.persisted? 
      self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
    # Not in container
    else
      # In same container as something else
      if d = c.container_item
        self.parent = d
      # In a new container
      else
        self.parent = ContainerItem.create!(contained_object: c) 
      end
    end

    # self.parent.save! if !self.parent.new_record?
    # save! unless new_record?
  end

  def object_fits_in_container
    if parent
      %w{x y z}.each do |coord|
        c = send("disposition_#{coord}")
        errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
      end
    end
  end

  def position_is_not_replicated
    if parent && (disposition_x || disposition_y || disposition_z)
      if ContainerItem.where.not(id: id).
          where(parent: parent,
                disposition_x: disposition_x,
                disposition_y: disposition_y,
                disposition_z: disposition_z ).count > 0
        errors.add(:base, 'position is already taken in this container')
      end
    end
  end

  # If the contained_object is a CollectionObject, it must have a parent container reference
  def contained_object_is_container_when_parent_id_is_blank
    if parent_id.blank? && container_id.blank? && container.blank?
      errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
    end
  end

  # parent_id links an object to a container through container_item
  def parent_contained_object_is_container
    unless parent_id.blank? && parent.nil?
      errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
    end
  end

  def parent_is_provided_if_object_is_not_container
    if !(contained_object_type =~ /Container/) && !parent 
      errors.add(:parent, "must be set if contained object is not a container")
    end
  end

  def contained_object_is_unique
    if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
      errors.add(:contained_object, 'is already in a container_item')
    end
  end

end

Instance Method Details

#contained_object_is_container_when_parent_id_is_blankObject (protected)

If the contained_object is a CollectionObject, it must have a parent container reference



161
162
163
164
165
# File 'app/models/container_item.rb', line 161

def contained_object_is_container_when_parent_id_is_blank
  if parent_id.blank? && container_id.blank? && container.blank?
    errors.add(:parent_id, 'can only be blank if object is a container') if contained_object_type != 'Container'
  end
end

#contained_object_is_uniqueObject (protected)



180
181
182
183
184
# File 'app/models/container_item.rb', line 180

def contained_object_is_unique
  if ContainerItem.where.not(id: id).where(project_id: project_id, contained_object_id: contained_object_id, contained_object_type: contained_object_type).count > 0
    errors.add(:contained_object, 'is already in a container_item')
  end
end

#containerContainer?

Returns the immediate container for this ContainerItem.

Returns:

  • (Container, nil)

    the immediate container for this ContainerItem



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

def container
  parent&.contained_object
end

#container=(object) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'app/models/container_item.rb', line 78

def container=(object)
  if object.metamorphosize.kind_of?(Container)
    if self.parent
      self.parent.contained_object = object
    else
      # This self required?!
      self.parent = ContainerItem.new(contained_object: object)
    end

    self.parent.save! if !self.parent.new_record?
    save! unless new_record?
  end
end

#object_fits_in_containerObject (protected)



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

def object_fits_in_container
  if parent
    %w{x y z}.each do |coord|
      c = send("disposition_#{coord}")
      errors.add("disposition_#{coord}".to_sym, 'is larger than the container size') if c && parent.contained_object.send("size_#{coord}") < c
    end
  end
end

#parent_contained_object_is_containerObject (protected)

parent_id links an object to a container through container_item



168
169
170
171
172
# File 'app/models/container_item.rb', line 168

def parent_contained_object_is_container
  unless parent_id.blank? && parent.nil?
    errors.add(:parent_id, "can only be set if parent's contained object is a container") if parent.contained_object_type != 'Container'
  end
end

#parent_is_provided_if_object_is_not_containerObject (protected)



174
175
176
177
178
# File 'app/models/container_item.rb', line 174

def parent_is_provided_if_object_is_not_container
  if !(contained_object_type =~ /Container/) && !parent 
    errors.add(:parent, "must be set if contained object is not a container")
  end
end

#position_is_not_replicatedObject (protected)



148
149
150
151
152
153
154
155
156
157
158
# File 'app/models/container_item.rb', line 148

def position_is_not_replicated
  if parent && (disposition_x || disposition_y || disposition_z)
    if ContainerItem.where.not(id: id).
        where(parent: parent,
              disposition_x: disposition_x,
              disposition_y: disposition_y,
              disposition_z: disposition_z ).count > 0
      errors.add(:base, 'position is already taken in this container')
    end
  end
end

#set_containerObject (protected)



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'app/models/container_item.rb', line 118

def set_container
  c = Container.find(container_id)

  # Already in some container
  if parent && parent.persisted? 
    self.parent.update_columns(contained_object_type: 'Container', contained_object_id: c.id)
  # Not in container
  else
    # In same container as something else
    if d = c.container_item
      self.parent = d
    # In a new container
    else
      self.parent = ContainerItem.create!(contained_object: c) 
    end
  end

  # self.parent.save! if !self.parent.new_record?
  # save! unless new_record?
end