Class: CompositionItem

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

Overview

A CompositionItem is one thing placed in a Composition, for example a block of text in a Template, or a label positioned on a page in a Layout. CompositionItems are Render, not Data.

CompositionItem is not instantiated directly. Subclasses define the units and meaning of the attributes, and their own uniqueness/constraints. Subclasses that use composition_id are named Item, e.g. LayoutItem, TemplateItem.

Coordinate system (units are defined by subclasses):

  • Left-handed, as CSS: x increases right, y increases down, z increases toward the viewer (higher z is in front, as CSS z-index). Renderers using a right-handed system (e.g. WebGL/three.js) negate y at their boundary.
  • The origin is the top-left of the Composition, or of the parent item when parent_id is set: child coordinates are relative to the parent, and the parent's rotation and zoom apply to its children (as nested SVG transforms), so moving a parent moves its children.
  • x, y locate the item's top-left corner; width and height extend right and down from there.
  • Rotations pivot on the item's center (as the CSS transform-origin default). A positive rotation_z is clockwise as seen by the viewer.
  • Stacking: higher z is in front. A nil z is 0. Equal z is broken by id, lower (older) behind, so every renderer paints identically.
  • In a Layout only a LayoutItem::GroupItem may be a parent, and groups are translation only, see there.

Direct Known Subclasses

LayoutItem, TemplateItem

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#composition_id ⇒ Integer?

Returns the Composition, not required (e.g. Label).

Returns:

  • (Integer, nil) —

    the Composition, not required (e.g. Label)



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#composition_item_object_id ⇒ Integer?

Returns polymorphic id of the thing composed.

Returns:

  • (Integer, nil) —

    polymorphic id of the thing composed



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#composition_item_object_type ⇒ String?

Returns polymorphic type of the thing composed.

Returns:

  • (String, nil) —

    polymorphic type of the thing composed



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#css_style ⇒ String?

Returns:

  • (String, nil)


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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#height ⇒ Integer?

Returns:

  • (Integer, nil)


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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#is_visible ⇒ Boolean?

Returns:

  • (Boolean, nil)


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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#opacity ⇒ Float?

Returns 0..100.

Returns:

  • (Float, nil) —

    0..100



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#parent_id ⇒ Integer?

Returns the parent CompositionItem, in the same Composition.

Returns:

  • (Integer, nil) —

    the parent CompositionItem, in the same Composition



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#position ⇒ Integer

Returns order among siblings (same composition and parent), see acts_as_list.

Returns:

  • (Integer) —

    order among siblings (same composition and parent), see acts_as_list



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#project_id ⇒ Integer?

Returns the project ID, nil when the Composition is community.

Returns:

  • (Integer, nil) —

    the project ID, nil when the Composition is community.



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#rotation_z ⇒ Float?

Returns rotation about the z axis (out of the page), i.e. flat, in-plane rotation, see Coordinate system and e.g. CSS rotateZ().

Returns:

  • (Float, nil) —

    rotation about the z axis (out of the page), i.e. flat, in-plane rotation, see Coordinate system and e.g. CSS rotateZ()



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#svg_clip ⇒ String?

Returns xml, sensu Depiction.

Returns:

  • (String, nil) —

    xml, sensu Depiction



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#svg_view_box ⇒ String?

Returns sensu Depiction.

Returns:

  • (String, nil) —

    sensu Depiction



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#text ⇒ String?

Returns:

  • (String, nil)


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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#total ⇒ Integer?

Returns:

  • (Integer, nil)


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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#type ⇒ String

Returns the subclass.

Returns:

  • (String) —

    the subclass



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#width ⇒ Integer?

Returns:

  • (Integer, nil)


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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#x ⇒ Float?

Returns left edge, increases right, see Coordinate system.

Returns:

  • (Float, nil) —

    left edge, increases right, see Coordinate system



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#y ⇒ Float?

Returns top edge, increases down, see Coordinate system.

Returns:

  • (Float, nil) —

    top edge, increases down, see Coordinate system



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#z ⇒ Float?

Returns increases toward the viewer, see Coordinate system.

Returns:

  • (Float, nil) —

    increases toward the viewer, see Coordinate system



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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

#zoom ⇒ Float?

Returns:

  • (Float, nil)


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

