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?, #scheduled?, #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.



105
106
107
# File 'app/models/alchemy/element.rb', line 105

def autogenerate_nested_elements
  @autogenerate_nested_elements
end

#skip_ingredient_validationsObject

Returns the value of attribute skip_ingredient_validations.



40
41
42
# File 'app/models/alchemy/element.rb', line 40

def skip_ingredient_validations
  @skip_ingredient_validations
end

Class Method Details

.all_from_clipboard(clipboard) ⇒ Object



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

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



175
176
177
178
179
# File 'app/models/alchemy/element.rb', line 175

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`



182
183
184
185
186
# File 'app/models/alchemy/element.rb', line 182

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


163
164
165
# File 'app/models/alchemy/element.rb', line 163

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



140
141
142
143
144
145
146
147
148
149
150
# File 'app/models/alchemy/element.rb', line 140

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)


266
267
268
# File 'app/models/alchemy/element.rb', line 266

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



298
299
300
# File 'app/models/alchemy/element.rb', line 298

def deprecated?
  !!definition.deprecated
end

#expanded?Boolean

The opposite of folded?

Returns:

  • (Boolean)


261
262
263
# File 'app/models/alchemy/element.rb', line 261

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



195
196
197
198
199
200
201
202
203
204
205
206
# File 'app/models/alchemy/element.rb', line 195

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.



321
322
323
# File 'app/models/alchemy/element.rb', line 321

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.



212
213
214
215
# File 'app/models/alchemy/element.rb', line 212

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.



221
222
223
224
# File 'app/models/alchemy/element.rb', line 221

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.



237
238
239
240
241
242
243
244
245
# File 'app/models/alchemy/element.rb', line 237

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.



250
251
252
253
# File 'app/models/alchemy/element.rb', line 250

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).



227
228
229
230
231
232
233
# File 'app/models/alchemy/element.rb', line 227

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)


256
257
258
# File 'app/models/alchemy/element.rb', line 256

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



316
317
318
# File 'app/models/alchemy/element.rb', line 316

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