Class: Composition
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Composition
- Includes:
- Housekeeping, Shared::IsRender, Shared::Pinnable
- Defined in:
- app/models/composition.rb
Overview
A Composition is the arrangement of things for rendering, for example a layout of labels on a page. Compositions are Render, not Data.
Composition is not instantiated directly, see Layout and Template.
Instance Attribute Summary collapse
-
#description ⇒ String?
A free text description.
-
#is_community ⇒ true?
True when the Composition is shared across all projects, in which case project_id is nil.
-
#name ⇒ String
The name of the Composition, unique within type.
-
#project_id ⇒ Integer?
The project ID, nil when is_community.
-
#type ⇒ String
The subclass, e.g.
Class Method Summary collapse
-
.community_permitted? ⇒ Boolean
True if some instances may be shared across projects (compare IsData's class-level .is_community?, which means all instances are).
-
.select_optimized(user_id, project_id) ⇒ Hash
Pinboard, recent (the Compositions the user worked on last, most recent first) and quick (inserted pins plus the 4 most recent).
-
.used_recently(user_id, project_id) ⇒ Array of Integer
Ids of the Compositions (of this class) visible to the project that user_id last changed, most recent first.
Instance Method Summary collapse
-
#annotates_community_object? ⇒ Boolean
Overrides Housekeeping::Projects so that community records are not required to have a project.
-
#community_has_no_item_objects ⇒ Object
protected
Community Compositions reference model attributes, not instance data.
-
#is_community? ⇒ Boolean
Overrides Housekeeping::Projects, which checks the class.
- #is_community_is_permitted ⇒ Object protected
- #set_community_project_id ⇒ Object protected
- #set_composition_items_project_id ⇒ Object protected
Methods included from Shared::Pinnable
#inserted?, #pinboard_item_for, #pinned?
Methods included from Housekeeping
#has_polymorphic_relationship?
Methods inherited from ApplicationRecord
Instance Attribute Details
#description ⇒ String?
Returns a free text description.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/composition.rb', line 28 class Composition < ApplicationRecord include Housekeeping include Shared::IsRender include Shared::Pinnable # Only true or nil normalizes :is_community, with: -> (v) { v.presence } before_validation :set_community_project_id after_save :set_composition_items_project_id, if: :saved_change_to_project_id? has_many :composition_items, inverse_of: :composition, dependent: :destroy scope :visible_to_project, -> (project_id) { where(project_id:).or(where(is_community: true)) } validates_presence_of :type, :name validates :name, uniqueness: { scope: [:project_id, :type] } validate :is_community_is_permitted validate :community_has_no_item_objects # @return [Boolean] # true if *some* instances may be shared across projects (compare # IsData's class-level .is_community?, which means *all* instances are). # Subclasses that reference only model attributes and attribute # modifiers, never instance data, may override this. def self.community_permitted? false end # @return [Array of Integer] # ids of the Compositions (of this class) visible to the project that # user_id last changed, most recent first. A change to any item counts # as a change to its Composition. Destroyed items leave no trace. def self.used_recently(user_id, project_id) visible = visible_to_project(project_id) own = visible.where(updated_by_id: user_id) .select(:id, :updated_at) items = CompositionItem .where(updated_by_id: user_id, composition_id: visible.select(:id)) .select('composition_id AS id', :updated_at) Composition .from("(#{own.to_sql} UNION ALL #{items.to_sql}) AS used") .group('used.id') .order(Arel.sql('MAX(used.updated_at) DESC')) .limit(10) .pluck('used.id') end # @return [Hash] # pinboard, recent (the Compositions the user worked on last, most # recent first) and quick (inserted pins plus the 4 most recent). Call # on a subclass, e.g. Layout::LightTable, for only its Compositions. def self.select_optimized(user_id, project_id) q = visible_to_project(project_id) pinned = q.pinned_by(user_id).pinned_in_project(project_id) r = used_recently(user_id, project_id) recent = q.where(id: r).to_a.sort_by { |c| r.index(c.id) } { quick: (pinned.pinboard_inserted.to_a + recent.first(4)).uniq, pinboard: pinned.pinboard_ordered.to_a, recent: } end # @return [Boolean] # overrides Housekeeping::Projects, which checks the class. Per record, # unlike Data where community is a property of the class. There is no # class-level .is_community? here, nothing asks it of Composition. def is_community? is_community == true end # @return [Boolean] # overrides Housekeeping::Projects so that community records are not # required to have a project def annotates_community_object? is_community? end protected def is_community_is_permitted if is_community? && !self.class.community_permitted? errors.add(:is_community, "is not permitted for #{type}") end end # Community Compositions reference model attributes, not instance data. def community_has_no_item_objects if is_community? && persisted? && composition_items.where.not(composition_item_object_id: nil).exists? errors.add(:is_community, 'is not permitted when items reference objects') end end def set_composition_items_project_id composition_items.update_all(project_id:) end def set_community_project_id if is_community? self.project_id = nil else self.project_id ||= Current.project_id end end end |
#is_community ⇒ true?
Returns true when the Composition is shared across all projects, in which case project_id is nil. Only permitted for some subclasses, see .community_permitted?. Never false, false is stored as nil.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/composition.rb', line 28 class Composition < ApplicationRecord include Housekeeping include Shared::IsRender include Shared::Pinnable # Only true or nil normalizes :is_community, with: -> (v) { v.presence } before_validation :set_community_project_id after_save :set_composition_items_project_id, if: :saved_change_to_project_id? has_many :composition_items, inverse_of: :composition, dependent: :destroy scope :visible_to_project, -> (project_id) { where(project_id:).or(where(is_community: true)) } validates_presence_of :type, :name validates :name, uniqueness: { scope: [:project_id, :type] } validate :is_community_is_permitted validate :community_has_no_item_objects # @return [Boolean] # true if *some* instances may be shared across projects (compare # IsData's class-level .is_community?, which means *all* instances are). # Subclasses that reference only model attributes and attribute # modifiers, never instance data, may override this. def self.community_permitted? false end # @return [Array of Integer] # ids of the Compositions (of this class) visible to the project that # user_id last changed, most recent first. A change to any item counts # as a change to its Composition. Destroyed items leave no trace. def self.used_recently(user_id, project_id) visible = visible_to_project(project_id) own = visible.where(updated_by_id: user_id) .select(:id, :updated_at) items = CompositionItem .where(updated_by_id: user_id, composition_id: visible.select(:id)) .select('composition_id AS id', :updated_at) Composition .from("(#{own.to_sql} UNION ALL #{items.to_sql}) AS used") .group('used.id') .order(Arel.sql('MAX(used.updated_at) DESC')) .limit(10) .pluck('used.id') end # @return [Hash] # pinboard, recent (the Compositions the user worked on last, most # recent first) and quick (inserted pins plus the 4 most recent). Call # on a subclass, e.g. Layout::LightTable, for only its Compositions. def self.select_optimized(user_id, project_id) q = visible_to_project(project_id) pinned = q.pinned_by(user_id).pinned_in_project(project_id) r = used_recently(user_id, project_id) recent = q.where(id: r).to_a.sort_by { |c| r.index(c.id) } { quick: (pinned.pinboard_inserted.to_a + recent.first(4)).uniq, pinboard: pinned.pinboard_ordered.to_a, recent: } end # @return [Boolean] # overrides Housekeeping::Projects, which checks the class. Per record, # unlike Data where community is a property of the class. There is no # class-level .is_community? here, nothing asks it of Composition. def is_community? is_community == true end # @return [Boolean] # overrides Housekeeping::Projects so that community records are not # required to have a project def annotates_community_object? is_community? end protected def is_community_is_permitted if is_community? && !self.class.community_permitted? errors.add(:is_community, "is not permitted for #{type}") end end # Community Compositions reference model attributes, not instance data. def community_has_no_item_objects if is_community? && persisted? && composition_items.where.not(composition_item_object_id: nil).exists? errors.add(:is_community, 'is not permitted when items reference objects') end end def set_composition_items_project_id composition_items.update_all(project_id:) end def set_community_project_id if is_community? self.project_id = nil else self.project_id ||= Current.project_id end end end |
#name ⇒ String
Returns the name of the Composition, unique within type.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/composition.rb', line 28 class Composition < ApplicationRecord include Housekeeping include Shared::IsRender include Shared::Pinnable # Only true or nil normalizes :is_community, with: -> (v) { v.presence } before_validation :set_community_project_id after_save :set_composition_items_project_id, if: :saved_change_to_project_id? has_many :composition_items, inverse_of: :composition, dependent: :destroy scope :visible_to_project, -> (project_id) { where(project_id:).or(where(is_community: true)) } validates_presence_of :type, :name validates :name, uniqueness: { scope: [:project_id, :type] } validate :is_community_is_permitted validate :community_has_no_item_objects # @return [Boolean] # true if *some* instances may be shared across projects (compare # IsData's class-level .is_community?, which means *all* instances are). # Subclasses that reference only model attributes and attribute # modifiers, never instance data, may override this. def self.community_permitted? false end # @return [Array of Integer] # ids of the Compositions (of this class) visible to the project that # user_id last changed, most recent first. A change to any item counts # as a change to its Composition. Destroyed items leave no trace. def self.used_recently(user_id, project_id) visible = visible_to_project(project_id) own = visible.where(updated_by_id: user_id) .select(:id, :updated_at) items = CompositionItem .where(updated_by_id: user_id, composition_id: visible.select(:id)) .select('composition_id AS id', :updated_at) Composition .from("(#{own.to_sql} UNION ALL #{items.to_sql}) AS used") .group('used.id') .order(Arel.sql('MAX(used.updated_at) DESC')) .limit(10) .pluck('used.id') end # @return [Hash] # pinboard, recent (the Compositions the user worked on last, most # recent first) and quick (inserted pins plus the 4 most recent). Call # on a subclass, e.g. Layout::LightTable, for only its Compositions. def self.select_optimized(user_id, project_id) q = visible_to_project(project_id) pinned = q.pinned_by(user_id).pinned_in_project(project_id) r = used_recently(user_id, project_id) recent = q.where(id: r).to_a.sort_by { |c| r.index(c.id) } { quick: (pinned.pinboard_inserted.to_a + recent.first(4)).uniq, pinboard: pinned.pinboard_ordered.to_a, recent: } end # @return [Boolean] # overrides Housekeeping::Projects, which checks the class. Per record, # unlike Data where community is a property of the class. There is no # class-level .is_community? here, nothing asks it of Composition. def is_community? is_community == true end # @return [Boolean] # overrides Housekeeping::Projects so that community records are not # required to have a project def annotates_community_object? is_community? end protected def is_community_is_permitted if is_community? && !self.class.community_permitted? errors.add(:is_community, "is not permitted for #{type}") end end # Community Compositions reference model attributes, not instance data. def community_has_no_item_objects if is_community? && persisted? && composition_items.where.not(composition_item_object_id: nil).exists? errors.add(:is_community, 'is not permitted when items reference objects') end end def set_composition_items_project_id composition_items.update_all(project_id:) end def set_community_project_id if is_community? self.project_id = nil else self.project_id ||= Current.project_id end end end |
#project_id ⇒ Integer?
Returns the project ID, nil when is_community.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/composition.rb', line 28 class Composition < ApplicationRecord include Housekeeping include Shared::IsRender include Shared::Pinnable # Only true or nil normalizes :is_community, with: -> (v) { v.presence } before_validation :set_community_project_id after_save :set_composition_items_project_id, if: :saved_change_to_project_id? has_many :composition_items, inverse_of: :composition, dependent: :destroy scope :visible_to_project, -> (project_id) { where(project_id:).or(where(is_community: true)) } validates_presence_of :type, :name validates :name, uniqueness: { scope: [:project_id, :type] } validate :is_community_is_permitted validate :community_has_no_item_objects # @return [Boolean] # true if *some* instances may be shared across projects (compare # IsData's class-level .is_community?, which means *all* instances are). # Subclasses that reference only model attributes and attribute # modifiers, never instance data, may override this. def self.community_permitted? false end # @return [Array of Integer] # ids of the Compositions (of this class) visible to the project that # user_id last changed, most recent first. A change to any item counts # as a change to its Composition. Destroyed items leave no trace. def self.used_recently(user_id, project_id) visible = visible_to_project(project_id) own = visible.where(updated_by_id: user_id) .select(:id, :updated_at) items = CompositionItem .where(updated_by_id: user_id, composition_id: visible.select(:id)) .select('composition_id AS id', :updated_at) Composition .from("(#{own.to_sql} UNION ALL #{items.to_sql}) AS used") .group('used.id') .order(Arel.sql('MAX(used.updated_at) DESC')) .limit(10) .pluck('used.id') end # @return [Hash] # pinboard, recent (the Compositions the user worked on last, most # recent first) and quick (inserted pins plus the 4 most recent). Call # on a subclass, e.g. Layout::LightTable, for only its Compositions. def self.select_optimized(user_id, project_id) q = visible_to_project(project_id) pinned = q.pinned_by(user_id).pinned_in_project(project_id) r = used_recently(user_id, project_id) recent = q.where(id: r).to_a.sort_by { |c| r.index(c.id) } { quick: (pinned.pinboard_inserted.to_a + recent.first(4)).uniq, pinboard: pinned.pinboard_ordered.to_a, recent: } end # @return [Boolean] # overrides Housekeeping::Projects, which checks the class. Per record, # unlike Data where community is a property of the class. There is no # class-level .is_community? here, nothing asks it of Composition. def is_community? is_community == true end # @return [Boolean] # overrides Housekeeping::Projects so that community records are not # required to have a project def annotates_community_object? is_community? end protected def is_community_is_permitted if is_community? && !self.class.community_permitted? errors.add(:is_community, "is not permitted for #{type}") end end # Community Compositions reference model attributes, not instance data. def community_has_no_item_objects if is_community? && persisted? && composition_items.where.not(composition_item_object_id: nil).exists? errors.add(:is_community, 'is not permitted when items reference objects') end end def set_composition_items_project_id composition_items.update_all(project_id:) end def set_community_project_id if is_community? self.project_id = nil else self.project_id ||= Current.project_id end end end |
#type ⇒ String
Returns the subclass, e.g. Layout or Template.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/composition.rb', line 28 class Composition < ApplicationRecord include Housekeeping include Shared::IsRender include Shared::Pinnable # Only true or nil normalizes :is_community, with: -> (v) { v.presence } before_validation :set_community_project_id after_save :set_composition_items_project_id, if: :saved_change_to_project_id? has_many :composition_items, inverse_of: :composition, dependent: :destroy scope :visible_to_project, -> (project_id) { where(project_id:).or(where(is_community: true)) } validates_presence_of :type, :name validates :name, uniqueness: { scope: [:project_id, :type] } validate :is_community_is_permitted validate :community_has_no_item_objects # @return [Boolean] # true if *some* instances may be shared across projects (compare # IsData's class-level .is_community?, which means *all* instances are). # Subclasses that reference only model attributes and attribute # modifiers, never instance data, may override this. def self.community_permitted? false end # @return [Array of Integer] # ids of the Compositions (of this class) visible to the project that # user_id last changed, most recent first. A change to any item counts # as a change to its Composition. Destroyed items leave no trace. def self.used_recently(user_id, project_id) visible = visible_to_project(project_id) own = visible.where(updated_by_id: user_id) .select(:id, :updated_at) items = CompositionItem .where(updated_by_id: user_id, composition_id: visible.select(:id)) .select('composition_id AS id', :updated_at) Composition .from("(#{own.to_sql} UNION ALL #{items.to_sql}) AS used") .group('used.id') .order(Arel.sql('MAX(used.updated_at) DESC')) .limit(10) .pluck('used.id') end # @return [Hash] # pinboard, recent (the Compositions the user worked on last, most # recent first) and quick (inserted pins plus the 4 most recent). Call # on a subclass, e.g. Layout::LightTable, for only its Compositions. def self.select_optimized(user_id, project_id) q = visible_to_project(project_id) pinned = q.pinned_by(user_id).pinned_in_project(project_id) r = used_recently(user_id, project_id) recent = q.where(id: r).to_a.sort_by { |c| r.index(c.id) } { quick: (pinned.pinboard_inserted.to_a + recent.first(4)).uniq, pinboard: pinned.pinboard_ordered.to_a, recent: } end # @return [Boolean] # overrides Housekeeping::Projects, which checks the class. Per record, # unlike Data where community is a property of the class. There is no # class-level .is_community? here, nothing asks it of Composition. def is_community? is_community == true end # @return [Boolean] # overrides Housekeeping::Projects so that community records are not # required to have a project def annotates_community_object? is_community? end protected def is_community_is_permitted if is_community? && !self.class.community_permitted? errors.add(:is_community, "is not permitted for #{type}") end end # Community Compositions reference model attributes, not instance data. def community_has_no_item_objects if is_community? && persisted? && composition_items.where.not(composition_item_object_id: nil).exists? errors.add(:is_community, 'is not permitted when items reference objects') end end def set_composition_items_project_id composition_items.update_all(project_id:) end def set_community_project_id if is_community? self.project_id = nil else self.project_id ||= Current.project_id end end end |
Class Method Details
.community_permitted? ⇒ Boolean
Returns true if some instances may be shared across projects (compare IsData's class-level .is_community?, which means all instances are). Subclasses that reference only model attributes and attribute modifiers, never instance data, may override this.
56 57 58 |
# File 'app/models/composition.rb', line 56 def self.community_permitted? false end |
.select_optimized(user_id, project_id) ⇒ Hash
Returns pinboard, recent (the Compositions the user worked on last, most recent first) and quick (inserted pins plus the 4 most recent). Call on a subclass, e.g. Layout::LightTable, for only its Compositions.
85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'app/models/composition.rb', line 85 def self.select_optimized(user_id, project_id) q = visible_to_project(project_id) pinned = q.pinned_by(user_id).pinned_in_project(project_id) r = used_recently(user_id, project_id) recent = q.where(id: r).to_a.sort_by { |c| r.index(c.id) } { quick: (pinned.pinboard_inserted.to_a + recent.first(4)).uniq, pinboard: pinned.pinboard_ordered.to_a, recent: } end |
.used_recently(user_id, project_id) ⇒ Array of Integer
Returns ids of the Compositions (of this class) visible to the project that user_id last changed, most recent first. A change to any item counts as a change to its Composition. Destroyed items leave no trace.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'app/models/composition.rb', line 64 def self.used_recently(user_id, project_id) visible = visible_to_project(project_id) own = visible.where(updated_by_id: user_id) .select(:id, :updated_at) items = CompositionItem .where(updated_by_id: user_id, composition_id: visible.select(:id)) .select('composition_id AS id', :updated_at) Composition .from("(#{own.to_sql} UNION ALL #{items.to_sql}) AS used") .group('used.id') .order(Arel.sql('MAX(used.updated_at) DESC')) .limit(10) .pluck('used.id') end |
Instance Method Details
#annotates_community_object? ⇒ Boolean
Returns overrides Housekeeping::Projects so that community records are not required to have a project.
110 111 112 |
# File 'app/models/composition.rb', line 110 def annotates_community_object? is_community? end |
#community_has_no_item_objects ⇒ Object (protected)
Community Compositions reference model attributes, not instance data.
123 124 125 126 127 128 |
# File 'app/models/composition.rb', line 123 def community_has_no_item_objects if is_community? && persisted? && composition_items.where.not(composition_item_object_id: nil).exists? errors.add(:is_community, 'is not permitted when items reference objects') end end |
#is_community? ⇒ Boolean
Returns overrides Housekeeping::Projects, which checks the class. Per record, unlike Data where community is a property of the class. There is no class-level .is_community? here, nothing asks it of Composition.
103 104 105 |
# File 'app/models/composition.rb', line 103 def is_community? is_community == true end |
#is_community_is_permitted ⇒ Object (protected)
116 117 118 119 120 |
# File 'app/models/composition.rb', line 116 def is_community_is_permitted if is_community? && !self.class.community_permitted? errors.add(:is_community, "is not permitted for #{type}") end end |
#set_community_project_id ⇒ Object (protected)
134 135 136 137 138 139 140 |
# File 'app/models/composition.rb', line 134 def set_community_project_id if is_community? self.project_id = nil else self.project_id ||= Current.project_id end end |
#set_composition_items_project_id ⇒ Object (protected)
130 131 132 |
# File 'app/models/composition.rb', line 130 def set_composition_items_project_id composition_items.update_all(project_id:) end |