alchemy_cms 6.0.2 → 6.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of alchemy_cms might be problematic. Click here for more details.
- 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
|