Skip to content

Commit ce9cb9d

Browse files
committed
Add IngredientDefinition model
Hashes are weak objects. We want a strong relationship between the yaml file and the record that is been created out of it.
1 parent f7c6fe1 commit ce9cb9d

17 files changed

Lines changed: 370 additions & 203 deletions

File tree

app/decorators/alchemy/element_editor.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ def respond_to?(*args, **kwargs)
8080
private
8181

8282
def find_or_create_ingredient(definition)
83-
element.ingredients.detect { |i| i.role == definition[:role] } ||
83+
element.ingredients.detect { _1.role == definition.role } ||
8484
element.ingredients.create!(
85-
role: definition[:role],
86-
type: Alchemy::Ingredient.normalize_type(definition[:type])
85+
role: definition.role,
86+
type: Alchemy::Ingredient.normalize_type(definition.type)
8787
)
8888
end
8989
end

app/decorators/alchemy/ingredient_editor.rb

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -99,62 +99,12 @@ def warnings
9999
Logger.warn("ingredient #{role} is missing its definition", caller(1..1))
100100
Alchemy.t(:ingredient_definition_missing)
101101
else
102-
deprecation_notice
103-
end
104-
end
105-
106-
# Returns a deprecation notice for ingredients marked deprecated
107-
#
108-
# You can either use localizations or pass a String as notice
109-
# in the ingredient definition.
110-
#
111-
# == Custom deprecation notices
112-
#
113-
# Use general ingredient deprecation notice
114-
#
115-
# - name: element_name
116-
# ingredients:
117-
# - role: old_ingredient
118-
# type: Text
119-
# deprecated: true
120-
#
121-
# Add a translation to your locale file for a per ingredient notice.
122-
#
123-
# en:
124-
# alchemy:
125-
# ingredient_deprecation_notices:
126-
# element_name:
127-
# old_ingredient: Foo baz widget is deprecated
128-
#
129-
# or use the global translation that apply to all deprecated ingredients.
130-
#
131-
# en:
132-
# alchemy:
133-
# ingredient_deprecation_notice: Foo baz widget is deprecated
134-
#
135-
# or pass string as deprecation notice.
136-
#
137-
# - name: element_name
138-
# ingredients:
139-
# - role: old_ingredient
140-
# type: Text
141-
# deprecated: This ingredient will be removed soon.
142-
#
143-
def deprecation_notice
144-
case definition[:deprecated]
145-
when String
146-
definition[:deprecated]
147-
when TrueClass
148-
Alchemy.t(
149-
role,
150-
scope: [:ingredient_deprecation_notices, element.name],
151-
default: Alchemy.t(:ingredient_deprecated)
152-
)
102+
definition.deprecation_notice(element_name: element&.name)
153103
end
154104
end
155105

156106
def validations
157-
definition.fetch(:validate, [])
107+
definition.validate
158108
end
159109

160110
def format_validation
@@ -173,7 +123,7 @@ def presence_validation?
173123
private
174124

175125
def form_field_counter
176-
element.ingredient_definitions.index { |i| i[:role] == role }
126+
element.ingredient_definitions.index { _1.role == role }
177127
end
178128
end
179129
end

app/models/alchemy/element/element_ingredients.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ def ingredient_definitions
5959
# Returns the definition for given ingredient role
6060
def ingredient_definition_for(role)
6161
if ingredient_definitions.blank?
62-
log_warning "Element #{name} is missing the ingredient definition for #{role}"
6362
nil
6463
else
65-
ingredient_definitions.find { |d| d[:role] == role.to_s }
64+
ingredient_definitions.find { _1.role == role.to_s } ||
65+
log_warning("Element #{name} is missing the ingredient definition for #{role}")
6666
end
6767
end
6868

@@ -99,10 +99,10 @@ def has_value_for?(role)
9999

100100
# Builds ingredients for this element as described in the +elements.yml+
101101
def build_ingredients
102-
ingredient_definitions.each do |attributes|
102+
ingredient_definitions.each do |definition|
103103
ingredients.build(
104-
role: attributes[:role],
105-
type: Alchemy::Ingredient.normalize_type(attributes[:type])
104+
role: definition.role,
105+
type: Alchemy::Ingredient.normalize_type(definition.type)
106106
)
107107
end
108108
end

