Class: Alchemy::Element

Inherits:
BaseRecord
  • Object
show all
Includes:
Definitions, ElementIngredients, Presenters, Publishable, Taggable
Defined in:
app/models/alchemy/element.rb,
app/models/alchemy/element/presenters.rb,
app/models/alchemy/element/definitions.rb,
app/models/alchemy/element/element_ingredients.rb

Defined Under Namespace

Modules: Definitions, ElementIngredients, Presenters

Constant Summary collapse

NAME_REGEXP =
/\A[a-z0-9_-]+\z/
FORBIDDEN_DEFINITION_ATTRIBUTES =
[
  "amount",
  "autogenerate",
  "compact",
  "deprecated",
  "hint",
  "icon",
  "ingredients",
  "message",
  "nestable_elements",
  "searchable",
  "taggable",
  "warning"
].freeze

Constants included from SearchableResource

SearchableResource::SEARCHABLE_COLUMN_TYPES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Presenters

#display_name, #display_name_with_preview_text, #preview_ingredient, #preview_text

Methods included from ElementIngredients

#copy_ingredients_to, #has_validations?, #has_value_for?, #ingredient_by_role, #ingredient_by_type, #ingredient_definition_for, #ingredient_definitions, #ingredients_by_type, #ingredients_with_errors, #richtext_ingredients_ids, #value_for

Methods included from Definitions

#definition

Methods included from Publishable

#already_public_for?, #public?, #publishable?, #still_public_for?

Methods included from Taggable

included, #tag_list=

Methods included from ConfigMissing

#const_missing

Methods included from SearchableResource

#ransackable_associations, #ransackable_attributes, #ransackable_scopes, #ransortable_attributes

Instance Attribute Details

#autogenerate_nested_elementsObject

Returns the value of attribute autogenerate_nested_elements.



102
103
104
# File 'app/models/alchemy/element.rb', line 102

def autogenerate_nested_elements
  @autogenerate_nested_elements
end

Class Method Details

.all_from_clipboard(clipboard) ⇒ Object



164
165
166
167
168
# File 'app/models/alchemy/element.rb', line 164

def all_from_clipboard(clipboard)
  return none if clipboard.nil?

  where(id: clipboard.collect { |e| e["id"] })
end

.all_from_clipboard_for_page(clipboard, page) ⇒ Object

All elements in clipboard that could be placed on page



172
173
174
175
176
# File 'app/models/alchemy/element.rb', line 172

def all_from_clipboard_for_page(clipboard, page)
  return none if clipboard.nil? || page.nil?

  all_from_clipboard(clipboard).where(name: page.available_element_names)
end

.all_from_clipboard_for_parent_element(clipboard, parent_element) ⇒ Object

All elements in clipboard that could be placed as a child of parent_element



179
180
181
182
183
# File 'app/models/alchemy/element.rb', line 179

def all_from_clipboard_for_parent_element(clipboard, parent_element)
  return none if clipboard.nil? || parent_element.nil?

  all_from_clipboard(clipboard).where(name: parent_element.definition.nestable_elements)
end

.copy(source_element, differences = {}) ⇒ Object

This methods does a copy of source and all its ingredients.

Options

You can pass a differences Hash as second option to update attributes for the copy.

Example

@copy = Alchemy::Element.copy(@element, {public: false})
@copy.public? # => false


160
161
162
# File 'app/models/alchemy/element.rb', line 160

def copy(source_element, differences = {})
  Alchemy::DuplicateElement.new(source_element).call(differences)
end

.new(attributes = {}) ⇒ Object

Builds a new element as described in /config/alchemy/elements.yml

  • Returns a new Alchemy::Element object if no name is given in attributes, because the definition can not be found w/o name

  • Raises Alchemy::ElementDefinitionError if no definition for given attributes could be found



137
138
139
140
141
142
143
144
145
146
147
# File 'app/models/alchemy/element.rb', line 137

def new(attributes = {})
  return super if attributes[:name].blank?

  element_attributes = attributes.to_h.merge(name: attributes[:name].split("#").first)
  element_definition = Element.definition_by_name(element_attributes[:name])
  if element_definition.nil?
    raise(ElementDefinitionError, attributes)
  end

  super(element_definition.attributes.merge(element_attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES))
