diff --git a/app/models/alchemy/essence_node.rb b/app/models/alchemy/essence_node.rb
new file mode 100644
index 0000000000..b39a8e6ec4
--- /dev/null
+++ b/app/models/alchemy/essence_node.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Alchemy
+ class EssenceNode < BaseRecord
+ NODE_ID = /\A\d+\z/
+
+ acts_as_essence(
+ ingredient_column: :node,
+ preview_text_column: :node_name,
+ belongs_to: {
+ class_name: "Alchemy::Node",
+ foreign_key: :node_id,
+ inverse_of: :essence_nodes,
+ optional: true
+ }
+ )
+
+ delegate :name, to: :node, prefix: true
+
+ def ingredient=(node)
+ case node
+ when NODE_ID
+ self.node = Alchemy::Page.new(id: node)
+ when Alchemy::Node
+ self.node = node
+ else
+ super
+ end
+ end
+ end
+end
diff --git a/app/models/alchemy/node.rb b/app/models/alchemy/node.rb
index 7649a5d64a..8ef38f8ed2 100644
--- a/app/models/alchemy/node.rb
+++ b/app/models/alchemy/node.rb
@@ -11,6 +11,8 @@ class Node < BaseRecord
belongs_to :language, class_name: 'Alchemy::Language'
belongs_to :page, class_name: 'Alchemy::Page', optional: true, inverse_of: :nodes
+ has_many :essence_nodes, class_name: "Alchemy::Node", inverse_of: :node
+
validates :name, presence: true, if: -> { page.nil? }
validates :url, format: { with: VALID_URL_REGEX }, unless: -> { url.nil? }
diff --git a/app/views/alchemy/essences/_essence_node_editor.html.erb b/app/views/alchemy/essences/_essence_node_editor.html.erb
new file mode 100644
index 0000000000..0dee2f0e82
--- /dev/null
+++ b/app/views/alchemy/essences/_essence_node_editor.html.erb
@@ -0,0 +1,21 @@
+<%#
+ Available locals:
+ * content (The object the essence is linked to the element)
+ * html_options
+
+ Please consult Alchemy::Content.rb docs for further methods on the content object
+%>
+
+ <%= content_label(content) %>
+ <%= select_tag(
+ content.form_field_name,
+ options_from_collection_for_select(
+ local_assigns.fetch(:options, {})[:nodes] || Alchemy::Node.where(site: Alchemy::Site.current),
+ :id,
+ :name,
+ content.essence.node_id
+ ),
+ include_blank: t(".none"),
+ class: "alchemy_selectbox essence_editor_select full_width"
+ ) %>
+
diff --git a/app/views/alchemy/essences/_essence_node_view.html.erb b/app/views/alchemy/essences/_essence_node_view.html.erb
new file mode 100644
index 0000000000..dfe27342d0
--- /dev/null
+++ b/app/views/alchemy/essences/_essence_node_view.html.erb
@@ -0,0 +1,2 @@
+<%# The content object holds the essence %>
+<%= content.ingredient %>
diff --git a/db/migrate/20200423073425_create_alchemy_essence_nodes.rb b/db/migrate/20200423073425_create_alchemy_essence_nodes.rb
new file mode 100644
index 0000000000..43ed9668bf
--- /dev/null
+++ b/db/migrate/20200423073425_create_alchemy_essence_nodes.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class CreateAlchemyEssenceNodes < ActiveRecord::Migration[5.2]
+ def change
+ create_table :alchemy_essence_nodes do |t|
+ t.integer "node_id"
+ t.index ["node_id"], name: "index_alchemy_essence_nodes_on_node_id"
+ t.timestamps
+ end
+ add_foreign_key "alchemy_essence_nodes", "alchemy_nodes", column: "node_id"
+ end
+end
diff --git a/spec/dummy/db/migrate/20200423073425_create_alchemy_essence_nodes.rb b/spec/dummy/db/migrate/20200423073425_create_alchemy_essence_nodes.rb
new file mode 120000
index 0000000000..7c19075432
--- /dev/null
+++ b/spec/dummy/db/migrate/20200423073425_create_alchemy_essence_nodes.rb
@@ -0,0 +1 @@
+../../../../db/migrate/20200423073425_create_alchemy_essence_nodes.rb
\ No newline at end of file
diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb
index 9f941565cf..74697e085d 100644
--- a/spec/dummy/db/schema.rb
+++ b/spec/dummy/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_02_26_213334) do
+ActiveRecord::Schema.define(version: 2020_04_23_073425) do
create_table "alchemy_attachments", force: :cascade do |t|
t.string "name"
@@ -110,6 +110,13 @@
t.integer "updater_id"
end
+ create_table "alchemy_essence_nodes", force: :cascade do |t|
+ t.integer "node_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["node_id"], name: "index_alchemy_essence_nodes_on_node_id"
+ end
+
create_table "alchemy_essence_pages", force: :cascade do |t|
t.integer "page_id"
t.datetime "created_at", null: false
@@ -360,6 +367,7 @@
add_foreign_key "alchemy_contents", "alchemy_elements", column: "element_id", on_update: :cascade, on_delete: :cascade
add_foreign_key "alchemy_elements", "alchemy_pages", column: "page_id", on_update: :cascade, on_delete: :cascade
+ add_foreign_key "alchemy_essence_nodes", "alchemy_nodes", column: "node_id"
add_foreign_key "alchemy_essence_pages", "alchemy_pages", column: "page_id"
add_foreign_key "alchemy_nodes", "alchemy_languages", column: "language_id"
add_foreign_key "alchemy_nodes", "alchemy_pages", column: "page_id", on_delete: :cascade
diff --git a/spec/models/alchemy/essence_node_spec.rb b/spec/models/alchemy/essence_node_spec.rb
new file mode 100644
index 0000000000..b37b9e1ae3
--- /dev/null
+++ b/spec/models/alchemy/essence_node_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Alchemy::EssenceNode, type: :model do
+ it { is_expected.to belong_to(:ingredient_association).optional.class_name('Alchemy::Node') }
+
+ describe '#ingredient' do
+ let(:node) { build(:alchemy_node) }
+
+ subject { described_class.new(node: node).ingredient }
+
+ it { is_expected.to eq(node) }
+ end
+
+ describe '#preview_text' do
+ let(:node) { build(:alchemy_node) }
+
+ subject { described_class.new(node: node).preview_text }
+
+ it { is_expected.to eq(node.name) }
+ end
+end