app/models/alchemy/element/presenters.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ def preview_text_from_preview_ingredient(maxlength)
104104
def first_ingredient_by_definition
105105
return if ingredient_definitions.empty?
106106

107-
role = ingredient_definitions.first["role"]
108-
ingredients.detect { |ingredient| ingredient.role == role }
107+
role = ingredient_definitions.first.role
108+
ingredients.detect { _1.role == role }
109109
end
110110
end
111111
end

app/models/alchemy/element_definition.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def attributes
106106
alias_method :definition, :attributes
107107

108108
def ingredients
109-
super.map(&:with_indifferent_access)
109+
super.map { IngredientDefinition.new(**_1) }
110110
end
111111

112112
# Returns a deprecation notice for elements marked deprecated

app/models/alchemy/ingredient.rb

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ module Alchemy
44
class Ingredient < BaseRecord
55
class DefinitionError < StandardError; end
66

7-
include Hints
8-
97
self.table_name = "alchemy_ingredients"
108

119
attribute :data, :json
@@ -16,7 +14,7 @@ class DefinitionError < StandardError; end
1614
has_one :page, through: :element, class_name: "Alchemy::Page"
1715

1816
after_initialize :set_default_value,
19-
if: -> { definition.key?(:default) && value.nil? }
17+
if: -> { definition.default && value.nil? }
2018

2119
validates :type, presence: true
2220
validates :role, presence: true, uniqueness: {scope: :element_id, case_sensitive: false}
@@ -38,6 +36,8 @@ class DefinitionError < StandardError; end
3836
scope :texts, -> { where(type: "Alchemy::Ingredients::Text") }
3937
scope :videos, -> { where(type: "Alchemy::Ingredients::Video") }
4038

39+
delegate :has_hint?, :hint, to: :definition
40+
4141
class << self
4242
# Defines getter and setter method aliases for related object
4343
#
@@ -97,15 +97,15 @@ def value
9797

9898
# Settings for this ingredient from the +elements.yml+ definition.
9999
def settings
100-
definition[:settings] || {}
100+
definition.settings
101101
end
102102

103103
# Definition hash for this ingredient from +elements.yml+ file.
104104
#
105105
def definition
106-
return {} unless element
106+
return IngredientDefinition.new unless element
107107

108-
element.ingredient_definition_for(role) || {}
108+
element.ingredient_definition_for(role) || IngredientDefinition.new
109109
end
110110

111111
# The first 30 characters of the value
@@ -126,17 +126,12 @@ def partial_name
126126

127127
# @return [Boolean]
128128
def has_validations?
129-
!!definition[:validate]
130-
end
131-
132-
# @return [Boolean]
133-
def has_hint?
134-
!!definition[:hint]
129+
definition.validate.any?
135130
end
136131

137132
# @return [Boolean]
138133
def deprecated?
139-
!!definition[:deprecated]
134+
!!definition.deprecated
140135
end
141136

142137
# @return [Boolean]
@@ -146,7 +141,7 @@ def has_tinymce?
146141

147142
# @return [Boolean]
148143
def preview_ingredient?
149-
!!definition[:as_element_title]
144+
!!definition.as_element_title
150145
end
151146

152147
# The view component of the ingredient with mapped options.
@@ -163,30 +158,8 @@ def view_component_class
163158
@_view_component_class ||= "#{self.class.name}View".constantize
164159
end
165160

