alchemy_cms 6.0.2 → 6.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +17 -1
- data/CHANGELOG.md +17 -0
- data/Gemfile +3 -1
- data/app/helpers/alchemy/elements_helper.rb +7 -5
- data/app/models/alchemy/content/factory.rb +112 -110
- data/app/models/alchemy/content.rb +3 -1
- data/app/models/alchemy/ingredient.rb +2 -0
- data/app/models/alchemy/page/page_natures.rb +5 -5
- data/app/models/alchemy/page/page_scopes.rb +4 -0
- data/config/alchemy/config.yml +1 -1
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +35 -25
- data/lib/alchemy/version.rb +1 -1
- data/package.json +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5eb21e30df422e1b214dd34d5d6166d8aaf195d393590efdf1e6a16a1247910
|
4
|
+
data.tar.gz: aa579d8246cfc7952a2f2491c86a78e272733eb7c21d993d4302201ee2b1a3f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b92efbc92492ccbc49ff95249672d5083499898d6ce70644b68a44fdc49720a716986c138211c308c1e3cf3a7b614db6d04a9463399c81c8167b31622aa1f5c
|
7
|
+
data.tar.gz: 4d297aea832eeea8b51183b234bcad0cacc168283e07f1d83ca180e1a59fb02601756869e74c3348f10b14308a62e660d6dd2b0a04251da51b4c574435e4518a
|
data/.github/workflows/ci.yml
CHANGED
@@ -20,6 +20,7 @@ jobs:
|
|
20
20
|
database:
|
21
21
|
- mysql
|
22
22
|
- postgresql
|
23
|
+
- mariadb
|
23
24
|
exclude:
|
24
25
|
- rails: "6.0"
|
25
26
|
ruby: "3.1"
|
@@ -27,12 +28,18 @@ jobs:
|
|
27
28
|
- rails: "6.0"
|
28
29
|
ruby: "3.1"
|
29
30
|
database: postgresql
|
31
|
+
- rails: "6.0"
|
32
|
+
ruby: "3.1"
|
33
|
+
database: mariadb
|
30
34
|
- rails: "7.0"
|
31
35
|
ruby: "2.6"
|
32
36
|
database: mysql
|
33
37
|
- rails: "7.0"
|
34
38
|
ruby: "2.6"
|
35
39
|
database: postgresql
|
40
|
+
- rails: "7.0"
|
41
|
+
ruby: "2.6"
|
42
|
+
database: mariadb
|
36
43
|
env:
|
37
44
|
DB: ${{ matrix.database }}
|
38
45
|
DB_USER: alchemy_user
|
@@ -58,6 +65,15 @@ jobs:
|
|
58
65
|
MYSQL_DATABASE: alchemy_cms_dummy_test
|
59
66
|
MYSQL_ROOT_PASSWORD: password
|
60
67
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
|
68
|
+
mariadb:
|
69
|
+
image: mariadb:latest
|
70
|
+
ports: ["3307:3306"]
|
71
|
+
env:
|
72
|
+
MARIADB_USER: alchemy_user
|
73
|
+
MARIADB_PASSWORD: password
|
74
|
+
MARIADB_DATABASE: alchemy_cms_dummy_test
|
75
|
+
MARIADB_ROOT_PASSWORD: password
|
76
|
+
options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=5
|
61
77
|
steps:
|
62
78
|
- uses: actions/checkout@v2.3.4
|
63
79
|
- name: Set up Ruby
|
@@ -81,7 +97,7 @@ jobs:
|
|
81
97
|
sudo apt install -qq --fix-missing libpq-dev -o dir::cache::archives="/home/runner/apt/cache"
|
82
98
|
sudo chown -R runner /home/runner/apt/cache
|
83
99
|
- name: Install MySQL headers
|
84
|
-
if: matrix.database == 'mysql'
|
100
|
+
if: matrix.database == 'mysql' || matrix.database == 'mariadb'
|
85
101
|
run: |
|
86
102
|
mkdir -p /home/runner/apt/cache
|
87
103
|
sudo apt update -qq
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## 6.0.5 (2022-05-11)
|
2
|
+
|
3
|
+
- Extract element ingredient migrator [#2337](https://github.com/AlchemyCMS/alchemy_cms/pull/2337) ([tvdeyen](https://github.com/tvdeyen))
|
4
|
+
|
5
|
+
## 6.0.4 (2022-05-06)
|
6
|
+
|
7
|
+
- Add support for Rails' recycable cache keys [#2334](https://github.com/AlchemyCMS/alchemy_cms/pull/2334) ([tvdeyen](https://github.com/tvdeyen))
|
8
|
+
- Make Alchemy::Content::Factory reloadable [#2333](https://github.com/AlchemyCMS/alchemy_cms/pull/2333) ([tvdeyen](https://github.com/tvdeyen))
|
9
|
+
- Wrap the result of rendering into an ActiveSupport::SafeBuffer [#2332](https://github.com/AlchemyCMS/alchemy_cms/pull/2332) ([mamhoff](https://github.com/mamhoff))
|
10
|
+
- Override Alchemy::Page.ransackable_scopes [#2328](https://github.com/AlchemyCMS/alchemy_cms/pull/2328) ([mamhoff](https://github.com/mamhoff))
|
11
|
+
- Default Link Format matcher: Allow tel: protocol [#2327](https://github.com/AlchemyCMS/alchemy_cms/pull/2327) ([mamhoff](https://github.com/mamhoff))
|
12
|
+
|
13
|
+
## 6.0.3 (2022-05-02)
|
14
|
+
|
15
|
+
- Add Support for MariaDB [#2326](https://github.com/AlchemyCMS/alchemy_cms/pull/2326) ([mamhoff](https://github.com/mamhoff))
|
16
|
+
- Fix Alchemy::Content::Factory module definition [#2325](https://github.com/AlchemyCMS/alchemy_cms/pull/2325) ([tvdeyen](https://github.com/tvdeyen))
|
17
|
+
|
1
18
|
## 6.0.2 (2022-04-27)
|
2
19
|
|
3
20
|
- Remove JSON decode from ingredient data store [#2323](https://github.com/AlchemyCMS/alchemy_cms/pull/2323) ([tvdeyen](https://github.com/tvdeyen))
|
data/Gemfile
CHANGED
@@ -17,7 +17,9 @@ end
|
|
17
17
|
if ENV["DB"].nil? || ENV["DB"] == "sqlite"
|
18
18
|
gem "sqlite3", "~> 1.4.1"
|
19
19
|
end
|
20
|
-
|
20
|
+
if ENV["DB"] == "mysql" || ENV["DB"] == "mariadb"
|
21
|
+
gem "mysql2", "~> 0.5.1"
|
22
|
+
end
|
21
23
|
gem "pg", "~> 1.0" if ENV["DB"] == "postgresql"
|
22
24
|
|
23
25
|
group :development, :test do
|
@@ -87,11 +87,13 @@ module Alchemy
|
|
87
87
|
elements = finder.elements(page_version: page_version)
|
88
88
|
|
89
89
|
default_rendering = ->(element, i) { render_element(element, options, i + 1) }
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
90
|
+
capture do
|
91
|
+
if block_given?
|
92
|
+
elements.map.with_index(&blk)
|
93
|
+
else
|
94
|
+
elements.map.with_index(&default_rendering)
|
95
|
+
end.join(options[:separator]).html_safe
|
96
|
+
end
|
95
97
|
end
|
96
98
|
|
97
99
|
# This helper renders a {Alchemy::Element} view partial.
|
@@ -3,139 +3,141 @@
|
|
3
3
|
module Alchemy
|
4
4
|
# Holds everything concerning the building and creating of contents and the related essence object.
|
5
5
|
#
|
6
|
-
|
7
|
-
|
6
|
+
class Content < BaseRecord
|
7
|
+
module Factory
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
SKIPPED_ATTRIBUTES_ON_COPY = %w(position created_at updated_at creator_id updater_id element_id id)
|
12
|
+
|
13
|
+
# Builds a new content as descriped in the elements.yml file.
|
14
|
+
#
|
15
|
+
# @param [Hash]
|
16
|
+
# The content definition used for finding the content in +elements.yml+ file
|
17
|
+
#
|
18
|
+
def new(attributes = {})
|
19
|
+
element = attributes[:element] || Element.find_by(id: attributes[:element_id])
|
20
|
+
return super if attributes.empty? || element.nil?
|
21
|
+
|
22
|
+
definition = element.content_definition_for(attributes[:name])
|
23
|
+
if definition.blank? && attributes[:essence_type].nil?
|
24
|
+
raise ContentDefinitionError, "No definition found in elements.yml for #{attributes.inspect} and #{element.inspect}"
|
25
|
+
end
|
26
|
+
|
27
|
+
super(
|
28
|
+
name: attributes[:name],
|
29
|
+
essence_type: attributes[:essence_type] || normalize_essence_type(definition[:type]),
|
30
|
+
element: element,
|
31
|
+
).tap(&:build_essence)
|
32
|
+
end
|
8
33
|
|
9
|
-
|
10
|
-
|
34
|
+
# Creates a new content from elements definition in the +elements.yml+ file.
|
35
|
+
#
|
36
|
+
# 1. It builds the content
|
37
|
+
# 2. It creates the essence record (content object gets saved)
|
38
|
+
#
|
39
|
+
# @return [Alchemy::Content]
|
40
|
+
#
|
41
|
+
def create(attributes = {})
|
42
|
+
new(attributes).tap do |content|
|
43
|
+
content.essence.save && content.save
|
44
|
+
end
|
45
|
+
end
|
11
46
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
47
|
+
# Creates a copy of source and also copies the associated essence.
|
48
|
+
#
|
49
|
+
# You can pass a differences hash to update the attributes of the copy.
|
50
|
+
#
|
51
|
+
# === Example
|
52
|
+
#
|
53
|
+
# @copy = Alchemy::Content.copy(@content, {element_id: 3})
|
54
|
+
# @copy.element_id # => 3
|
55
|
+
#
|
56
|
+
def copy(source, differences = {})
|
57
|
+
Content.new(
|
58
|
+
source.attributes.with_indifferent_access.
|
59
|
+
except(*SKIPPED_ATTRIBUTES_ON_COPY).
|
60
|
+
merge(differences.with_indifferent_access)
|
61
|
+
).tap do |new_content|
|
62
|
+
new_content.build_essence(
|
63
|
+
source.essence.attributes.
|
64
|
+
except(*SKIPPED_ATTRIBUTES_ON_COPY)
|
65
|
+
)
|
66
|
+
new_content.save
|
67
|
+
end
|
68
|
+
end
|
20
69
|
|
21
|
-
|
22
|
-
|
23
|
-
|
70
|
+
# Returns all content definitions from elements.yml
|
71
|
+
#
|
72
|
+
def definitions
|
73
|
+
definitions = Element.definitions.flat_map { |e| e["contents"] }
|
74
|
+
definitions.compact!
|
75
|
+
definitions
|
24
76
|
end
|
25
77
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
78
|
+
# Returns a normalized Essence type
|
79
|
+
#
|
80
|
+
# Adds Alchemy module name in front of given essence type
|
81
|
+
# unless there is a Class with the specified name that is an essence.
|
82
|
+
#
|
83
|
+
# @param [String]
|
84
|
+
# the essence type to normalize
|
85
|
+
#
|
86
|
+
def normalize_essence_type(essence_type)
|
87
|
+
essence_type = essence_type.classify
|
88
|
+
return essence_type if is_an_essence?(essence_type)
|
89
|
+
|
90
|
+
"Alchemy::#{essence_type}"
|
91
|
+
end
|
32
92
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def create(attributes = {})
|
41
|
-
new(attributes).tap do |content|
|
42
|
-
content.essence.save && content.save
|
93
|
+
private
|
94
|
+
|
95
|
+
def is_an_essence?(essence_type)
|
96
|
+
klass = Module.const_get(essence_type)
|
97
|
+
klass.is_a?(Class) && klass.new.acts_as_essence?
|
98
|
+
rescue NameError
|
99
|
+
false
|
43
100
|
end
|
44
101
|
end
|
45
102
|
|
46
|
-
#
|
47
|
-
|
48
|
-
#
|
49
|
-
#
|
50
|
-
# === Example
|
51
|
-
#
|
52
|
-
# @copy = Alchemy::Content.copy(@content, {element_id: 3})
|
53
|
-
# @copy.element_id # => 3
|
103
|
+
# Instance Methods
|
104
|
+
|
105
|
+
# Returns the definition hash from +elements.yml+ file.
|
54
106
|
#
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
merge(differences.with_indifferent_access)
|
60
|
-
).tap do |new_content|
|
61
|
-
new_content.build_essence(
|
62
|
-
source.essence.attributes.
|
63
|
-
except(*SKIPPED_ATTRIBUTES_ON_COPY)
|
64
|
-
)
|
65
|
-
new_content.save
|
107
|
+
def definition
|
108
|
+
if element.blank?
|
109
|
+
log_warning "Content with id #{id} is missing its Element."
|
110
|
+
return {}
|
66
111
|
end
|
112
|
+
element.content_definition_for(name) || {}
|
67
113
|
end
|
68
114
|
|
69
|
-
#
|
115
|
+
# Build essence from definition.
|
70
116
|
#
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
117
|
+
# If an optional type is passed, this type of essence gets created.
|
118
|
+
#
|
119
|
+
def build_essence(attributes = {})
|
120
|
+
self.essence = essence_class.new(
|
121
|
+
{ content: self, ingredient: default_value }.merge(attributes)
|
122
|
+
)
|
75
123
|
end
|
76
124
|
|
77
|
-
#
|
78
|
-
#
|
79
|
-
# Adds Alchemy module name in front of given essence type
|
80
|
-
# unless there is a Class with the specified name that is an essence.
|
125
|
+
# Creates essence from definition.
|
81
126
|
#
|
82
|
-
#
|
83
|
-
# the essence type to normalize
|
127
|
+
# If an optional type is passed, this type of essence gets created.
|
84
128
|
#
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
"Alchemy::#{essence_type}"
|
129
|
+
def create_essence!(attrs = {})
|
130
|
+
build_essence(attrs).save!
|
131
|
+
save!
|
90
132
|
end
|
91
133
|
|
92
134
|
private
|
93
135
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
false
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Instance Methods
|
103
|
-
|
104
|
-
# Returns the definition hash from +elements.yml+ file.
|
105
|
-
#
|
106
|
-
def definition
|
107
|
-
if element.blank?
|
108
|
-
log_warning "Content with id #{id} is missing its Element."
|
109
|
-
return {}
|
136
|
+
# Returns a class constant from definition's type field or the essence_type column
|
137
|
+
#
|
138
|
+
def essence_class
|
139
|
+
(essence_type || Content.normalize_essence_type(definition["type"])).constantize
|
110
140
|
end
|
111
|
-
element.content_definition_for(name) || {}
|
112
|
-
end
|
113
|
-
|
114
|
-
# Build essence from definition.
|
115
|
-
#
|
116
|
-
# If an optional type is passed, this type of essence gets created.
|
117
|
-
#
|
118
|
-
def build_essence(attributes = {})
|
119
|
-
self.essence = essence_class.new(
|
120
|
-
{ content: self, ingredient: default_value }.merge(attributes)
|
121
|
-
)
|
122
|
-
end
|
123
|
-
|
124
|
-
# Creates essence from definition.
|
125
|
-
#
|
126
|
-
# If an optional type is passed, this type of essence gets created.
|
127
|
-
#
|
128
|
-
def create_essence!(attrs = {})
|
129
|
-
build_essence(attrs).save!
|
130
|
-
save!
|
131
|
-
end
|
132
|
-
|
133
|
-
private
|
134
|
-
|
135
|
-
# Returns a class constant from definition's type field or the essence_type column
|
136
|
-
#
|
137
|
-
def essence_class
|
138
|
-
(essence_type || Content.normalize_essence_type(definition["type"])).constantize
|
139
141
|
end
|
140
142
|
end
|
141
143
|
end
|
@@ -16,13 +16,15 @@
|
|
16
16
|
# updater_id :integer
|
17
17
|
#
|
18
18
|
|
19
|
+
require_dependency "alchemy/content/factory"
|
20
|
+
|
19
21
|
module Alchemy
|
20
22
|
class Content < BaseRecord
|
21
23
|
include Alchemy::Logger
|
22
24
|
include Alchemy::Hints
|
23
25
|
|
24
26
|
# Concerns
|
25
|
-
include
|
27
|
+
include Factory
|
26
28
|
|
27
29
|
belongs_to :essence, polymorphic: true, dependent: :destroy, inverse_of: :content
|
28
30
|
belongs_to :element, touch: true, inverse_of: :contents
|
@@ -103,17 +103,17 @@ module Alchemy
|
|
103
103
|
page_layout.parameterize.underscore
|
104
104
|
end
|
105
105
|
|
106
|
-
# Returns the
|
106
|
+
# Returns the version that's taken for Rails' recycable cache key.
|
107
107
|
#
|
108
108
|
# Uses the +published_at+ value that's updated when the user publishes the page.
|
109
109
|
#
|
110
|
-
# If the page is the current preview it uses the updated_at value as cache key.
|
110
|
+
# If the page is the current preview it uses the +updated_at+ value as cache key.
|
111
111
|
#
|
112
|
-
def
|
112
|
+
def cache_version
|
113
113
|
if Page.current_preview == id
|
114
|
-
|
114
|
+
updated_at.to_s
|
115
115
|
else
|
116
|
-
|
116
|
+
published_at.to_s
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
data/config/alchemy/config.yml
CHANGED
@@ -200,7 +200,7 @@ link_target_options: [blank]
|
|
200
200
|
format_matchers:
|
201
201
|
email: !ruby/regexp '/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/'
|
202
202
|
url: !ruby/regexp '/\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix'
|
203
|
-
link_url: !ruby/regexp '/^(mailto:|\/|[a-z]+:\/\/)/'
|
203
|
+
link_url: !ruby/regexp '/^(tel:|mailto:|\/|[a-z]+:\/\/)/'
|
204
204
|
|
205
205
|
# The layout used for rendering the +alchemy/admin/pages#show+ action.
|
206
206
|
admin_page_preview_layout: application
|
@@ -24,31 +24,8 @@ module Alchemy::Upgrader::Tasks
|
|
24
24
|
if elements.any?
|
25
25
|
puts "-- Creating ingredients for #{elements.count} #{element_definition[:name]}(s)"
|
26
26
|
elements.each do |element|
|
27
|
-
|
28
|
-
|
29
|
-
content = element.content_by_name(ingredient_definition[:role])
|
30
|
-
next unless content
|
31
|
-
|
32
|
-
essence = content.essence
|
33
|
-
ingredient = element.ingredients.build(
|
34
|
-
role: ingredient_definition[:role],
|
35
|
-
type: Alchemy::Ingredient.normalize_type(ingredient_definition[:type]),
|
36
|
-
)
|
37
|
-
belongs_to_associations = essence.class.reflect_on_all_associations(:belongs_to)
|
38
|
-
if belongs_to_associations.any?
|
39
|
-
ingredient.related_object = essence.public_send(belongs_to_associations.first.name)
|
40
|
-
else
|
41
|
-
ingredient.value = content.ingredient
|
42
|
-
end
|
43
|
-
data = ingredient.class.stored_attributes.fetch(:data, []).each_with_object({}) do |attr, d|
|
44
|
-
d[attr] = essence.public_send(attr)
|
45
|
-
end
|
46
|
-
ingredient.data = data
|
47
|
-
print "."
|
48
|
-
ingredient.save!
|
49
|
-
content.destroy!
|
50
|
-
end
|
51
|
-
end
|
27
|
+
MigrateElementIngredients.call(element)
|
28
|
+
print "."
|
52
29
|
end
|
53
30
|
puts "\n"
|
54
31
|
else
|
@@ -58,5 +35,38 @@ module Alchemy::Upgrader::Tasks
|
|
58
35
|
end
|
59
36
|
end
|
60
37
|
end
|
38
|
+
|
39
|
+
class MigrateElementIngredients
|
40
|
+
def self.call(element)
|
41
|
+
Alchemy::Element.transaction do
|
42
|
+
element.definition[:ingredients].each do |ingredient_definition|
|
43
|
+
ingredient = element.ingredients.build(
|
44
|
+
role: ingredient_definition[:role],
|
45
|
+
type: Alchemy::Ingredient.normalize_type(ingredient_definition[:type]),
|
46
|
+
)
|
47
|
+
|
48
|
+
content = element.content_by_name(ingredient_definition[:role])
|
49
|
+
if content
|
50
|
+
essence = content.essence
|
51
|
+
if essence
|
52
|
+
belongs_to_associations = essence.class.reflect_on_all_associations(:belongs_to)
|
53
|
+
if belongs_to_associations.any?
|
54
|
+
ingredient.related_object = essence.public_send(belongs_to_associations.first.name)
|
55
|
+
else
|
56
|
+
ingredient.value = content.ingredient
|
57
|
+
end
|
58
|
+
data = ingredient.class.stored_attributes.fetch(:data, []).each_with_object({}) do |attr, d|
|
59
|
+
d[attr] = essence.public_send(attr)
|
60
|
+
end
|
61
|
+
ingredient.data = data
|
62
|
+
end
|
63
|
+
content.destroy!
|
64
|
+
end
|
65
|
+
|
66
|
+
ingredient.save!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
61
71
|
end
|
62
72
|
end
|
data/lib/alchemy/version.rb
CHANGED
data/package.json
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alchemy_cms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.
|
4
|
+
version: 6.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas von Deyen
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2022-
|
16
|
+
date: 2022-05-11 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: actionmailer
|