Skip to content

Commit

Permalink
Add has_many ingredients relation to element
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdeyen committed Apr 9, 2021
1 parent d33b031 commit d219ef6
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 2 deletions.
3 changes: 3 additions & 0 deletions app/models/alchemy/element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

require_dependency "alchemy/element/definitions"
require_dependency "alchemy/element/element_contents"
require_dependency "alchemy/element/element_ingredients"
require_dependency "alchemy/element/element_essences"
require_dependency "alchemy/element/presenters"

Expand All @@ -39,6 +40,7 @@ class Element < BaseRecord
"nestable_elements",
"contents",
"hint",
"ingredients",
"taggable",
"compact",
"message",
Expand Down Expand Up @@ -126,6 +128,7 @@ class Element < BaseRecord
include Definitions
include ElementContents
include ElementEssences
include ElementIngredients
include Presenters

# class methods
Expand Down
132 changes: 132 additions & 0 deletions app/models/alchemy/element/element_ingredients.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# frozen_string_literal: true

module Alchemy
class Element < BaseRecord
# Methods concerning ingredients for elements
#
module ElementIngredients
extend ActiveSupport::Concern

included do
attr_accessor :autogenerate_ingredients

has_many :ingredients,
class_name: "Alchemy::Ingredient",
inverse_of: :element,
dependent: :destroy

after_create :create_ingredients,
unless: -> { autogenerate_ingredients == false }
end

# Find first ingredient from element by given name.
def ingredient_by_name(name)
ingredients_by_name(name).first
end

# Find first ingredient from element by given type.
def ingredient_by_type(type)
ingredients_by_type(type).first
end

# All ingredients from element by given name.
def ingredients_by_name(name)
ingredients.select { |ingredient| ingredient.name == name.to_s }
end

# All ingredients from element by given type.
def ingredients_by_type(type)
ingredients.select do |ingredient|
ingredient.type == Ingredient.normalize_type(type)
end
end

# Updates all related ingredients by calling +update+ on each of them.
#
# @param ingredients_attributes [Hash]
# Hash of ingredients attributes.
# The keys has to be the #id of the ingredient to update.
# The values a Hash of attribute names and values
#
# @return [Boolean]
# True if +errors+ are blank or +ingredients_attributes+ hash is nil
#
# == Example
#
# @element.update_ingredients(
# "1" => {ingredient: "Title"},
# "2" => {link: "https://google.com"}
# )
#
def update_ingredients(ingredients_attributes)
return true if ingredients_attributes.nil?

ingredients.each do |ingredient|
ingredient_hash = ingredients_attributes[ingredient.id.to_s] || next
ingredient.update(ingredient_hash) || errors.add(:ingredients, :validation_failed)
end

errors.empty?
end

# Copy current ingredient's ingredients to given target element
def copy_ingredients_to(element)
ingredients.map do |ingredient|
Ingredient.copy(ingredient, element_id: element.id)
end
end

# Returns all element ingredient definitions from the +elements.yml+ file
def ingredient_definitions
definition.fetch(:ingredients, [])
end

# Returns the definition for given ingredient role
def ingredient_definition_for(role)
if ingredient_definitions.blank?
log_warning "Element #{name} is missing the ingredient definition for #{role}"
nil
else
ingredient_definitions.find { |d| d["name"] == role.to_s }
end
end

# Returns an array of all Richtext ingredients ids from elements
#
# This is used to re-initialize the TinyMCE editor in the element editor.
#
def richtext_ingredients_ids
ids = ingredients.select(&:has_tinymce?).collect(&:id)
expanded_nested_elements = nested_elements.expanded
if expanded_nested_elements.present?
ids += expanded_nested_elements.collect(&:richtext_ingredients_ids)
end
ids.flatten
end

# Has any of the ingredients validations defined?
def has_validations?
ingredients.any?(&:has_validations?)
end

# All element ingredients where the validation has failed.
def ingredients_with_errors
ingredients.select(&:validation_failed?)
end

private

# Builds ingredients for this element as described in the +elements.yml+
def build_ingredients
definition.fetch("ingredients", []).map do |attributes|
Ingredient.new(attributes.merge(element: self))
end
end

# Creates ingredients for this element as described in the +elements.yml+
def create_ingredients
build_ingredients.map(&:save)
end
end
end
end
4 changes: 2 additions & 2 deletions app/models/alchemy/ingredient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Ingredient < BaseRecord
self.abstract_class = true
self.table_name = "alchemy_ingredients"

belongs_to :element, class_name: "Alchemy::Element"
belongs_to :element, class_name: "Alchemy::Element", inverse_of: :ingredients
belongs_to :related_object, polymorphic: true, optional: true

validates :role, presence: true, uniqueness: { scope: :element_id }
Expand Down Expand Up @@ -50,7 +50,7 @@ def settings
def definition
return {} unless element

element.content_definition_for(role) || {}
element.ingredient_definition_for(role) || {}
end

# The first 30 characters of the value
Expand Down

0 comments on commit d219ef6

Please sign in to comment.