166-
def hint_translation_attribute
167-
role
168-
end
169-
170-
def hint_translation_scope
171-
"ingredient_hints"
172-
end
173-
174161
def set_default_value
175-
self.value = default_value
176-
end
177-
178-
# Returns the default value from ingredient definition
179-
#
180-
# If the value is a symbol it gets passed through i18n
181-
# inside the +alchemy.default_ingredient_texts+ scope
182-
def default_value
183-
default = definition[:default]
184-
case default
185-
when Symbol
186-
Alchemy.t(default, scope: :default_ingredient_texts)
187-
else
188-
default
189-
end
162+
self.value = definition.default_value
190163
end
191164
end
192165
end
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# frozen_string_literal: true
2+
3+
module Alchemy
4+
class IngredientDefinition
5+
include ActiveModel::Model
6+
include ActiveModel::Attributes
7+
include Alchemy::Hints
8+
9+
extend ActiveModel::Translation
10+
11+
attribute :role, :string
12+
attribute :type, :string
13+
attribute :as_element_title, :boolean, default: false
14+
attribute :settings, default: {}
15+
attribute :validate, default: []
16+
attribute :group, :string
17+
attribute :default
18+
attribute :deprecated
19+
attribute :hint
20+
21+
validates :role,
22+
presence: true,
23+
format: {
24+
with: /\A[a-z_-]+\z/
25+
}
26+
27+
validates :type,
28+
presence: true,
29+
format: {
30+
with: /\A[A-Z][a-zA-Z]+\z/
31+
}
32+
33+
delegate :blank?, to: :role
34+
35+
def attributes
36+
super.with_indifferent_access
37+
end
38+
39+
def settings
40+
super.with_indifferent_access
41+
end
42+
43+
def validate
44+
super.map do |validation|
45+
case validation
46+
when Hash
47+
validation.with_indifferent_access
48+
else
49+
validation
50+
end
51+
end
52+
end
53+
54+
# Returns the default value from ingredient definition
55+
#
56+
# If the value is a symbol it gets passed through i18n
57+
# inside the +alchemy.default_ingredient_texts+ scope
58+
def default_value
59+
case default
60+
when Symbol
61+
Alchemy.t(default, scope: :default_ingredient_texts)
62+
when String
63+
default
64+
end
65+
end
66+
67+
# Returns a deprecation notice for ingredients marked deprecated
68+
#
69+
# You can either use localizations or pass a String as notice
70+
# in the ingredient definition.
71+
#
72+
# == Custom deprecation notices
73+
#
74+
# Use general ingredient deprecation notice
75+
#
76+
# - name: element_name
77+
# ingredients:
78+
# - role: old_ingredient
79+
# type: Text
80+
# deprecated: true
81+
#
82+
# Add a translation to your locale file for a per ingredient notice.
83+
#
84+
# en:
85+
# alchemy:
86+
# ingredient_deprecation_notices:
87+
# old_ingredient: Foo baz widget is deprecated
88+
#
89+
# You can scope the translation per element as well.
90+
#
91+
# en:
92+
# alchemy:
93+
# ingredient_deprecation_notices:
94+
# element_name:
95+
# old_ingredient: Elements foo baz widget is deprecated
96+
#
97+
# or use the global translation that apply to all deprecated ingredients.
98+
#
99+
# en:
100+
# alchemy:
101+
# ingredient_deprecation_notice: Foo baz widget is deprecated
102+
#
103+
# or pass string as deprecation notice.
104+
#
105+
# - name: element_name
106+
# ingredients:
107+
# - role: old_ingredient
108+
# type: Text
109+
# deprecated: This ingredient will be removed soon.
110+
#
111+
def deprecation_notice(element_name: nil)
112+
case deprecated
113+
when String
114+
deprecated
115+
when TrueClass
116+
Alchemy.t(
117+
role,
118+
scope: [:ingredient_deprecation_notices, element_name].compact,
119+
default: Alchemy.t(:ingredient_deprecated)
120+
)
121+
end
122+
end
123+
124+
private
125+
126+
def hint_translation_scope
127+
:ingredient_hints
128+
end
129+
130+
def hint_translation_attribute
131+
role
132+
end
133+
end
134+
end

app/models/alchemy/ingredient_validator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def validate(ingredient)
6464
attr_reader :ingredient
6565

6666
def validations
67-
ingredient.definition.fetch(:validate, [])
67+
ingredient.definition.validate
6868
end
6969

7070
def validate_presence(*)

app/views/alchemy/admin/elements/_element.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
<!-- Ingredients -->
3434
<% if element.has_ingredients_defined? %>
3535
<div class="element-ingredient-editors">
36-
<%= render element.ingredients.select { |i| !i.definition[:group] }, element_form: f %>
36+
<%= render element.ingredients.select { !_1.definition.group }, element_form: f %>
3737

3838
<!-- Each ingredient group -->
39-
<% element.ingredients.select { |i| i.definition[:group] }.group_by { |i| i.definition[:group] }.each do |group, ingredients| %>
39+
<% element.ingredients.select { _1.definition.group }.group_by { _1.definition.group }.each do |group, ingredients| %>
4040
<%= content_tag :details, class: "ingredient-group", id: "element_#{element.id}_ingredient_group_#{group.parameterize.underscore}", is: "alchemy-ingredient-group" do %>
4141
<summary>
4242
<%= element.translated_group group %>

0 commit comments

Comments
 (0)