end

Instance Method Details

#compact?Boolean

Defined as compact element?

Returns:

  • (Boolean)


263
264
265
# File 'app/models/alchemy/element.rb', line 263

def compact?
  definition.compact
end

#deprecated?Boolean

Defined as deprecated element?

You can either set true or a String on your elements definition.

Passing true

- name: old_element
  deprecated: true

The deprecation notice can be translated. Either as global notice for all deprecated elements.

en:
  alchemy:
    element_deprecation_notice: Foo baz widget is deprecated

Or add a translation to your locale file for a per element notice.

en:
  alchemy:
    element_deprecation_notices:
      old_element: Foo baz widget is deprecated

Pass a String

- name: old_element
  deprecated: This element will be removed soon.

Returns:

  • (Boolean)

    Boolean



295
296
297
# File 'app/models/alchemy/element.rb', line 295

def deprecated?
  !!definition.deprecated
end

#expanded?Boolean

The opposite of folded?

Returns:

  • (Boolean)


258
259
260
# File 'app/models/alchemy/element.rb', line 258

def expanded?
  !folded?
end

#folded_parent_element_idsArray<Integer>

Returns IDs of all folded parent elements from immediate parent up to root

Walks up the ancestor chain and collects only the ones that are folded, skipping already expanded parents.

Returns:

  • (Array<Integer>)

    Folded parent element IDs from immediate parent to root



192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/models/alchemy/element.rb', line 192

def folded_parent_element_ids
  return [] unless parent_element_id

  ids = []
  current_id = parent_element_id
  while current_id
    folded, parent_id = self.class.where(id: current_id).pick(:folded, :parent_element_id)
    ids << current_id if folded
    current_id = parent_id
  end
  ids
end

#nestable_elementsObject

A collection of element names that can be nested inside this element.



318
319
320
# File 'app/models/alchemy/element.rb', line 318

def nestable_elements
  definition.nestable_elements
end

#next(name = nil) ⇒ Object

Returns next public element from same page.

Pass an element name to get next of this kind.



209
210
211
212
# File 'app/models/alchemy/element.rb', line 209

def next(name = nil)
  elements = page.elements.published.where("position > ?", position)
  select_element(elements, name, :asc)
end

#prev(name = nil) ⇒ Object

Returns previous public element from same page.

Pass an element name to get previous of this kind.



218
219
220
221
# File 'app/models/alchemy/element.rb', line 218

def prev(name = nil)
  elements = page.elements.published.where("position < ?", position)
  select_element(elements, name, :desc)
end

#public=(value) ⇒ Object

Convenience setter to set public_on attribute when setting public to true or false.



234
235
236
237
238
239
240
241
242
# File 'app/models/alchemy/element.rb', line 234

def public=(value)
  @public_on_explicitely_set = true
  if ActiveModel::Type::Boolean.new.cast(value)
    self.public_on = Time.current
    self.public_until = nil
  else
    self.public_until = Time.current
  end
end

#public_on=(value) ⇒ Object

Override setter to track if public_on was already set in order to not override it with default value if someone explicitly set it to nil.



247
248
249
250
# File 'app/models/alchemy/element.rb', line 247

def public_on=(value)
  @public_on_explicitely_set = true
  super
end

#store_page(page) ⇒ Object

Stores the page into touchable_pages (Pages that have to be touched after updating the element).



224
225
226
227
228
229
230
# File 'app/models/alchemy/element.rb', line 224

def store_page(page)
  return true if page.nil?

  unless touchable_pages.include? page
    touchable_pages << page
  end
end

#taggable?Boolean

Returns true if the definition of this element has a taggable true value.

Returns:

  • (Boolean)


253
254
255
# File 'app/models/alchemy/element.rb', line 253

def taggable?
  definition.taggable == true
end

#to_partial_pathObject

The element’s view partial is dependent from its name

Define elements

Elements are defined in the config/alchemy/elements.yml file

- name: article
  ingredients:
  ...

Override the view

Element partials live in app/views/alchemy/elements



313
314
315
# File 'app/models/alchemy/element.rb', line 313

def to_partial_path
  "alchemy/elements/#{name}"
end