class CompositionItem < ApplicationRecord
  include Housekeeping
  include Shared::IsRender

  acts_as_list scope: [:project_id, :composition_id, :parent_id]

  before_validation :set_community_project_id

  belongs_to :composition, inverse_of: :composition_items
  belongs_to :composition_item_object, polymorphic: true
  belongs_to :parent, class_name: 'CompositionItem', inverse_of: :children

  has_many :children, class_name: 'CompositionItem', foreign_key: :parent_id,
    inverse_of: :parent, dependent: :destroy

  validates_presence_of :type
  validates :opacity, numericality: { in: 0..100 }, allow_nil: true
  validate :composition_is_unchanged, on: :update
  validate :parent_is_in_composition
  validate :parent_is_not_self_or_descendant, if: :parent_id_changed?
  validate :community_composition_has_no_object
  validate :composition_item_object_is_permitted

  # @return [Boolean]
  #   Unify (ApplicationEnumeration.relation_targets_community?) asks this
  #   of classes that Data has_many of. Items that reference objects are
  #   never community, see #community_composition_has_no_object.
  def self.is_community?
    false
  end

  # @return [Boolean]
  #   true if instances of klass may be the composition_item_object.
  #   Abstract: no objects are permitted unless a subclass says so.
  def self.composition_item_object_permitted?(klass)
    false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects, which checks the class. Per record,
  #   follows the Composition, see Composition#is_community?
  def is_community?
    composition&.is_community? ? true : false
  end

  # @return [Boolean]
  #   overrides Housekeeping::Projects so that items of community
  #   Compositions are not required to have a project
  def annotates_community_object?
    is_community?
  end

  protected

  # Items of a community Composition have no project.
  def set_community_project_id
    self.project_id = nil if is_community?
  end

  # Moving an item would leave its children behind, parented across
  # Compositions.
  def composition_is_unchanged
    if composition_id_changed?
      errors.add(:composition, 'may not be changed')
    end
  end

  def parent_is_in_composition
    if parent && parent.composition_id != composition_id
      errors.add(:parent, 'must be in the same composition')
    end
  end

  # Also rejects a cycle already in the data above self (and terminates).
  def parent_is_not_self_or_descendant
    seen = [id] # may be [nil], which is harmless
    pid = parent_id
    while pid
      if seen.include?(pid)
        errors.add(:parent, 'would create a cycle')
        return
      end
      seen.push(pid)
      pid = CompositionItem.where(id: pid).pick(:parent_id)
    end
  end

  def composition_item_object_is_permitted
    if composition_item_object &&
        !self.class.composition_item_object_permitted?(
          composition_item_object.class
        )
      errors.add(:composition_item_object_type, 'is not permitted')
    end
  end

  # Community Compositions reference model attributes, not instance data.
  def community_composition_has_no_object
    if is_community? && composition_item_object_id.present?
      errors.add(:composition_item_object, 'is not permitted in a community composition')
    end
  end

end

Class Method Details

.composition_item_object_permitted?(klass) ⇒ Boolean

Returns true if instances of klass may be the composition_item_object. Abstract: no objects are permitted unless a subclass says so.

Returns:

  • (Boolean) —

    true if instances of klass may be the composition_item_object. Abstract: no objects are permitted unless a subclass says so.



140
141
142
# File 'app/models/composition_item.rb', line 140

def self.composition_item_object_permitted?(klass)
  false
end

.is_community? ⇒ Boolean

Returns Unify (ApplicationEnumeration.relation_targets_community?) asks this of classes that Data has_many of. Items that reference objects are never community, see #community_composition_has_no_object.

Returns:

  • (Boolean) —

    Unify (ApplicationEnumeration.relation_targets_community?) asks this of classes that Data has_many of. Items that reference objects are never community, see #community_composition_has_no_object.



133
134
135
# File 'app/models/composition_item.rb', line 133

def self.is_community?
  false
end

Instance Method Details

#annotates_community_object? ⇒ Boolean

Returns overrides Housekeeping::Projects so that items of community Compositions are not required to have a project.

Returns:

  • (Boolean) —

    overrides Housekeeping::Projects so that items of community Compositions are not required to have a project



154
155
156
# File 'app/models/composition_item.rb', line 154

def annotates_community_object?
  is_community?
end

#community_composition_has_no_object ⇒ Object (protected)

Community Compositions reference model attributes, not instance data.



203
204
205
206
207
# File 'app/models/composition_item.rb', line 203

def community_composition_has_no_object
  if is_community? && composition_item_object_id.present?
    errors.add(:composition_item_object, 'is not permitted in a community composition')
  end
end

#composition_is_unchanged ⇒ Object (protected)

Moving an item would leave its children behind, parented across Compositions.



167
168
169
170
171
# File 'app/models/composition_item.rb', line 167

def composition_is_unchanged
  if composition_id_changed?
    errors.add(:composition, 'may not be changed')
  end
end

#composition_item_object_is_permitted ⇒ Object (protected)



193
194
195
196
197
198
199
200
# File 'app/models/composition_item.rb', line 193

def composition_item_object_is_permitted
  if composition_item_object &&
      !self.class.composition_item_object_permitted?(
        composition_item_object.class
      )
    errors.add(:composition_item_object_type, 'is not permitted')
  end
end

#is_community? ⇒ Boolean

Returns overrides Housekeeping::Projects, which checks the class. Per record, follows the Composition, see Composition#is_community?.

Returns:

  • (Boolean) —

    overrides Housekeeping::Projects, which checks the class. Per record, follows the Composition, see Composition#is_community?



147
148
149
# File 'app/models/composition_item.rb', line 147

def is_community?
  composition&.is_community? ? true : false
end

#parent_is_in_composition ⇒ Object (protected)



173
174
175
176
177
# File 'app/models/composition_item.rb', line 173

def parent_is_in_composition
  if parent && parent.composition_id != composition_id
    errors.add(:parent, 'must be in the same composition')
  end
end

#parent_is_not_self_or_descendant ⇒ Object (protected)

Also rejects a cycle already in the data above self (and terminates).



180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/models/composition_item.rb', line 180

def parent_is_not_self_or_descendant
  seen = [id] # may be [nil], which is harmless
  pid = parent_id
  while pid
    if seen.include?(pid)
      errors.add(:parent, 'would create a cycle')
      return
    end
    seen.push(pid)
    pid = CompositionItem.where(id: pid).pick(:parent_id)
  end
end

#set_community_project_id ⇒ Object (protected)

Items of a community Composition have no project.



161
162
163
# File 'app/models/composition_item.rb', line 161

def set_community_project_id
  self.project_id = nil if is_community?
end