module Workarea class Content # This class represents a single block of content. These are grouped # by their area field for display in the storefront. # # The actual HTML rendered is defined by the type field. This # field is used to decide which partial is used to display for storefront. # The data field is passed to the partial as local assigns. # # This flexibility makes it very easy to add a new type to the system: # 1) Define the block type using the DSL ({Workarea.define_content_block_types}) # 2) Add the storefront partial, e.g. workarea/storefront/content_blocks/types/_foo.html.haml # class Block include ApplicationDocument include Releasable include Ordering # @!attribute type_id # @return [Symbol] the content block type id # field :type_id, type: Symbol # @!attribute area # @return [String] what area on the page this block belongs to # field :area, type: String, default: 'default' # @!attribute data # @return [HashWithIndifferentAccess] data passed to partials to render # field :data, type: HashWithIndifferentAccess, default: HashWithIndifferentAccess.new, localize: true # @!attribute hidden_breakpoints # @return [Array] the breakpoints for which the block will be hidden # field :hidden_breakpoints, type: Array, default: [] # @!attribute content # @return [Content] # embedded_in :content, class_name: 'Workarea::Content', inverse_of: :blocks validates :area, presence: true validates :type, presence: true validate :data_fields before_validation :set_defaults, on: :create before_validation :typecast_data delegate :icon, :fieldsets, to: :type # Tries to find a unique name for this block relative to the other blocks # in the content area. # # @return [String] # def name BlockName.new(self).to_s end # The bag of data used to render this content block on the storefront. # Any relevant data for rendering can be stored here. All data will be # typecasted and validated by the {Workarea::Content::Field} system. # # @return [HashWithIndifferentAccess] # def data value = super unless value.nil? || value.is_a?(HashWithIndifferentAccess) wrapped_hash = value.with_indifferent_access self.send(:data=, wrapped_hash) unless self.frozen? value = wrapped_hash end value end def data=(value) unless value.is_a?(HashWithIndifferentAccess) value = value.with_indifferent_access end super(value) end # The {Workarea::Content::BlockType} that this block is. See documentation # for {Workarea.define_content_block_types} for info how to define block types. # # @return [Workarea::Content::BlockType] # def type Workarea.config.content_block_types.detect { |bt| bt.id == type_id } end def type=(value) self.type_id = value.is_a?(BlockType) ? value.id : value end # Any miscellaneous config set on the {#type} by the content block DSL. # # @return [HashWithIndifferentAccess] # def config type.config.with_indifferent_access end # Whether this block is at the top of the blocks list # # @return [Boolean] # def at_top? self == siblings.select(&:active?).first end # Whether this block is at the top of the blocks list # # @return [Boolean] # def at_bottom? self == siblings.select(&:active?).last end def to_draft result = Content::BlockDraft.instantiate(as_document.merge('content_id' => _parent.id)) result.new_record = true result end private def set_defaults return if type.blank? || data.present? self.data = type.defaults end def typecast_data self.data = type.typecast!(data) if type.present? && data.present? end def data_fields return unless type.present? type.fields.each do |field| if field.required? && data[field.slug.to_s].blank? errors.add(field.slug, I18n.t('errors.messages.blank')) end end end def siblings return self.class.none if content.blank? content.blocks.where(area: area) end end end end