diff --git a/decidim-core/app/cells/decidim/content_blocks/participatory_space_hero_cell.rb b/decidim-core/app/cells/decidim/content_blocks/participatory_space_hero_cell.rb
index c5bb72dbe002..8847a73940f7 100644
--- a/decidim-core/app/cells/decidim/content_blocks/participatory_space_hero_cell.rb
+++ b/decidim-core/app/cells/decidim/content_blocks/participatory_space_hero_cell.rb
@@ -32,9 +32,9 @@ def subtitle_text
# If it is called from the landing page content block, use the background image defined there
# Else, use the banner image defined in the space (for assemblies)
def image_path
- return model.images_container.attached_uploader(:background_image).path if model.respond_to?(:images_container)
+ return model.images_container.attached_uploader(:background_image).url if model.respond_to?(:images_container)
- attached_uploader(:banner_image).path
+ attached_uploader(:banner_image).url
end
def has_hashtag?
diff --git a/decidim-core/app/controllers/concerns/decidim/devise_controllers.rb b/decidim-core/app/controllers/concerns/decidim/devise_controllers.rb
index 15ba751a8cc1..afa1bc71cbf2 100644
--- a/decidim-core/app/controllers/concerns/decidim/devise_controllers.rb
+++ b/decidim-core/app/controllers/concerns/decidim/devise_controllers.rb
@@ -21,6 +21,7 @@ module DeviseControllers
include Decidim::SafeRedirect
include NeedsSnippets
include UserBlockedChecker
+ include ActiveStorage::SetCurrent
helper Decidim::TranslationsHelper
helper Decidim::MetaTagsHelper
diff --git a/decidim-core/app/controllers/decidim/application_controller.rb b/decidim-core/app/controllers/decidim/application_controller.rb
index 67e0a3856c65..40bd3c53423e 100644
--- a/decidim-core/app/controllers/decidim/application_controller.rb
+++ b/decidim-core/app/controllers/decidim/application_controller.rb
@@ -24,6 +24,7 @@ class ApplicationController < ::DecidimController
include DisableRedirectionToExternalHost
include NeedsPasswordChange
include LinkedResourceReference
+ include ActiveStorage::SetCurrent
helper Decidim::MetaTagsHelper
helper Decidim::DecidimFormHelper
diff --git a/decidim-core/app/models/decidim/attachment.rb b/decidim-core/app/models/decidim/attachment.rb
index 4d4c58d6ef0b..9352dcac4be9 100644
--- a/decidim-core/app/models/decidim/attachment.rb
+++ b/decidim-core/app/models/decidim/attachment.rb
@@ -84,11 +84,12 @@ def file_type
#
# Returns String.
def url
- if file?
- attached_uploader(:file).path
- elsif link?
- link
- end
+ @url ||=
+ if file?
+ attached_uploader(:file).url
+ elsif link?
+ link
+ end
end
# The URL to download the thumbnail of the file. Only works with images.
@@ -97,7 +98,7 @@ def url
def thumbnail_url
return unless photo?
- attached_uploader(:file).path(variant: :thumbnail)
+ @thumbnail_url ||= attached_uploader(:file).variant_url(:thumbnail)
end
# The URL to download the a big version of the file. Only works with images.
@@ -106,7 +107,7 @@ def thumbnail_url
def big_url
return unless photo?
- attached_uploader(:file).path(variant: :big)
+ @big_url ||= attached_uploader(:file).variant_url(:big)
end
def set_content_type_and_size
diff --git a/decidim-core/app/models/decidim/content_block.rb b/decidim-core/app/models/decidim/content_block.rb
index 1673310835c7..260358f09372 100644
--- a/decidim-core/app/models/decidim/content_block.rb
+++ b/decidim-core/app/models/decidim/content_block.rb
@@ -60,8 +60,8 @@ def reload(*)
#
# # This is how you can access the image data, just like with any other
# # uploader field. You can use the uploader variants too.
- # content_block.images_container.attached_uploader(:my_image).path
- # content_block.images_container.attached_uploader(:my_image).path(variant: :big)
+ # content_block.images_container.attached_uploader(:my_image).url
+ # content_block.images_container.attached_uploader(:my_image).variant_url(:big)
#
# # This will delete the attached image
# content_block.images_container.my_image = nil
diff --git a/decidim-core/app/views/decidim/manifests/show.json.erb b/decidim-core/app/views/decidim/manifests/show.json.erb
index e68ef8d9a52c..b8fd32b199fa 100644
--- a/decidim-core/app/views/decidim/manifests/show.json.erb
+++ b/decidim-core/app/views/decidim/manifests/show.json.erb
@@ -8,22 +8,22 @@
"background_color": "<%= organization_params.colors["primary"] %>",
"icons": [
{
- "src": "<%= current_organization.attached_uploader(:favicon).variant_path :small %>",
+ "src": "<%= current_organization.attached_uploader(:favicon).variant_url :small %>",
"sizes": "32x32",
"type": "image/png"
},
{
- "src": "<%= current_organization.attached_uploader(:favicon).variant_path :medium %>",
+ "src": "<%= current_organization.attached_uploader(:favicon).variant_url :medium %>",
"sizes": "180x180",
"type": "image/png"
},
{
- "src": "<%= current_organization.attached_uploader(:favicon).variant_path :big %>",
+ "src": "<%= current_organization.attached_uploader(:favicon).variant_url :big %>",
"sizes": "192x192",
"type": "image/png"
},
{
- "src": "<%= current_organization.attached_uploader(:favicon).variant_path :huge %>",
+ "src": "<%= current_organization.attached_uploader(:favicon).variant_url :huge %>",
"sizes": "512x512",
"type": "image/png"
}
diff --git a/decidim-core/app/views/layouts/decidim/_logo.html.erb b/decidim-core/app/views/layouts/decidim/_logo.html.erb
index e1b0ccaa1c86..8968b210c5a6 100644
--- a/decidim-core/app/views/layouts/decidim/_logo.html.erb
+++ b/decidim-core/app/views/layouts/decidim/_logo.html.erb
@@ -1,7 +1,7 @@
<% if organization %>
<%= link_to decidim.root_url(host: organization.host), "aria-label": t("front_page_link", scope: "decidim.accessibility") do %>
<% if organization.logo.attached? %>
- <%= image_tag organization.attached_uploader(:logo).variant_path(:medium), alt: t("logo", scope: "decidim.accessibility", organization: current_organization_name) %>
+ <%= image_tag organization.attached_uploader(:logo).variant_url(:medium), alt: t("logo", scope: "decidim.accessibility", organization: current_organization_name) %>
<% else %>
<%= current_organization_name %>
<% end %>
diff --git a/decidim-core/app/views/layouts/decidim/footer/_main_intro.html.erb b/decidim-core/app/views/layouts/decidim/footer/_main_intro.html.erb
index 1bd2fada7cd7..e5860e20edef 100644
--- a/decidim-core/app/views/layouts/decidim/footer/_main_intro.html.erb
+++ b/decidim-core/app/views/layouts/decidim/footer/_main_intro.html.erb
@@ -1,6 +1,6 @@
<% if current_organization.official_img_footer.attached? %>
<%= link_to current_organization.official_url, class: "block mb-6" do %>
- <%= image_tag current_organization.attached_uploader(:official_img_footer).path, alt: current_organization_name, class: "max-h-16" %>
+ <%= image_tag current_organization.attached_uploader(:official_img_footer).url, alt: current_organization_name, class: "max-h-16" %>
<% end %>
<% end %>
diff --git a/decidim-core/app/views/layouts/decidim/header/_main_links_desktop.html.erb b/decidim-core/app/views/layouts/decidim/header/_main_links_desktop.html.erb
index d863b7d017a3..c84471655604 100644
--- a/decidim-core/app/views/layouts/decidim/header/_main_links_desktop.html.erb
+++ b/decidim-core/app/views/layouts/decidim/header/_main_links_desktop.html.erb
@@ -33,7 +33,7 @@
<%= image_tag(
- current_user.attached_uploader(:avatar).path(variant: :thumb),
+ current_user.attached_uploader(:avatar).variant_url(:thumb),
alt: t("decidim.author.avatar", name: decidim_sanitize(current_user.avatar.name))
) %>
diff --git a/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb b/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb
index aef9de54ab05..9d30660f460a 100644
--- a/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb
+++ b/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb
@@ -20,7 +20,7 @@
<% if current_user.avatar.attached? %>
<%= image_tag(
- current_user.attached_uploader(:avatar).path(variant: :thumb),
+ current_user.attached_uploader(:avatar).variant_url(:thumb),
alt: t("decidim.author.avatar", name: decidim_sanitize(current_user.avatar.name))
) %>
diff --git a/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb b/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb
index b8c8fd6aad03..77229e7c8725 100644
--- a/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb
+++ b/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb
@@ -6,7 +6,7 @@
<% if current_user.avatar.attached? %>
<%= image_tag(
- current_user.attached_uploader(:avatar).path(variant: :thumb),
+ current_user.attached_uploader(:avatar).variant_url(:thumb),
alt: t("decidim.author.avatar", name: decidim_sanitize(current_user.avatar.name))
) %>
diff --git a/decidim-core/lib/decidim/asset_router/storage.rb b/decidim-core/lib/decidim/asset_router/storage.rb
index e7d2fb0b67ee..c8f9adf843ec 100644
--- a/decidim-core/lib/decidim/asset_router/storage.rb
+++ b/decidim-core/lib/decidim/asset_router/storage.rb
@@ -6,6 +6,23 @@ module AssetRouter
# saved through ActiveStorage. This handles the different cases for routing
# to the remote routes when using an assets CDN or to local routes when
# using the local disk storage driver.
+ #
+ # Note that when the assets are stored in a remote storage service, such as
+ # Amazon S3, Google Cloud Storage or Azure Storage, this generates the asset
+ # URL directly to the storage service itself bypassing the Rails server and
+ # saving CPU time from serving the asset redirect requests. This causes a
+ # significant performance improvement on pages that display a lot of images.
+ # It will also produce a less significant performance improvement when using
+ # the local disk storage because in this situation, the images are served
+ # using one request instead of two when served directly from the storage
+ # service rather than through the asset redirect URL.
+ #
+ # When implementing changes to the logic, please keep the remote storage
+ # options and performance implications in mind because the specs for this
+ # utility do not cover the remote storage options because the extra
+ # configuration needed to test, the service itself needed for testing and
+ # the extra dependency overhead for adding these remote storage gems when
+ # they are not needed.
class Storage
# Initializes the router.
#
@@ -13,25 +30,36 @@ class Storage
# to
def initialize(asset)
@asset = asset
+ @blob =
+ case asset
+ when ActiveStorage::Blob
+ asset
+ else
+ asset&.blob
+ end
end
# Generates the correct URL to the asset with the provided options.
#
# @param options The options for the URL that are the normal route options
# Rails route helpers accept
- def url(**options)
- if asset.is_a? ActiveStorage::Attached
- routes.rails_blob_url(asset.blob, **default_options.merge(options))
- elsif asset.is_a? ActiveStorage::Blob
- routes.rails_blob_url(asset, **default_options.merge(options))
- else
- representation_url(**options)
+ # @return [String] The URL of the asset
+ def url(**)
+ case asset
+ when ActiveStorage::Attached
+ ensure_current_host(asset.record, **)
+ blob_url(**)
+ when ActiveStorage::Blob
+ blob_url(**)
+ else # ActiveStorage::VariantWithRecord, ActiveStorage::Variant
+ ensure_current_host(nil, **)
+ representation_url(**)
end
end
private
- attr_reader :asset
+ attr_reader :asset, :blob
# Provides the route helpers depending on whether the URL is generated to
# the local host or an external CDN (remote).
@@ -80,24 +108,198 @@ def remote_storage_options
}.compact
end
- # Converts the variation URLs last part to the correct file extension in
- # case the variation has a different format than the original image.
+ # Most of the times the current host should be set through the controller
+ # already when the logic below is unnecessary. This logic is needed e.g.
+ # for serializers where the request context is not available.
+ #
+ # @param record The record for which to check the organization
+ # @param opts Options for building the URL
+ # @return [void]
+ def ensure_current_host(record, **opts)
+ return if asset_url_available?
+
+ options = remote? ? remote_storage_options : routes.default_url_options
+ options = options.merge(opts)
+
+ if opts[:host].blank? && record.present?
+ organization = organization_for(record)
+ options[:host] = organization.host if organization
+ end
+
+ uri =
+ if options[:protocol] == "https" || options[:scheme] == "https"
+ URI::HTTPS.build(options)
+ else
+ URI::HTTP.build(options)
+ end
+
+ ActiveStorage::Current.url_options = { host: uri.to_s }
+ end
+
+ # Determines the organization for the passed record.
#
- # @return [String] The converted representation URL
+ # @param record The record for which to fetch the organization
+ # @return [Decidim::Organization, nil] The organization for the record or
+ # `nil` if the organization cannot be determined
+ def organization_for(record)
+ if record.is_a?(Decidim::Organization)
+ record
+ elsif record.respond_to?(:organization)
+ record.organization
+ end
+ end
+
+ # Returns the URL for the given blob object.
+ #
+ # @param blob The blob object
+ # @param options Options for building the URL
+ # @return [String, nil] The URL to the blob object or `nil` if the blob is
+ # not defined.
+ def blob_url(**options)
+ return unless blob
+
+ if options[:only_path] || remote? || !asset_url_available?
+ routes.rails_blob_url(blob, **default_options.merge(options))
+ else
+ blob.url(**options)
+ end
+ end
+
+ # Returns a representation URL for the asset either directly through the
+ # storage service or through the Rails representation URL in case the
+ # path URL is requested or if the asset variant has not been processed yet
+ # and is not therefore yet stored at the storage service.
+ #
+ # @return [String] The representation URL for the image variant
def representation_url(**options)
+ return rails_representation_url(**options) if options[:only_path] || remote?
+
+ representation_url = variant_url(**options)
+ return representation_url if representation_url.present?
+
+ # In case the representation has not been processed yet, it may not have
+ # a representation URL yet and it therefore needs to be served through
+ # the local representation URL for the first time (or until it has been
+ # processed).
+ if options[:host]
+ rails_representation_url(**options)
+ else
+ representation_url(**options.merge(only_path: true))
+ end
+ end
+
+ # Returns the local Rails representation URL meaning that the asset will
+ # be served through the service itself. This may be necessary if the asset
+ # variant (e.g. a thumbnail) has not been processed yet because the
+ # variant representation has not been requested before.
+ #
+ # Due to performance reasons it is advised to avoid requesting the assets
+ # through the Rails representation URLs when possible because that causes
+ # a lot of requests to the Rails backend and slowness to the service under
+ # heavy loads.
+ #
+ # Converts the variation URLs last part to the correct file extension in
+ # case the variation has a different format than the original image. The
+ # conversion needs to be only done for the Rails representation URLs
+ # because once the image is stored at the storage service, it already has
+ # the correct file extension.
+ #
+ # @param options The options for building the URL
+ # @return [String, nil] The converted representation URL or `nil` if the
+ # asset is not defined.
+ def rails_representation_url(**options)
+ return unless asset
+
representation_url = routes.rails_representation_url(asset, **default_options.merge(options))
+
variation = asset.try(:variation)
return representation_url unless variation
format = variation.try(:format)
return representation_url unless format
+ return unless blob
- original_ext = File.extname(asset.blob.filename.to_s)
+ original_ext = File.extname(blob.filename.to_s)
return representation_url if original_ext == ".#{format}"
- basename = File.basename(asset.blob.filename.to_s, original_ext)
+ basename = File.basename(blob.filename.to_s, original_ext)
representation_url.sub(/#{basename}\.#{original_ext.tr(".", "")}$/, "#{basename}.#{format}")
end
+
+ # Fetches the image variant's URL at the storage service if the variant
+ # has already been processed and is stored at the storage service. If the
+ # variant has not been processed yet, returns `nil` in which case the
+ # variant has to be served through the service's own representation URL
+ # causing it to be processed and stored at the storage service.
+ #
+ # @param options The options for building the URL
+ # @return [String, nil] The variant URL at the storage service or `nil` if
+ # the variant has not been processed yet and does not yet exist at the
+ # storage service or `nil` when the asset is not defined
+ def variant_url(**options)
+ return unless asset
+ return unless asset_url_available?
+ return unless asset_exist?
+
+ case asset
+ when ActiveStorage::VariantWithRecord
+ # This is used when `ActiveStorage.track_variants` is enabled through
+ # `config.active_storage.track_variants`. In case the variant has not
+ # been processed yet, the `#url` method would return nil.
+ #
+ # Note that if the `asset.processed?` returns `true`, the variant
+ # record has been created in the database but it does not mean that
+ # it has been uploaded to the storage service yet. Likely a bug in
+ # ActiveStorage but to be sure that the asset is uploaded to the
+ # storage service, we also check that.
+ asset.url(**options) if asset.processed?
+ else # ActiveStorage::Variant
+ # Check whether the variant exists at the storage service before
+ # returning its URL. Otherwise the URL would be returned even when the
+ # variant is not yet processed causing 404 errors for the images on
+ # the page.
+ #
+ # Note that the `ActiveStorage::Variant#url` method only accepts
+ # certain keyword arguments where as the other objects allow any
+ # keyword arguments.
+ possible_kwargs = asset.method(:url).parameters.select { |p| p[0] == :key }.map { |p| p[1] }
+ asset.url(**options.slice(*possible_kwargs))
+ end
+ end
+
+ # Determines if the asset exists at the storage service.
+ #
+ # @return [Boolean] A boolean answering the question "does this asset
+ # exist at the storage service?".
+ def asset_exist?
+ return false if asset.key.blank?
+
+ blob.service.exist?(asset.key)
+ end
+
+ # Determines if the current host is required to build the asset URL.
+ #
+ # @return [Boolean] A boolean indicating if the current host is required
+ # to build the asset URL.
+ def current_host_required?
+ return false unless blob
+
+ blob.service.is_a?(ActiveStorage::Service::DiskService)
+ end
+
+ # Determines if the asset URL can be generated.
+ #
+ # @return [Boolean] A boolean indicating if the asset URL can be
+ # generated.
+ def asset_url_available?
+ # If the service is an external service, the URL can be generated
+ # regardless of the current host being set.
+ return true unless current_host_required?
+
+ # For the disk service, the URL can be only generated if the current
+ # host has been set.
+ ActiveStorage::Current.url_options&.dig(:host).present?
+ end
end
end
end
diff --git a/decidim-core/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb
index 0269f7ef4194..527c30d8b23e 100644
--- a/decidim-core/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb
+++ b/decidim-core/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb
@@ -10,7 +10,7 @@
it "includes the attachment urls" do
attachment_urls = response["attachments"].map { |attachment| attachment["url"] }
- expect(attachment_urls).to include(*attachments.map(&:url))
+ expect(attachment_urls).to include_blob_urls(*attachments.map(&:file).map(&:blob))
end
end
end
diff --git a/decidim-core/spec/cells/decidim/content_blocks/hero_cell_spec.rb b/decidim-core/spec/cells/decidim/content_blocks/hero_cell_spec.rb
index 69dbe2da4a49..fbaa84fc601b 100644
--- a/decidim-core/spec/cells/decidim/content_blocks/hero_cell_spec.rb
+++ b/decidim-core/spec/cells/decidim/content_blocks/hero_cell_spec.rb
@@ -44,7 +44,7 @@
end
it "uses that image's big version as background" do
- expect(subject.to_s).to include(content_block.images_container.attached_uploader(:background_image).path(variant: :big))
+ expect(subject.to_s).to include(content_block.images_container.attached_uploader(:background_image).variant_url(:big))
end
end
diff --git a/decidim-core/spec/cells/decidim/content_blocks/participatory_space_hero_cell_spec.rb b/decidim-core/spec/cells/decidim/content_blocks/participatory_space_hero_cell_spec.rb
index 8bef65ac6116..e03a269c0b1e 100644
--- a/decidim-core/spec/cells/decidim/content_blocks/participatory_space_hero_cell_spec.rb
+++ b/decidim-core/spec/cells/decidim/content_blocks/participatory_space_hero_cell_spec.rb
@@ -78,7 +78,9 @@
end
it "uses that image's big version as background" do
- expect(subject.to_s).to include(content_block.images_container.attached_uploader(:background_image).path(variant: :big))
+ style = subject.find("section")["style"]
+ background_url = style.match(/background-image:url\('([^']+)'\)/)[1]
+ expect(background_url).to be_blob_url(content_block.images_container.background_image.blob)
end
end
end
diff --git a/decidim-core/spec/lib/asset_router/storage_spec.rb b/decidim-core/spec/lib/asset_router/storage_spec.rb
index 0599affee5b4..7c006394f810 100644
--- a/decidim-core/spec/lib/asset_router/storage_spec.rb
+++ b/decidim-core/spec/lib/asset_router/storage_spec.rb
@@ -17,15 +17,30 @@ module Decidim::AssetRouter
let(:default_port) { Capybara.server_port }
context "with an ActiveStorage::Attached" do
- it "creates the route to the blob" do
- expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ it "creates the disk service route to the blob" do
+ ActiveStorage::Current.url_options = { host: "http://localhost:#{default_port}" }
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
- context "with extra URL options" do
- let(:options) { { port: nil, host: "custom.host", utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
+ context "when the host is not set" do
+ it "sets the host based on the asset" do
+ expect(subject).to match(%r{^http://#{organization.host}:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
+ end
+ end
- it "handles the extra URL options correctly" do
- expect(subject).to match(%r{^http://custom.host/rails/active_storage/blobs/redirect/.*/avatar.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ context "when requesting the local redirect path to the asset" do
+ let(:options) { { only_path: true } }
+
+ it "creates the redirect route to the blob" do
+ expect(subject).to match(%r{^/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
+ end
+
+ context "with extra URL options" do
+ let(:options) { { only_path: true, utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
+
+ it "handles the extra URL options correctly" do
+ expect(subject).to match(%r{^/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ end
end
end
end
@@ -33,25 +48,143 @@ module Decidim::AssetRouter
context "with an ActiveStorage::Blob" do
let(:asset) { organization.official_img_footer.blob }
- it "creates the route to the blob" do
- expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ it "creates the disk service route to the blob" do
+ ActiveStorage::Current.url_options = { host: "http://localhost:#{default_port}" }
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
- context "with extra URL options" do
- let(:options) { { port: nil, host: "custom.host", utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
+ context "when the host is not set" do
+ it "creates the redirect route to the blob" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
+ end
+ end
- it "handles the extra URL options correctly" do
- expect(subject).to match(%r{^http://custom.host/rails/active_storage/blobs/redirect/.*/avatar.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ context "when requesting the local redirect path to the asset" do
+ let(:options) { { only_path: true } }
+
+ it "creates the redirect route to the blob" do
+ expect(subject).to match(%r{^/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
+ end
+
+ context "with extra URL options" do
+ let(:options) { { only_path: true, utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
+
+ it "handles the extra URL options correctly" do
+ expect(subject).to match(%r{^/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ end
end
end
end
context "with a variant" do
let(:asset) { organization.official_img_footer.variant(resize_to_fit: [160, 160]) }
+ let(:track_variants) { true }
+
+ before do
+ # This is typically set through `config.active_storage.track_variants`
+ # and for test environment it seems to be enabled by default at the
+ # time of writing these specs. This config is overridden for these
+ # specs because the default configurations may be changed through
+ # other changes or gem updates.
+ allow(ActiveStorage).to receive(:track_variants).and_return(track_variants)
+ end
it "creates the route to the variant" do
- expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/representations/redirect/.*/avatar.jpg$})
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.jpg$})
+ end
+
+ context "when the asset has been processed" do
+ before { asset.processed }
+
+ it "creates the route to the variant through the storage service" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
+ end
+
+ # Note that this situation should not normally happen but it is
+ # possible e.g. if the backend has created the variant record in the
+ # database but has not yet uploaded the asset to the storage service.
+ context "and does not exist at the storage service" do
+ before do
+ path = asset.blob.service.path_for(asset.key)
+ File.delete(path)
+ end
+
+ it "creates the redirect route to the variant" do
+ expect(asset.processed?).to be(true)
+ expect(asset.key).to be_present
+ expect(asset.blob.service.exist?(asset.key)).to be(false)
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.jpg$})
+ end
+ end
end
+
+ context "when track_variants is disabled" do
+ let(:track_variants) { false }
+
+ it "creates the route to the variant" do
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.jpg$})
+ end
+
+ context "and the asset has been processed" do
+ before { asset.processed }
+
+ it "creates the route to the variant through the storage service" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
+ end
+
+ context "and when passing incompatible URL options" do
+ # The `:host` option is passed e.g. in many mailers.
+ # `ActiveStorage::Variant#url` method does not allow this argument
+ # which is why this test is testing that it does not lead to an
+ # error.
+ let(:options) { { host: "example.lvh.me" } }
+
+ it "creates the route to the variant" do
+ expect(subject).to match(%r{^http://example\.lvh\.me:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
+ end
+ end
+ end
+ end
+
+ context "when the variant has a different file extension" do
+ let(:asset) { organization.official_img_footer.variant(resize_to_fit: [160, 160], format: "png") }
+
+ it "creates the route to the variant with converted file extension" do
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.png$})
+ end
+
+ context "when the asset has been processed" do
+ before { asset.processed }
+
+ it "creates the route to the variant through the storage service" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.png$})
+ end
+ end
+
+ context "when track_variants is disabled" do
+ let(:track_variants) { false }
+
+ it "creates the route to the variant with converted file extension" do
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.png$})
+ end
+
+ context "and the asset has been processed" do
+ before { asset.processed }
+
+ it "creates the route to the variant through the storage service" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.png$})
+ end
+ end
+ end
+ end
+ end
+
+ # This is used by the generator specs to check that some default
+ # configurations are set correctly.
+ context "with nil" do
+ let(:asset) { nil }
+
+ it { is_expected.to be_nil }
end
context "when the CDN host is defined" do
@@ -61,14 +194,14 @@ module Decidim::AssetRouter
end
it "creates the route to the CDN blob" do
- expect(subject).to match(%r{^https://cdn.example.org/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject).to match(%r{^https://cdn\.example\.org/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
end
context "with extra URL options" do
let(:options) { { utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
it "handles the extra URL options correctly" do
- expect(subject).to match(%r{^https://cdn.example.org/rails/active_storage/blobs/redirect/.*/avatar.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ expect(subject).to match(%r{^https://cdn\.example\.org/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
end
end
end
diff --git a/decidim-core/spec/lib/upgrade/wysiwyg_migrator_spec.rb b/decidim-core/spec/lib/upgrade/wysiwyg_migrator_spec.rb
index b6f240dbdf4a..30168bbc1fe4 100644
--- a/decidim-core/spec/lib/upgrade/wysiwyg_migrator_spec.rb
+++ b/decidim-core/spec/lib/upgrade/wysiwyg_migrator_spec.rb
@@ -9,6 +9,7 @@ module Decidim
let(:organization) { create(:organization) }
let(:component) { create(:component, organization:) }
let(:image) { create(:attachment, attached_to: organization) }
+ let(:image_path) { image.attached_uploader(:file).path }
let(:content) { original_content }
let(:original_content) do
@@ -46,10 +47,10 @@ module Decidim
Paragraph content with an inline image.
-
+
And some text after that.
- ![This image had an alternative text](#{image.url})
+ ![This image had an alternative text](#{image_path})
Here we had some unrecognized node.
Blockquote element content
should be wrapped inside a paragraph.
@@ -151,11 +152,11 @@ module Decidim
Paragraph content with an inline image.
-
+
And some text after that.
-
+
diff --git a/decidim-core/spec/models/decidim/content_block_spec.rb b/decidim-core/spec/models/decidim/content_block_spec.rb
index 21bb18966694..8e3ee29f677a 100644
--- a/decidim-core/spec/models/decidim/content_block_spec.rb
+++ b/decidim-core/spec/models/decidim/content_block_spec.rb
@@ -25,7 +25,7 @@ module Decidim
context "when the image has not been uploaded" do
it "returns nil" do
- expect(subject.images_container.attached_uploader(:background_image).path).to be_nil
+ expect(subject.images_container.attached_uploader(:background_image).url).to be_nil
end
end
@@ -46,7 +46,7 @@ module Decidim
it "returns the image" do
expect(subject.images_container.background_image.attached?).to be true
- expect(subject.images_container.attached_uploader(:background_image).path).not_to be_nil
+ expect(subject.images_container.attached_uploader(:background_image).url).not_to be_nil
end
end
diff --git a/decidim-core/spec/types/user_group_type_spec.rb b/decidim-core/spec/types/user_group_type_spec.rb
index a54a4f61972d..e753c09b5228 100644
--- a/decidim-core/spec/types/user_group_type_spec.rb
+++ b/decidim-core/spec/types/user_group_type_spec.rb
@@ -38,7 +38,7 @@ module Core
let(:query) { "{ avatarUrl }" }
it "returns the user avatar url" do
- expect(response).to include("avatarUrl" => model.attached_uploader(:avatar).path)
+ expect(response["avatarUrl"]).to be_blob_url(model.avatar.blob)
end
end
diff --git a/decidim-core/spec/types/user_type_spec.rb b/decidim-core/spec/types/user_type_spec.rb
index 79613a22a8c2..bf734001ce4b 100644
--- a/decidim-core/spec/types/user_type_spec.rb
+++ b/decidim-core/spec/types/user_type_spec.rb
@@ -50,7 +50,7 @@ module Core
let(:query) { "{ avatarUrl }" }
it "returns the user avatar url (small version)" do
- expect(response).to include("avatarUrl" => model.attached_uploader(:avatar).path(variant: :thumb))
+ expect(response).to include("avatarUrl" => model.attached_uploader(:avatar).variant_url(:thumb))
end
end
diff --git a/decidim-core/spec/uploaders/application_uploader_spec.rb b/decidim-core/spec/uploaders/application_uploader_spec.rb
index 7a9a9c8b2830..8e8e8596425a 100644
--- a/decidim-core/spec/uploaders/application_uploader_spec.rb
+++ b/decidim-core/spec/uploaders/application_uploader_spec.rb
@@ -7,6 +7,7 @@ module Decidim
subject { described_class.new(model, mounted_as) }
let(:model) { create(:organization) }
+ let(:hostname) { model.host }
let(:mounted_as) { :official_img_footer }
before do
@@ -48,21 +49,21 @@ module Decidim
let(:default_port) { Rails.env.development? ? 3000 : Capybara.server_port }
it "returns a URL containing the port only" do
- expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^http://#{Regexp.escape(hostname)}:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
context "when force_ssl is enabled" do
include_context "with force_ssl enabled"
it "returns a URL containing the port and protocol" do
- expect(subject.variant_url(:testing)).to match(%r{^https://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^https://#{Regexp.escape(hostname)}:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
context "and the PORT environment variable is defined as 3001" do
let(:local_port) { 3001 }
it "returns a URL containing the port and protocol" do
- expect(subject.variant_url(:testing)).to match(%r{^https://localhost:3001/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^https://#{Regexp.escape(hostname)}:3001/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
@@ -70,7 +71,7 @@ module Decidim
let(:local_port) { 443 }
it "returns a URL containing the protocol only" do
- expect(subject.variant_url(:testing)).to match(%r{^https://localhost/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^https://#{Regexp.escape(hostname)}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
end
@@ -87,7 +88,7 @@ module Decidim
end
it "returns a URL to the variant" do
- expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/representations/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.jpg$})
end
context "when the provided file is invariable" do
@@ -96,7 +97,7 @@ module Decidim
end
it "returns the original URL" do
- expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^http://#{Regexp.escape(hostname)}:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
end
@@ -113,7 +114,15 @@ module Decidim
end
it "returns a URL to the variant with the correct extension" do
- expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/representations/redirect/.*/avatar.png$})
+ expect(subject.variant_url(:testing)).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.png$})
+ end
+
+ context "and the variant has been processed" do
+ before { subject.variant(:testing).process }
+
+ it "returns a URL to the variant with the correct extension" do
+ expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.png$})
+ end
end
end
end
@@ -132,6 +141,7 @@ module Decidim
context "with production environment" do
let(:hostname) { "production.decidim.test" }
+ let(:options) { { host: hostname } }
before do
allow(Rails.env).to receive(:development?).and_return(false)
@@ -139,14 +149,14 @@ module Decidim
end
it "returns a default URL" do
- expect(subject.variant_url(:testing)).to match(%r{^http://production.decidim.test/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^http://#{Regexp.escape(hostname)}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
context "and the PORT environment variable is defined as 443" do
let(:local_port) { 443 }
it "returns a URL containing the protocol only" do
- expect(subject.variant_url(:testing)).to match(%r{^https://production.decidim.test/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^https://#{Regexp.escape(hostname)}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
@@ -154,7 +164,7 @@ module Decidim
let(:local_port) { 8080 }
it "returns a URL containing the port" do
- expect(subject.variant_url(:testing)).to match(%r{^http://production.decidim.test:8080/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^http://#{Regexp.escape(hostname)}:8080/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
@@ -162,14 +172,14 @@ module Decidim
include_context "with force_ssl enabled"
it "returns a URL containing the protocol only" do
- expect(subject.variant_url(:testing)).to match(%r{^https://production.decidim.test/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^https://#{Regexp.escape(hostname)}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
context "and the PORT environment variable is defined as 8080" do
let(:local_port) { 8080 }
it "returns a URL containing the port and protocol" do
- expect(subject.variant_url(:testing)).to match(%r{^https://production.decidim.test:8080/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^https://#{Regexp.escape(hostname)}:8080/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
end
@@ -190,7 +200,7 @@ module Decidim
end
it "returns a URL containing the CDN configurations" do
- expect(subject.variant_url(:testing)).to match(%r{^https://cdn.example.org/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^#{Regexp.escape(cdn_host)}/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
end
end
end
diff --git a/decidim-dev/lib/decidim/dev/test/rspec_support/activestorage_matchers.rb b/decidim-dev/lib/decidim/dev/test/rspec_support/activestorage_matchers.rb
new file mode 100644
index 000000000000..170f43dea3f7
--- /dev/null
+++ b/decidim-dev/lib/decidim/dev/test/rspec_support/activestorage_matchers.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+module ActiveStorageMatchers
+ def be_blob_url(expected)
+ BeBlobUrl.new(expected)
+ end
+
+ def include_blob_urls(*expected)
+ IncludeBlobUrls.new(expected)
+ end
+
+ class BlobMatch
+ BLOB_URL_MATCHERS = {
+ redirect: %r{/rails/active_storage/blobs/redirect/([^/]+)/([^/]+)$},
+ representation: %r{/rails/active_storage/representations/redirect/([^/]+)/([^/]+)/([^/]+)$},
+ disk: %r{/rails/active_storage/disk/([^/]+)/([^/]+)$}
+ }.freeze
+
+ def initialize(url)
+ @url = url
+ end
+
+ def blob
+ return unless key_match
+
+ @blob ||=
+ case url_type
+ when :redirect, :representation
+ ActiveStorage::Blob.find_signed(key_match)
+ when :disk
+ decoded = ActiveStorage.verifier.verified(key_match, purpose: :blob_key)
+ ActiveStorage::Blob.find_by(key: decoded[:key]) if decoded
+ end
+ end
+
+ def variation
+ return unless variation_match
+
+ blob.representation(variation_match)
+ end
+
+ def key_match
+ return unless match
+
+ match[1]
+ end
+
+ def variation_match
+ return unless match
+
+ match[2] if url_type == :representation
+ end
+
+ def filename_match
+ return unless match
+
+ case url_type
+ when :representation
+ match[3]
+ else
+ match[2]
+ end
+ end
+
+ private
+
+ attr_reader :url, :url_type
+
+ def match
+ return @match if @url_type
+
+ @url_type = :none
+ @match = nil
+ BLOB_URL_MATCHERS.each do |type, matcher|
+ @match = url.match(matcher)
+ if @match
+ @url_type = type
+ break
+ end
+ end
+
+ @match
+ end
+ end
+
+ class BeBlobUrl
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def description
+ "be a blob URL"
+ end
+
+ def matches?(actual)
+ @actual = actual
+ match = BlobMatch.new(actual)
+ match.blob == expected
+ end
+
+ def failure_message
+ "expected #{actual} to match blob with ID #{expected.id}"
+ end
+
+ def failure_message_when_negated
+ "expected #{actual} not to match blob with ID #{expected.id}"
+ end
+
+ private
+
+ attr_reader :expected, :actual
+ end
+
+ class IncludeBlobUrls
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def description
+ "include blob URLs"
+ end
+
+ def matches?(actual)
+ @actual = actual
+
+ actual.all? do |url|
+ match = BlobMatch.new(url)
+ expected.include?(match.blob)
+ end
+ end
+
+ def failure_message
+ "expected #{actual.inspect} to match blobs with ID #{expected.map(&:id).join(", ")}"
+ end
+
+ def failure_message_when_negated
+ "expected #{actual.inspect} not to match blobs with ID #{expected.map(&:id).join(", ")}"
+ end
+
+ private
+
+ attr_reader :expected, :actual
+ end
+end
+
+RSpec.configure do |config|
+ config.include ActiveStorageMatchers
+end
diff --git a/decidim-dev/lib/decidim/dev/test/spec_helper.rb b/decidim-dev/lib/decidim/dev/test/spec_helper.rb
index d22822c11cc6..3a371add381a 100644
--- a/decidim-dev/lib/decidim/dev/test/spec_helper.rb
+++ b/decidim-dev/lib/decidim/dev/test/spec_helper.rb
@@ -45,4 +45,11 @@
"connect-src": %W(#{Decidim::Dev::Test::MapServer.host})
}
end
+
+ config.before do
+ # Ensure that the current host is not set for any spec in order to test that
+ # the automatic current host definition is working correctly in all
+ # situations.
+ ActiveStorage::Current.url_options = {}
+ end
end
diff --git a/decidim-forms/spec/presenters/decidim/admin/questionnaire_answer_presenter_spec.rb b/decidim-forms/spec/presenters/decidim/admin/questionnaire_answer_presenter_spec.rb
index d17a91a260be..bc3d057f5292 100644
--- a/decidim-forms/spec/presenters/decidim/admin/questionnaire_answer_presenter_spec.rb
+++ b/decidim-forms/spec/presenters/decidim/admin/questionnaire_answer_presenter_spec.rb
@@ -79,14 +79,28 @@ module Decidim
let!(:attachment) { create(:attachment, :with_image, attached_to: answer) }
it "returns the download attachment link" do
- expect(subject.body).to eq(%())
+ regexp = %r{^- ((((?!).)*))
}
+ expect(subject.body).to match(regexp)
+
+ match = subject.body.match(regexp)
+ href = match[2]
+ inner = match[3]
+
+ expect(inner).to eq(%(#{decidim_escape_translated(attachment.title)} jpeg 105 KB))
+ expect(href).to be_blob_url(attachment.file.blob)
end
context "when the attachment does not have a title" do
let!(:attachment) { create(:attachment, :with_image, attached_to: answer, title: {}, description: {}) }
it "returns the download attachment link" do
- expect(subject.body).to eq(%())
+ regexp = %r{^$}
+ expect(subject.body).to match(regexp)
+
+ match = subject.body.match(regexp)
+ href = match[1]
+
+ expect(href).to be_blob_url(attachment.file.blob)
end
end
end
diff --git a/decidim-forms/spec/serializers/decidim/forms/user_answers_serializer_spec.rb b/decidim-forms/spec/serializers/decidim/forms/user_answers_serializer_spec.rb
index 1bd0cdeeef34..cc3b00849e21 100644
--- a/decidim-forms/spec/serializers/decidim/forms/user_answers_serializer_spec.rb
+++ b/decidim-forms/spec/serializers/decidim/forms/user_answers_serializer_spec.rb
@@ -85,7 +85,7 @@ module Forms
[key, choices.map { |choice| choice&.body }]
end
- serialized_files_answer = files_answer.attachments.map(&:url)
+ serialized_files_blobs = files_answer.attachments.map(&:file).map(&:blob)
expect(serialized).to include(
"#{multichoice_question.position + 1}. #{translated(multichoice_question.body, locale: I18n.locale)}" => [multichoice_answer_choices.first.body, multichoice_answer_choices.last.body]
@@ -99,8 +99,8 @@ module Forms
"#{matrixmultiple_question.position + 1}. #{translated(matrixmultiple_question.body, locale: I18n.locale)}" => serialized_matrix_answer
)
- expect(serialized).to include(
- "#{files_question.position + 1}. #{translated(files_question.body, locale: I18n.locale)}" => serialized_files_answer
+ expect(serialized["#{files_question.position + 1}. #{translated(files_question.body, locale: I18n.locale)}"]).to include_blob_urls(
+ *serialized_files_blobs
)
end
diff --git a/decidim-forms/spec/services/decidim/forms/download_your_data_user_answer_serializer_spec.rb b/decidim-forms/spec/services/decidim/forms/download_your_data_user_answer_serializer_spec.rb
index 3ed4412bf070..0635a2a381be 100644
--- a/decidim-forms/spec/services/decidim/forms/download_your_data_user_answer_serializer_spec.rb
+++ b/decidim-forms/spec/services/decidim/forms/download_your_data_user_answer_serializer_spec.rb
@@ -62,7 +62,7 @@ module Forms
end
it "includes the answer" do
- expect(serialized).to include(answer: answer.attachments.map(&:url))
+ expect(serialized[:answer]).to include_blob_urls(*answer.attachments.map(&:file).map(&:blob))
end
end
diff --git a/decidim-initiatives/app/cells/decidim/initiatives/initiative_g_cell.rb b/decidim-initiatives/app/cells/decidim/initiatives/initiative_g_cell.rb
index 49000ab12be7..21ee8fbfc88f 100644
--- a/decidim-initiatives/app/cells/decidim/initiatives/initiative_g_cell.rb
+++ b/decidim-initiatives/app/cells/decidim/initiatives/initiative_g_cell.rb
@@ -11,16 +11,14 @@ def resource_path
Decidim::Initiatives::Engine.routes.url_helpers.initiative_path(model)
end
- def has_image?
- image.present?
- end
-
def image
@image ||= model.attachments.find(&:image?)
end
- def resource_image_path
- image.url if has_image?
+ def resource_image_url
+ return if image.blank?
+
+ image.url
end
def metadata_cell
diff --git a/decidim-initiatives/app/views/decidim/initiatives/committee_requests/new.html.erb b/decidim-initiatives/app/views/decidim/initiatives/committee_requests/new.html.erb
index d46ef8ada56d..c57c329da669 100644
--- a/decidim-initiatives/app/views/decidim/initiatives/committee_requests/new.html.erb
+++ b/decidim-initiatives/app/views/decidim/initiatives/committee_requests/new.html.erb
@@ -1,12 +1,12 @@
<% add_decidim_meta_tags({
- image_url: current_initiative.type.attached_uploader(:banner_image).path,
+ image_url: current_initiative.type.attached_uploader(:banner_image).url,
description: translated_attribute(current_initiative.description),
title: translated_attribute(current_initiative.title),
url: initiative_url(current_initiative.id)
}) %>
<% add_decidim_page_title(translated_attribute(current_initiative.title)) %>
-<% provide :meta_image_url, current_initiative.type.attached_uploader(:banner_image).path %>
+<% provide :meta_image_url, current_initiative.type.attached_uploader(:banner_image).url %>
<%= render layout: "layouts/decidim/shared/layout_center" do %>
diff --git a/decidim-initiatives/app/views/decidim/initiatives/initiatives/print.html.erb b/decidim-initiatives/app/views/decidim/initiatives/initiatives/print.html.erb
index 324813412987..0e17fa89030b 100644
--- a/decidim-initiatives/app/views/decidim/initiatives/initiatives/print.html.erb
+++ b/decidim-initiatives/app/views/decidim/initiatives/initiatives/print.html.erb
@@ -2,7 +2,7 @@
<% if current_organization.logo.present? %>
- <%= image_tag current_organization.attached_uploader(:logo).path(variant: :medium), title: current_organization_name %>
+ <%= image_tag current_organization.attached_uploader(:logo).variant_url(:medium), title: current_organization_name %>
<% end %>
<%= current_organization_name %>
diff --git a/decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb b/decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb
index e1f5d5ee1119..46b35d2c57a4 100644
--- a/decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb
+++ b/decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb
@@ -1,5 +1,5 @@
<% add_decidim_meta_tags({
- image_url: current_initiative.type.attached_uploader(:banner_image).path,
+ image_url: current_initiative.type.attached_uploader(:banner_image).url,
description: translated_attribute(current_initiative.description),
title: translated_attribute(current_initiative.title),
url: initiative_url(current_initiative.id)
diff --git a/decidim-initiatives/app/views/layouts/decidim/_initiative_header.html.erb b/decidim-initiatives/app/views/layouts/decidim/_initiative_header.html.erb
index f098bce3296d..c91068db1bbb 100644
--- a/decidim-initiatives/app/views/layouts/decidim/_initiative_header.html.erb
+++ b/decidim-initiatives/app/views/layouts/decidim/_initiative_header.html.erb
@@ -1,7 +1,7 @@
+ style="background-image:url('<%= current_participatory_space.type.attached_uploader(:banner_image).url %>');">
diff --git a/decidim-initiatives/app/views/layouts/decidim/initiative.html.erb b/decidim-initiatives/app/views/layouts/decidim/initiative.html.erb
index 16c36ff60024..a49d60de5d8a 100644
--- a/decidim-initiatives/app/views/layouts/decidim/initiative.html.erb
+++ b/decidim-initiatives/app/views/layouts/decidim/initiative.html.erb
@@ -1,7 +1,7 @@
<%= append_javascript_pack_tag "decidim_initiatives" %>
<%= append_stylesheet_pack_tag "decidim_initiatives", media: "all" %>
-<% provide :meta_image_url, current_participatory_space.type.attached_uploader(:banner_image).path %>
+<% provide :meta_image_url, current_participatory_space.type.attached_uploader(:banner_image).url %>
<%= render "layouts/decidim/application" do %>
<%= render layout: "layouts/decidim/shared/layout_center" do %>
diff --git a/decidim-initiatives/app/views/layouts/decidim/initiative_head.html.erb b/decidim-initiatives/app/views/layouts/decidim/initiative_head.html.erb
index 0a59ea4ea6e3..2a644271e767 100644
--- a/decidim-initiatives/app/views/layouts/decidim/initiative_head.html.erb
+++ b/decidim-initiatives/app/views/layouts/decidim/initiative_head.html.erb
@@ -1,7 +1,7 @@
<%= append_javascript_pack_tag "decidim_initiatives" %>
<%= append_stylesheet_pack_tag "decidim_initiatives", media: "all" %>
-<% provide :meta_image_url, current_initiative.type.attached_uploader(:banner_image).path %>
+<% provide :meta_image_url, current_initiative.type.attached_uploader(:banner_image).url %>
<%= render partial: "layouts/decidim/header/follow_space_menu_bar_button", locals: { participatory_space: current_participatory_space } %>
diff --git a/decidim-initiatives/lib/decidim/api/initiative_api_type.rb b/decidim-initiatives/lib/decidim/api/initiative_api_type.rb
index 05cacdbecbd5..63cb06cd8274 100644
--- a/decidim-initiatives/lib/decidim/api/initiative_api_type.rb
+++ b/decidim-initiatives/lib/decidim/api/initiative_api_type.rb
@@ -23,7 +23,7 @@ class InitiativeApiType < Decidim::Api::Types::BaseObject
field :initiatives, [Decidim::Initiatives::InitiativeType, { null: true }], "The initiatives that have this type", null: false
def banner_image
- object.attached_uploader(:banner_image).path
+ object.attached_uploader(:banner_image).url
end
end
end
diff --git a/decidim-initiatives/spec/types/initiative_api_type_spec.rb b/decidim-initiatives/spec/types/initiative_api_type_spec.rb
index e60e8b67d6e9..b735f1bcc43a 100644
--- a/decidim-initiatives/spec/types/initiative_api_type_spec.rb
+++ b/decidim-initiatives/spec/types/initiative_api_type_spec.rb
@@ -54,7 +54,7 @@ module Initiatives
let(:query) { "{ bannerImage }" }
it "returns the banner image field" do
- expect(response["bannerImage"]).to eq(model.attached_uploader(:banner_image).path)
+ expect(response["bannerImage"]).to be_blob_url(model.banner_image.blob)
end
end
diff --git a/decidim-initiatives/spec/types/integration_schema_spec.rb b/decidim-initiatives/spec/types/integration_schema_spec.rb
index 8a19a8c24edd..7e1f227087ab 100644
--- a/decidim-initiatives/spec/types/integration_schema_spec.rb
+++ b/decidim-initiatives/spec/types/integration_schema_spec.rb
@@ -29,22 +29,6 @@
"description" => { "translation" => initiative.description[locale] },
"hashtag" => initiative.hashtag,
"id" => initiative.id.to_s,
- "initiativeType" => {
- "bannerImage" => initiative.type.attached_uploader(:banner_image).path,
- "collectUserExtraFields" => initiative.type.collect_user_extra_fields?,
- "createdAt" => initiative.type.created_at.iso8601.to_s.gsub("Z", "+00:00"),
- "description" => { "translation" => initiative.type.description[locale] },
- "extraFieldsLegalInformation" => initiative.type.extra_fields_legal_information,
- "id" => initiative.type.id.to_s,
- "initiatives" => initiative.type.initiatives.map { |i| { "id" => i.id.to_s } },
- "minimumCommitteeMembers" => initiative.type.minimum_committee_members,
- "promotingCommitteeEnabled" => initiative.type.promoting_committee_enabled,
- "signatureType" => initiative.type.signature_type,
- "title" => { "translation" => initiative.type.title[locale] },
- "undoOnlineSignaturesEnabled" => initiative.type.undo_online_signatures_enabled,
- "updatedAt" => initiative.type.updated_at.iso8601.to_s.gsub("Z", "+00:00"),
- "validateSmsCodeOnVotes" => initiative.type.validate_sms_code_on_votes
- },
"offlineVotes" => initiative.offline_votes_count,
"onlineVotes" => initiative.online_votes_count,
"publishedAt" => initiative.published_at.iso8601.to_s.gsub("Z", "+00:00"),
@@ -61,6 +45,23 @@
}
end
+ let(:initiative_type_data) do
+ {
+ "collectUserExtraFields" => initiative.type.collect_user_extra_fields?,
+ "createdAt" => initiative.type.created_at.iso8601.to_s.gsub("Z", "+00:00"),
+ "description" => { "translation" => initiative.type.description[locale] },
+ "extraFieldsLegalInformation" => initiative.type.extra_fields_legal_information,
+ "id" => initiative.type.id.to_s,
+ "initiatives" => initiative.type.initiatives.map { |i| { "id" => i.id.to_s } },
+ "minimumCommitteeMembers" => initiative.type.minimum_committee_members,
+ "promotingCommitteeEnabled" => initiative.type.promoting_committee_enabled,
+ "signatureType" => initiative.type.signature_type,
+ "title" => { "translation" => initiative.type.title[locale] },
+ "undoOnlineSignaturesEnabled" => initiative.type.undo_online_signatures_enabled,
+ "updatedAt" => initiative.type.updated_at.iso8601.to_s.gsub("Z", "+00:00"),
+ "validateSmsCodeOnVotes" => initiative.type.validate_sms_code_on_votes
+ }
+ end
let(:initiatives) do
%(
@@ -143,7 +144,10 @@
end
it "returns the correct response" do
- expect(response["initiatives"].first).to eq(initiative_data)
+ data = response["initiatives"].first
+ expect(data).to include(initiative_data)
+ expect(data["initiativeType"]).to include(initiative_type_data)
+ expect(data["initiativeType"]["bannerImage"]).to be_blob_url(initiative.type.banner_image.blob)
end
it_behaves_like "implements stats type" do
@@ -161,7 +165,7 @@
end
end
- describe "single assembly" do
+ describe "single initiative" do
let(:initiatives) do
%(
initiative(id: #{initiative.id}){
@@ -234,7 +238,10 @@
end
it "returns the correct response" do
- expect(response["initiative"]).to eq(initiative_data)
+ data = response["initiative"]
+ expect(data).to include(initiative_data)
+ expect(data["initiativeType"]).to include(initiative_type_data)
+ expect(data["initiativeType"]["bannerImage"]).to be_blob_url(initiative.type.banner_image.blob)
end
it_behaves_like "implements stats type" do
diff --git a/decidim-meetings/app/cells/decidim/meetings/meeting_l_cell.rb b/decidim-meetings/app/cells/decidim/meetings/meeting_l_cell.rb
index fea8f0a2a9b0..ac48249eb1e8 100644
--- a/decidim-meetings/app/cells/decidim/meetings/meeting_l_cell.rb
+++ b/decidim-meetings/app/cells/decidim/meetings/meeting_l_cell.rb
@@ -9,10 +9,6 @@ module Meetings
class MeetingLCell < Decidim::CardLCell
alias meeting model
- def has_image?
- true
- end
-
def extra_class
"card__calendar-list__reset"
end
diff --git a/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_g_cell.rb b/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_g_cell.rb
index 1431a9464375..49481561005f 100644
--- a/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_g_cell.rb
+++ b/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_g_cell.rb
@@ -11,8 +11,8 @@ def resource_path
Decidim::ParticipatoryProcesses::Engine.routes.url_helpers.participatory_process_path(model)
end
- def resource_image_path
- model.attached_uploader(:hero_image).path
+ def resource_image_url
+ model.attached_uploader(:hero_image).url
end
def start_date
diff --git a/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_group_g_cell.rb b/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_group_g_cell.rb
index 9c1101a8d901..e75ab09bbef2 100644
--- a/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_group_g_cell.rb
+++ b/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_group_g_cell.rb
@@ -11,8 +11,8 @@ def resource_path
Decidim::ParticipatoryProcesses::Engine.routes.url_helpers.participatory_process_group_path(model)
end
- def resource_image_path
- model.attached_uploader(:hero_image).path
+ def resource_image_url
+ model.attached_uploader(:hero_image).url
end
def metadata_cell
diff --git a/decidim-participatory_processes/app/helpers/decidim/participatory_processes/participatory_process_helper.rb b/decidim-participatory_processes/app/helpers/decidim/participatory_processes/participatory_process_helper.rb
index 32b39a236c29..9f28625cb597 100644
--- a/decidim-participatory_processes/app/helpers/decidim/participatory_processes/participatory_process_helper.rb
+++ b/decidim-participatory_processes/app/helpers/decidim/participatory_processes/participatory_process_helper.rb
@@ -53,7 +53,7 @@ def participatory_process_group_cta_settings(process_group)
OpenStruct.new(
text: translated_attribute(cta_settings.button_text),
path: cta_settings.button_url,
- image_url: block.images_container.attached_uploader(:background_image).path(variant: :big)
+ image_url: block.images_container.attached_uploader(:background_image).variant_url(:big)
)
end
diff --git a/decidim-participatory_processes/app/views/decidim/participatory_processes/participatory_processes/show.html.erb b/decidim-participatory_processes/app/views/decidim/participatory_processes/participatory_processes/show.html.erb
index 44ccefcc2185..7d33d425a6db 100644
--- a/decidim-participatory_processes/app/views/decidim/participatory_processes/participatory_processes/show.html.erb
+++ b/decidim-participatory_processes/app/views/decidim/participatory_processes/participatory_processes/show.html.erb
@@ -1,6 +1,6 @@
<% add_decidim_meta_tags({
title: translated_attribute(current_participatory_space.title),
- image_url: current_participatory_space.attached_uploader(:hero_image).path,
+ image_url: current_participatory_space.attached_uploader(:hero_image).url,
description: translated_attribute(current_participatory_space.short_description),
url: participatory_process_url(current_participatory_space)
}) %>
diff --git a/decidim-participatory_processes/app/views/layouts/decidim/_process_header.html.erb b/decidim-participatory_processes/app/views/layouts/decidim/_process_header.html.erb
index 31ac78eb82f1..3eeb377e44fa 100644
--- a/decidim-participatory_processes/app/views/layouts/decidim/_process_header.html.erb
+++ b/decidim-participatory_processes/app/views/layouts/decidim/_process_header.html.erb
@@ -1,4 +1,4 @@
-
+
diff --git a/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb b/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
index 3c7fdc2f3a84..5bb35ea9f490 100644
--- a/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
+++ b/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
@@ -14,7 +14,7 @@ class ParticipatoryProcessGroupType < Decidim::Api::Types::BaseObject
field :hero_image, GraphQL::Types::String, "The hero image for this participatory process group", null: true
def hero_image
- object.attached_uploader(:hero_image).path
+ object.attached_uploader(:hero_image).url
end
end
end
diff --git a/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb b/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
index db6f068652bb..00f31d79559a 100644
--- a/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
+++ b/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
@@ -47,11 +47,11 @@ class ParticipatoryProcessType < Decidim::Api::Types::BaseObject
description: "The participatory process group in which this process belong to"
def banner_image
- object.attached_uploader(:banner_image).path
+ object.attached_uploader(:banner_image).url
end
def hero_image
- object.attached_uploader(:hero_image).path
+ object.attached_uploader(:hero_image).url
end
end
end
diff --git a/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb b/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
index b0baced444f4..fda26ea52aa5 100644
--- a/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
+++ b/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
@@ -22,7 +22,8 @@ module Decidim::ParticipatoryProcesses
end
it "displays the process hero image" do
- expect(subject).to have_css("img[src='#{model.attached_uploader(:hero_image).path}']")
+ img = subject.find("img")
+ expect(img["src"]).to be_blob_url(model.hero_image.blob)
end
it_behaves_like "process card with metadata", metadata_class: "card__grid-metadata"
diff --git a/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb b/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
index aad80b079dbd..3582ecd1557f 100644
--- a/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
+++ b/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
@@ -22,7 +22,7 @@ module Decidim
context "when image is attached" do
it "returns an URL including the organization domain" do
expect(subject.hero_image_url).to include(process.organization.host)
- expect(subject.hero_image_url).to include(process.attached_uploader(:hero_image).path)
+ expect(subject.hero_image_url).to be_blob_url(process.hero_image.blob)
end
end
end
@@ -41,7 +41,7 @@ module Decidim
context "when image is attached" do
it "returns an URL including the organization domain" do
expect(subject.banner_image_url).to include(process.organization.host)
- expect(subject.banner_image_url).to include(process.attached_uploader(:banner_image).path)
+ expect(subject.banner_image_url).to be_blob_url(process.banner_image.blob)
end
end
end
diff --git a/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb b/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
index f327aa53b896..ab7545c5c279 100644
--- a/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
+++ b/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
@@ -14,29 +14,29 @@ module Decidim::ParticipatoryProcesses
expect(serialized).to be_a(Hash)
- expect(subject.serialize).to include(id: resource.id)
- expect(subject.serialize).to include(title: resource.title)
- expect(subject.serialize).to include(subtitle: resource.subtitle)
- expect(subject.serialize).to include(slug: resource.slug)
- expect(subject.serialize).to include(hashtag: resource.hashtag)
- expect(subject.serialize).to include(short_description: resource.short_description)
- expect(subject.serialize).to include(description: resource.description)
- expect(subject.serialize).to include(announcement: resource.announcement)
- expect(subject.serialize).to include(start_date: resource.start_date)
- expect(subject.serialize).to include(end_date: resource.end_date)
- expect(subject.serialize).to include(remote_hero_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessPresenter.new(resource).hero_image_url)
- expect(subject.serialize).to include(remote_banner_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessPresenter.new(resource).banner_image_url)
- expect(subject.serialize).to include(developer_group: resource.developer_group)
- expect(subject.serialize).to include(local_area: resource.local_area)
- expect(subject.serialize).to include(meta_scope: resource.meta_scope)
- expect(subject.serialize).to include(participatory_scope: resource.participatory_scope)
- expect(subject.serialize).to include(participatory_structure: resource.participatory_structure)
- expect(subject.serialize).to include(target: resource.target)
- expect(subject.serialize).to include(private_space: resource.private_space)
- expect(subject.serialize).to include(promoted: resource.promoted)
- expect(subject.serialize).to include(scopes_enabled: resource.scopes_enabled)
- expect(subject.serialize).to include(show_metrics: resource.show_metrics)
- expect(subject.serialize).to include(show_statistics: resource.show_statistics)
+ expect(serialized).to include(id: resource.id)
+ expect(serialized).to include(title: resource.title)
+ expect(serialized).to include(subtitle: resource.subtitle)
+ expect(serialized).to include(slug: resource.slug)
+ expect(serialized).to include(hashtag: resource.hashtag)
+ expect(serialized).to include(short_description: resource.short_description)
+ expect(serialized).to include(description: resource.description)
+ expect(serialized).to include(announcement: resource.announcement)
+ expect(serialized).to include(start_date: resource.start_date)
+ expect(serialized).to include(end_date: resource.end_date)
+ expect(serialized[:remote_hero_image_url]).to be_blob_url(resource.hero_image.blob)
+ expect(serialized[:remote_banner_image_url]).to be_blob_url(resource.banner_image.blob)
+ expect(serialized).to include(developer_group: resource.developer_group)
+ expect(serialized).to include(local_area: resource.local_area)
+ expect(serialized).to include(meta_scope: resource.meta_scope)
+ expect(serialized).to include(participatory_scope: resource.participatory_scope)
+ expect(serialized).to include(participatory_structure: resource.participatory_structure)
+ expect(serialized).to include(target: resource.target)
+ expect(serialized).to include(private_space: resource.private_space)
+ expect(serialized).to include(promoted: resource.promoted)
+ expect(serialized).to include(scopes_enabled: resource.scopes_enabled)
+ expect(serialized).to include(show_metrics: resource.show_metrics)
+ expect(serialized).to include(show_statistics: resource.show_statistics)
end
context "when process has area" do
@@ -109,7 +109,7 @@ module Decidim::ParticipatoryProcesses
expect(serialized_participatory_process_group).to include(id: resource.participatory_process_group.id)
expect(serialized_participatory_process_group).to include(title: resource.participatory_process_group.title)
expect(serialized_participatory_process_group).to include(description: resource.participatory_process_group.description)
- expect(serialized_participatory_process_group).to include(remote_hero_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessGroupPresenter.new(resource.participatory_process_group).hero_image_url)
+ expect(serialized_participatory_process_group[:remote_hero_image_url]).to be_blob_url(resource.participatory_process_group.hero_image.blob)
end
end
@@ -180,7 +180,7 @@ module Decidim::ParticipatoryProcesses
expect(serialized_participatory_process_attachment).to include(title: attachment.title)
expect(serialized_participatory_process_attachment).to include(weight: attachment.weight)
expect(serialized_participatory_process_attachment).to include(description: attachment.description)
- expect(serialized_participatory_process_attachment).to include(remote_file_url: Decidim::AttachmentPresenter.new(resource.attachments.first).attachment_file_url)
+ expect(serialized_participatory_process_attachment[:remote_file_url]).to be_blob_url(resource.attachments.first.file.blob)
end
end
end
diff --git a/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb b/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
index 411c996ad5e4..2339a34fefd5 100644
--- a/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
+++ b/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
@@ -19,6 +19,9 @@ def invite_user
fill_in "Email", with: email
select role, from: "Role"
click_on "Create"
+ expect(page).to have_content("successfully added to this participatory process")
logout :user
+ visit decidim.root_path
+ expect(page).to have_content(organization.name)
end
end
diff --git a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
index 72f13d67ffe6..9b9bb3f5e509 100644
--- a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
+++ b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
@@ -127,8 +127,18 @@
click_on "Update"
expect(page).to have_admin_callout("successfully")
- expect(page).to have_css("img[src*='#{participatory_process3.attached_uploader(:hero_image).path}']")
- expect(page).to have_css("img[src*='#{participatory_process3.attached_uploader(:banner_image).path}']")
+
+ hero_blob = participatory_process3.hero_image.blob
+ within %([data-active-uploads] [data-filename="#{hero_blob.filename}"]) do
+ src = page.find("img")["src"]
+ expect(src).to be_blob_url(hero_blob)
+ end
+
+ banner_blob = participatory_process3.banner_image.blob
+ within %([data-active-uploads] [data-filename="#{banner_blob.filename}"]) do
+ src = page.find("img")["src"]
+ expect(src).to be_blob_url(banner_blob)
+ end
end
end
end
diff --git a/decidim-participatory_processes/spec/types/integration_schema_spec.rb b/decidim-participatory_processes/spec/types/integration_schema_spec.rb
index 16592513b5dc..7960800ca67a 100644
--- a/decidim-participatory_processes/spec/types/integration_schema_spec.rb
+++ b/decidim-participatory_processes/spec/types/integration_schema_spec.rb
@@ -159,7 +159,6 @@
"translation" => participatory_process.announcement[locale]
},
"attachments" => [],
- "bannerImage" => participatory_process.attached_uploader(:banner_image).path.sub(Rails.public_path.to_s, ""),
"categories" => [],
"components" => components,
"createdAt" => participatory_process.created_at.iso8601.to_s.gsub("Z", "+00:00"),
@@ -167,7 +166,6 @@
"developerGroup" => { "translation" => participatory_process.developer_group[locale] },
"endDate" => participatory_process.end_date.to_s,
"hashtag" => "",
- "heroImage" => participatory_process.attached_uploader(:hero_image).path.sub(Rails.public_path.to_s, ""),
"id" => participatory_process.id.to_s,
"linkedParticipatorySpaces" => [],
"localArea" => { "translation" => participatory_process.local_area[locale] },
@@ -203,10 +201,15 @@
describe "valid query" do
it "executes successfully" do
- expect { response }.not_to raise_error(StandardError)
+ expect { response }.not_to raise_error
end
- it { expect(response["participatoryProcess"]).to eq(participatory_process_response) }
+ it "returns the correct response" do
+ data = response["participatoryProcess"]
+ expect(data).to include(participatory_process_response)
+ expect(data["heroImage"]).to be_blob_url(participatory_process.hero_image.blob)
+ expect(data["bannerImage"]).to be_blob_url(participatory_process.banner_image.blob)
+ end
it_behaves_like "implements stats type" do
let(:participatory_process_query) do
diff --git a/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb b/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
index edebf3f4c80c..88645ddfdcbf 100644
--- a/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
+++ b/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
@@ -38,7 +38,7 @@ module ParticipatoryProcesses
let(:query) { "{ heroImage }" }
it "returns the hero image of the process" do
- expect(response["heroImage"]).to eq(model.attached_uploader(:hero_image).path)
+ expect(response["heroImage"]).to be_blob_url(model.hero_image.blob)
end
end
diff --git a/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb b/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
index c83ae3c278d7..8f07aee31837 100644
--- a/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
+++ b/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
@@ -115,7 +115,7 @@ module ParticipatoryProcesses
let(:query) { "{ bannerImage }" }
it "returns the banner image of the process" do
- expect(response["bannerImage"]).to eq(model.attached_uploader(:banner_image).path)
+ expect(response["bannerImage"]).to be_blob_url(model.banner_image.blob)
end
end
@@ -123,7 +123,7 @@ module ParticipatoryProcesses
let(:query) { "{ heroImage }" }
it "returns the hero image of the process" do
- expect(response["heroImage"]).to eq(model.attached_uploader(:hero_image).path)
+ expect(response["heroImage"]).to be_blob_url(model.hero_image.blob)
end
end
diff --git a/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb b/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
index 00d093807862..ca06e9ed8f9e 100644
--- a/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
+++ b/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
@@ -27,7 +27,7 @@ def cache_hash
hash << model.endorsements_count
hash << model.comments_count
hash << Digest::MD5.hexdigest(model.component.cache_key_with_version)
- hash << Digest::MD5.hexdigest(resource_image_path) if resource_image_path
+ hash << Digest::MD5.hexdigest(resource_image_url) if resource_image_url
hash << render_space? ? 1 : 0
if current_user
hash << current_user.cache_key_with_version
diff --git a/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb b/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
index ee86dd209801..3fcd3a4afe22 100644
--- a/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
+++ b/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
@@ -257,7 +257,8 @@
go_to_admin_proposal_page(proposal)
within "#photos" do
- expect(page).to have_xpath("//img[@src=\"#{image.thumbnail_url}\"]")
+ img = page.find("img")
+ expect(img["src"]).to be_blob_url(image.file.blob)
end
end
end
diff --git a/decidim-system/app/controllers/decidim/system/application_controller.rb b/decidim-system/app/controllers/decidim/system/application_controller.rb
index e9de268ecbae..1bfc0f1e9da8 100644
--- a/decidim-system/app/controllers/decidim/system/application_controller.rb
+++ b/decidim-system/app/controllers/decidim/system/application_controller.rb
@@ -8,6 +8,7 @@ class ApplicationController < ActionController::Base
include PayloadInfo
include Headers::HttpCachingDisabler
include DisableRedirectionToExternalHost
+ include ActiveStorage::SetCurrent
protect_from_forgery with: :exception, prepend: true
diff --git a/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb b/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
index 11ff5007504a..c8c33329a7ae 100644
--- a/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
+++ b/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
@@ -20,7 +20,7 @@
<% @oauth_applications.each do |oauth_application| %>
- <%= image_tag oauth_application.attached_uploader(:organization_logo).path, class: "max-w-md" %>
+ <%= image_tag oauth_application.attached_uploader(:organization_logo).url, class: "max-w-md" %>
<%= link_to oauth_application.name, oauth_application %>
diff --git a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb
index 45e39771848d..a3f2cccd6979 100644
--- a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb
+++ b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb
@@ -17,7 +17,7 @@
- <%= image_tag @pending_authorization.attached_uploader(:verification_attachment).variant_path(:big), class: "thumbnail" %>
+ <%= image_tag @pending_authorization.attached_uploader(:verification_attachment).variant_url(:big), class: "thumbnail" %>
diff --git a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
index 98841178a1c3..4249ddd24747 100644
--- a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
+++ b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
@@ -24,7 +24,7 @@
<%= link_to new_pending_authorization_confirmation_path(authorization.id) do %>
- <%= image_tag authorization.attached_uploader(:verification_attachment).variant_path(:thumbnail), class: "thumbnail-list" %>
+ <%= image_tag authorization.attached_uploader(:verification_attachment).variant_url(:thumbnail), class: "thumbnail-list" %>
<% end %>
diff --git a/decidim-verifications/spec/system/id_documents/id_document_online_review_spec.rb b/decidim-verifications/spec/system/id_documents/id_document_online_review_spec.rb
index ce7ae2556d4b..1c8a8dc81fb5 100644
--- a/decidim-verifications/spec/system/id_documents/id_document_online_review_spec.rb
+++ b/decidim-verifications/spec/system/id_documents/id_document_online_review_spec.rb
@@ -58,6 +58,7 @@
context "and the user logs back in" do
before do
+ expect(page).to have_content("Verification rejected. Participant will be prompted to amend their documents")
relogin_as user, scope: :user
visit decidim_verifications.authorizations_path
click_on "Identity documents"
<%= image_tag(
- current_user.attached_uploader(:avatar).path(variant: :thumb),
+ current_user.attached_uploader(:avatar).variant_url(:thumb),
alt: t("decidim.author.avatar", name: decidim_sanitize(current_user.avatar.name))
) %>
diff --git a/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb b/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb
index b8c8fd6aad03..77229e7c8725 100644
--- a/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb
+++ b/decidim-core/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb
@@ -6,7 +6,7 @@
<% if current_user.avatar.attached? %>
<%= image_tag(
- current_user.attached_uploader(:avatar).path(variant: :thumb),
+ current_user.attached_uploader(:avatar).variant_url(:thumb),
alt: t("decidim.author.avatar", name: decidim_sanitize(current_user.avatar.name))
) %>
diff --git a/decidim-core/lib/decidim/asset_router/storage.rb b/decidim-core/lib/decidim/asset_router/storage.rb
index e7d2fb0b67ee..c8f9adf843ec 100644
--- a/decidim-core/lib/decidim/asset_router/storage.rb
+++ b/decidim-core/lib/decidim/asset_router/storage.rb
@@ -6,6 +6,23 @@ module AssetRouter
# saved through ActiveStorage. This handles the different cases for routing
# to the remote routes when using an assets CDN or to local routes when
# using the local disk storage driver.
+ #
+ # Note that when the assets are stored in a remote storage service, such as
+ # Amazon S3, Google Cloud Storage or Azure Storage, this generates the asset
+ # URL directly to the storage service itself bypassing the Rails server and
+ # saving CPU time from serving the asset redirect requests. This causes a
+ # significant performance improvement on pages that display a lot of images.
+ # It will also produce a less significant performance improvement when using
+ # the local disk storage because in this situation, the images are served
+ # using one request instead of two when served directly from the storage
+ # service rather than through the asset redirect URL.
+ #
+ # When implementing changes to the logic, please keep the remote storage
+ # options and performance implications in mind because the specs for this
+ # utility do not cover the remote storage options because the extra
+ # configuration needed to test, the service itself needed for testing and
+ # the extra dependency overhead for adding these remote storage gems when
+ # they are not needed.
class Storage
# Initializes the router.
#
@@ -13,25 +30,36 @@ class Storage
# to
def initialize(asset)
@asset = asset
+ @blob =
+ case asset
+ when ActiveStorage::Blob
+ asset
+ else
+ asset&.blob
+ end
end
# Generates the correct URL to the asset with the provided options.
#
# @param options The options for the URL that are the normal route options
# Rails route helpers accept
- def url(**options)
- if asset.is_a? ActiveStorage::Attached
- routes.rails_blob_url(asset.blob, **default_options.merge(options))
- elsif asset.is_a? ActiveStorage::Blob
- routes.rails_blob_url(asset, **default_options.merge(options))
- else
- representation_url(**options)
+ # @return [String] The URL of the asset
+ def url(**)
+ case asset
+ when ActiveStorage::Attached
+ ensure_current_host(asset.record, **)
+ blob_url(**)
+ when ActiveStorage::Blob
+ blob_url(**)
+ else # ActiveStorage::VariantWithRecord, ActiveStorage::Variant
+ ensure_current_host(nil, **)
+ representation_url(**)
end
end
private
- attr_reader :asset
+ attr_reader :asset, :blob
# Provides the route helpers depending on whether the URL is generated to
# the local host or an external CDN (remote).
@@ -80,24 +108,198 @@ def remote_storage_options
}.compact
end
- # Converts the variation URLs last part to the correct file extension in
- # case the variation has a different format than the original image.
+ # Most of the times the current host should be set through the controller
+ # already when the logic below is unnecessary. This logic is needed e.g.
+ # for serializers where the request context is not available.
+ #
+ # @param record The record for which to check the organization
+ # @param opts Options for building the URL
+ # @return [void]
+ def ensure_current_host(record, **opts)
+ return if asset_url_available?
+
+ options = remote? ? remote_storage_options : routes.default_url_options
+ options = options.merge(opts)
+
+ if opts[:host].blank? && record.present?
+ organization = organization_for(record)
+ options[:host] = organization.host if organization
+ end
+
+ uri =
+ if options[:protocol] == "https" || options[:scheme] == "https"
+ URI::HTTPS.build(options)
+ else
+ URI::HTTP.build(options)
+ end
+
+ ActiveStorage::Current.url_options = { host: uri.to_s }
+ end
+
+ # Determines the organization for the passed record.
#
- # @return [String] The converted representation URL
+ # @param record The record for which to fetch the organization
+ # @return [Decidim::Organization, nil] The organization for the record or
+ # `nil` if the organization cannot be determined
+ def organization_for(record)
+ if record.is_a?(Decidim::Organization)
+ record
+ elsif record.respond_to?(:organization)
+ record.organization
+ end
+ end
+
+ # Returns the URL for the given blob object.
+ #
+ # @param blob The blob object
+ # @param options Options for building the URL
+ # @return [String, nil] The URL to the blob object or `nil` if the blob is
+ # not defined.
+ def blob_url(**options)
+ return unless blob
+
+ if options[:only_path] || remote? || !asset_url_available?
+ routes.rails_blob_url(blob, **default_options.merge(options))
+ else
+ blob.url(**options)
+ end
+ end
+
+ # Returns a representation URL for the asset either directly through the
+ # storage service or through the Rails representation URL in case the
+ # path URL is requested or if the asset variant has not been processed yet
+ # and is not therefore yet stored at the storage service.
+ #
+ # @return [String] The representation URL for the image variant
def representation_url(**options)
+ return rails_representation_url(**options) if options[:only_path] || remote?
+
+ representation_url = variant_url(**options)
+ return representation_url if representation_url.present?
+
+ # In case the representation has not been processed yet, it may not have
+ # a representation URL yet and it therefore needs to be served through
+ # the local representation URL for the first time (or until it has been
+ # processed).
+ if options[:host]
+ rails_representation_url(**options)
+ else
+ representation_url(**options.merge(only_path: true))
+ end
+ end
+
+ # Returns the local Rails representation URL meaning that the asset will
+ # be served through the service itself. This may be necessary if the asset
+ # variant (e.g. a thumbnail) has not been processed yet because the
+ # variant representation has not been requested before.
+ #
+ # Due to performance reasons it is advised to avoid requesting the assets
+ # through the Rails representation URLs when possible because that causes
+ # a lot of requests to the Rails backend and slowness to the service under
+ # heavy loads.
+ #
+ # Converts the variation URLs last part to the correct file extension in
+ # case the variation has a different format than the original image. The
+ # conversion needs to be only done for the Rails representation URLs
+ # because once the image is stored at the storage service, it already has
+ # the correct file extension.
+ #
+ # @param options The options for building the URL
+ # @return [String, nil] The converted representation URL or `nil` if the
+ # asset is not defined.
+ def rails_representation_url(**options)
+ return unless asset
+
representation_url = routes.rails_representation_url(asset, **default_options.merge(options))
+
variation = asset.try(:variation)
return representation_url unless variation
format = variation.try(:format)
return representation_url unless format
+ return unless blob
- original_ext = File.extname(asset.blob.filename.to_s)
+ original_ext = File.extname(blob.filename.to_s)
return representation_url if original_ext == ".#{format}"
- basename = File.basename(asset.blob.filename.to_s, original_ext)
+ basename = File.basename(blob.filename.to_s, original_ext)
representation_url.sub(/#{basename}\.#{original_ext.tr(".", "")}$/, "#{basename}.#{format}")
end
+
+ # Fetches the image variant's URL at the storage service if the variant
+ # has already been processed and is stored at the storage service. If the
+ # variant has not been processed yet, returns `nil` in which case the
+ # variant has to be served through the service's own representation URL
+ # causing it to be processed and stored at the storage service.
+ #
+ # @param options The options for building the URL
+ # @return [String, nil] The variant URL at the storage service or `nil` if
+ # the variant has not been processed yet and does not yet exist at the
+ # storage service or `nil` when the asset is not defined
+ def variant_url(**options)
+ return unless asset
+ return unless asset_url_available?
+ return unless asset_exist?
+
+ case asset
+ when ActiveStorage::VariantWithRecord
+ # This is used when `ActiveStorage.track_variants` is enabled through
+ # `config.active_storage.track_variants`. In case the variant has not
+ # been processed yet, the `#url` method would return nil.
+ #
+ # Note that if the `asset.processed?` returns `true`, the variant
+ # record has been created in the database but it does not mean that
+ # it has been uploaded to the storage service yet. Likely a bug in
+ # ActiveStorage but to be sure that the asset is uploaded to the
+ # storage service, we also check that.
+ asset.url(**options) if asset.processed?
+ else # ActiveStorage::Variant
+ # Check whether the variant exists at the storage service before
+ # returning its URL. Otherwise the URL would be returned even when the
+ # variant is not yet processed causing 404 errors for the images on
+ # the page.
+ #
+ # Note that the `ActiveStorage::Variant#url` method only accepts
+ # certain keyword arguments where as the other objects allow any
+ # keyword arguments.
+ possible_kwargs = asset.method(:url).parameters.select { |p| p[0] == :key }.map { |p| p[1] }
+ asset.url(**options.slice(*possible_kwargs))
+ end
+ end
+
+ # Determines if the asset exists at the storage service.
+ #
+ # @return [Boolean] A boolean answering the question "does this asset
+ # exist at the storage service?".
+ def asset_exist?
+ return false if asset.key.blank?
+
+ blob.service.exist?(asset.key)
+ end
+
+ # Determines if the current host is required to build the asset URL.
+ #
+ # @return [Boolean] A boolean indicating if the current host is required
+ # to build the asset URL.
+ def current_host_required?
+ return false unless blob
+
+ blob.service.is_a?(ActiveStorage::Service::DiskService)
+ end
+
+ # Determines if the asset URL can be generated.
+ #
+ # @return [Boolean] A boolean indicating if the asset URL can be
+ # generated.
+ def asset_url_available?
+ # If the service is an external service, the URL can be generated
+ # regardless of the current host being set.
+ return true unless current_host_required?
+
+ # For the disk service, the URL can be only generated if the current
+ # host has been set.
+ ActiveStorage::Current.url_options&.dig(:host).present?
+ end
end
end
end
diff --git a/decidim-core/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb
index 0269f7ef4194..527c30d8b23e 100644
--- a/decidim-core/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb
+++ b/decidim-core/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb
@@ -10,7 +10,7 @@
it "includes the attachment urls" do
attachment_urls = response["attachments"].map { |attachment| attachment["url"] }
- expect(attachment_urls).to include(*attachments.map(&:url))
+ expect(attachment_urls).to include_blob_urls(*attachments.map(&:file).map(&:blob))
end
end
end
diff --git a/decidim-core/spec/cells/decidim/content_blocks/hero_cell_spec.rb b/decidim-core/spec/cells/decidim/content_blocks/hero_cell_spec.rb
index 69dbe2da4a49..fbaa84fc601b 100644
--- a/decidim-core/spec/cells/decidim/content_blocks/hero_cell_spec.rb
+++ b/decidim-core/spec/cells/decidim/content_blocks/hero_cell_spec.rb
@@ -44,7 +44,7 @@
end
it "uses that image's big version as background" do
- expect(subject.to_s).to include(content_block.images_container.attached_uploader(:background_image).path(variant: :big))
+ expect(subject.to_s).to include(content_block.images_container.attached_uploader(:background_image).variant_url(:big))
end
end
diff --git a/decidim-core/spec/cells/decidim/content_blocks/participatory_space_hero_cell_spec.rb b/decidim-core/spec/cells/decidim/content_blocks/participatory_space_hero_cell_spec.rb
index 8bef65ac6116..e03a269c0b1e 100644
--- a/decidim-core/spec/cells/decidim/content_blocks/participatory_space_hero_cell_spec.rb
+++ b/decidim-core/spec/cells/decidim/content_blocks/participatory_space_hero_cell_spec.rb
@@ -78,7 +78,9 @@
end
it "uses that image's big version as background" do
- expect(subject.to_s).to include(content_block.images_container.attached_uploader(:background_image).path(variant: :big))
+ style = subject.find("section")["style"]
+ background_url = style.match(/background-image:url\('([^']+)'\)/)[1]
+ expect(background_url).to be_blob_url(content_block.images_container.background_image.blob)
end
end
end
diff --git a/decidim-core/spec/lib/asset_router/storage_spec.rb b/decidim-core/spec/lib/asset_router/storage_spec.rb
index 0599affee5b4..7c006394f810 100644
--- a/decidim-core/spec/lib/asset_router/storage_spec.rb
+++ b/decidim-core/spec/lib/asset_router/storage_spec.rb
@@ -17,15 +17,30 @@ module Decidim::AssetRouter
let(:default_port) { Capybara.server_port }
context "with an ActiveStorage::Attached" do
- it "creates the route to the blob" do
- expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ it "creates the disk service route to the blob" do
+ ActiveStorage::Current.url_options = { host: "http://localhost:#{default_port}" }
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
- context "with extra URL options" do
- let(:options) { { port: nil, host: "custom.host", utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
+ context "when the host is not set" do
+ it "sets the host based on the asset" do
+ expect(subject).to match(%r{^http://#{organization.host}:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
+ end
+ end
- it "handles the extra URL options correctly" do
- expect(subject).to match(%r{^http://custom.host/rails/active_storage/blobs/redirect/.*/avatar.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ context "when requesting the local redirect path to the asset" do
+ let(:options) { { only_path: true } }
+
+ it "creates the redirect route to the blob" do
+ expect(subject).to match(%r{^/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
+ end
+
+ context "with extra URL options" do
+ let(:options) { { only_path: true, utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
+
+ it "handles the extra URL options correctly" do
+ expect(subject).to match(%r{^/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ end
end
end
end
@@ -33,25 +48,143 @@ module Decidim::AssetRouter
context "with an ActiveStorage::Blob" do
let(:asset) { organization.official_img_footer.blob }
- it "creates the route to the blob" do
- expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ it "creates the disk service route to the blob" do
+ ActiveStorage::Current.url_options = { host: "http://localhost:#{default_port}" }
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
- context "with extra URL options" do
- let(:options) { { port: nil, host: "custom.host", utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
+ context "when the host is not set" do
+ it "creates the redirect route to the blob" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
+ end
+ end
- it "handles the extra URL options correctly" do
- expect(subject).to match(%r{^http://custom.host/rails/active_storage/blobs/redirect/.*/avatar.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ context "when requesting the local redirect path to the asset" do
+ let(:options) { { only_path: true } }
+
+ it "creates the redirect route to the blob" do
+ expect(subject).to match(%r{^/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
+ end
+
+ context "with extra URL options" do
+ let(:options) { { only_path: true, utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
+
+ it "handles the extra URL options correctly" do
+ expect(subject).to match(%r{^/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ end
end
end
end
context "with a variant" do
let(:asset) { organization.official_img_footer.variant(resize_to_fit: [160, 160]) }
+ let(:track_variants) { true }
+
+ before do
+ # This is typically set through `config.active_storage.track_variants`
+ # and for test environment it seems to be enabled by default at the
+ # time of writing these specs. This config is overridden for these
+ # specs because the default configurations may be changed through
+ # other changes or gem updates.
+ allow(ActiveStorage).to receive(:track_variants).and_return(track_variants)
+ end
it "creates the route to the variant" do
- expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/representations/redirect/.*/avatar.jpg$})
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.jpg$})
+ end
+
+ context "when the asset has been processed" do
+ before { asset.processed }
+
+ it "creates the route to the variant through the storage service" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
+ end
+
+ # Note that this situation should not normally happen but it is
+ # possible e.g. if the backend has created the variant record in the
+ # database but has not yet uploaded the asset to the storage service.
+ context "and does not exist at the storage service" do
+ before do
+ path = asset.blob.service.path_for(asset.key)
+ File.delete(path)
+ end
+
+ it "creates the redirect route to the variant" do
+ expect(asset.processed?).to be(true)
+ expect(asset.key).to be_present
+ expect(asset.blob.service.exist?(asset.key)).to be(false)
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.jpg$})
+ end
+ end
end
+
+ context "when track_variants is disabled" do
+ let(:track_variants) { false }
+
+ it "creates the route to the variant" do
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.jpg$})
+ end
+
+ context "and the asset has been processed" do
+ before { asset.processed }
+
+ it "creates the route to the variant through the storage service" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
+ end
+
+ context "and when passing incompatible URL options" do
+ # The `:host` option is passed e.g. in many mailers.
+ # `ActiveStorage::Variant#url` method does not allow this argument
+ # which is why this test is testing that it does not lead to an
+ # error.
+ let(:options) { { host: "example.lvh.me" } }
+
+ it "creates the route to the variant" do
+ expect(subject).to match(%r{^http://example\.lvh\.me:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
+ end
+ end
+ end
+ end
+
+ context "when the variant has a different file extension" do
+ let(:asset) { organization.official_img_footer.variant(resize_to_fit: [160, 160], format: "png") }
+
+ it "creates the route to the variant with converted file extension" do
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.png$})
+ end
+
+ context "when the asset has been processed" do
+ before { asset.processed }
+
+ it "creates the route to the variant through the storage service" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.png$})
+ end
+ end
+
+ context "when track_variants is disabled" do
+ let(:track_variants) { false }
+
+ it "creates the route to the variant with converted file extension" do
+ expect(subject).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.png$})
+ end
+
+ context "and the asset has been processed" do
+ before { asset.processed }
+
+ it "creates the route to the variant through the storage service" do
+ expect(subject).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.png$})
+ end
+ end
+ end
+ end
+ end
+
+ # This is used by the generator specs to check that some default
+ # configurations are set correctly.
+ context "with nil" do
+ let(:asset) { nil }
+
+ it { is_expected.to be_nil }
end
context "when the CDN host is defined" do
@@ -61,14 +194,14 @@ module Decidim::AssetRouter
end
it "creates the route to the CDN blob" do
- expect(subject).to match(%r{^https://cdn.example.org/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject).to match(%r{^https://cdn\.example\.org/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
end
context "with extra URL options" do
let(:options) { { utm_source: "website", utm_medium: "email", utm_campaign: "testing" } }
it "handles the extra URL options correctly" do
- expect(subject).to match(%r{^https://cdn.example.org/rails/active_storage/blobs/redirect/.*/avatar.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
+ expect(subject).to match(%r{^https://cdn\.example\.org/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg\?utm_campaign=testing&utm_medium=email&utm_source=website$})
end
end
end
diff --git a/decidim-core/spec/lib/upgrade/wysiwyg_migrator_spec.rb b/decidim-core/spec/lib/upgrade/wysiwyg_migrator_spec.rb
index b6f240dbdf4a..30168bbc1fe4 100644
--- a/decidim-core/spec/lib/upgrade/wysiwyg_migrator_spec.rb
+++ b/decidim-core/spec/lib/upgrade/wysiwyg_migrator_spec.rb
@@ -9,6 +9,7 @@ module Decidim
let(:organization) { create(:organization) }
let(:component) { create(:component, organization:) }
let(:image) { create(:attachment, attached_to: organization) }
+ let(:image_path) { image.attached_uploader(:file).path }
let(:content) { original_content }
let(:original_content) do
@@ -46,10 +47,10 @@ module Decidim
Paragraph content with an inline image.
-
+
And some text after that.
Here we had some unrecognized node.
Blockquote element content@@ -151,11 +152,11 @@ module Decidim
should be wrapped inside a paragraph.
Paragraph content with an inline image.
-
+
And some text after that.
-
+
diff --git a/decidim-core/spec/models/decidim/content_block_spec.rb b/decidim-core/spec/models/decidim/content_block_spec.rb
index 21bb18966694..8e3ee29f677a 100644
--- a/decidim-core/spec/models/decidim/content_block_spec.rb
+++ b/decidim-core/spec/models/decidim/content_block_spec.rb
@@ -25,7 +25,7 @@ module Decidim
context "when the image has not been uploaded" do
it "returns nil" do
- expect(subject.images_container.attached_uploader(:background_image).path).to be_nil
+ expect(subject.images_container.attached_uploader(:background_image).url).to be_nil
end
end
@@ -46,7 +46,7 @@ module Decidim
it "returns the image" do
expect(subject.images_container.background_image.attached?).to be true
- expect(subject.images_container.attached_uploader(:background_image).path).not_to be_nil
+ expect(subject.images_container.attached_uploader(:background_image).url).not_to be_nil
end
end
diff --git a/decidim-core/spec/types/user_group_type_spec.rb b/decidim-core/spec/types/user_group_type_spec.rb
index a54a4f61972d..e753c09b5228 100644
--- a/decidim-core/spec/types/user_group_type_spec.rb
+++ b/decidim-core/spec/types/user_group_type_spec.rb
@@ -38,7 +38,7 @@ module Core
let(:query) { "{ avatarUrl }" }
it "returns the user avatar url" do
- expect(response).to include("avatarUrl" => model.attached_uploader(:avatar).path)
+ expect(response["avatarUrl"]).to be_blob_url(model.avatar.blob)
end
end
diff --git a/decidim-core/spec/types/user_type_spec.rb b/decidim-core/spec/types/user_type_spec.rb
index 79613a22a8c2..bf734001ce4b 100644
--- a/decidim-core/spec/types/user_type_spec.rb
+++ b/decidim-core/spec/types/user_type_spec.rb
@@ -50,7 +50,7 @@ module Core
let(:query) { "{ avatarUrl }" }
it "returns the user avatar url (small version)" do
- expect(response).to include("avatarUrl" => model.attached_uploader(:avatar).path(variant: :thumb))
+ expect(response).to include("avatarUrl" => model.attached_uploader(:avatar).variant_url(:thumb))
end
end
diff --git a/decidim-core/spec/uploaders/application_uploader_spec.rb b/decidim-core/spec/uploaders/application_uploader_spec.rb
index 7a9a9c8b2830..8e8e8596425a 100644
--- a/decidim-core/spec/uploaders/application_uploader_spec.rb
+++ b/decidim-core/spec/uploaders/application_uploader_spec.rb
@@ -7,6 +7,7 @@ module Decidim
subject { described_class.new(model, mounted_as) }
let(:model) { create(:organization) }
+ let(:hostname) { model.host }
let(:mounted_as) { :official_img_footer }
before do
@@ -48,21 +49,21 @@ module Decidim
let(:default_port) { Rails.env.development? ? 3000 : Capybara.server_port }
it "returns a URL containing the port only" do
- expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^http://#{Regexp.escape(hostname)}:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
context "when force_ssl is enabled" do
include_context "with force_ssl enabled"
it "returns a URL containing the port and protocol" do
- expect(subject.variant_url(:testing)).to match(%r{^https://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^https://#{Regexp.escape(hostname)}:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
context "and the PORT environment variable is defined as 3001" do
let(:local_port) { 3001 }
it "returns a URL containing the port and protocol" do
- expect(subject.variant_url(:testing)).to match(%r{^https://localhost:3001/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^https://#{Regexp.escape(hostname)}:3001/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
@@ -70,7 +71,7 @@ module Decidim
let(:local_port) { 443 }
it "returns a URL containing the protocol only" do
- expect(subject.variant_url(:testing)).to match(%r{^https://localhost/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^https://#{Regexp.escape(hostname)}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
end
@@ -87,7 +88,7 @@ module Decidim
end
it "returns a URL to the variant" do
- expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/representations/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.jpg$})
end
context "when the provided file is invariable" do
@@ -96,7 +97,7 @@ module Decidim
end
it "returns the original URL" do
- expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^http://#{Regexp.escape(hostname)}:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
end
@@ -113,7 +114,15 @@ module Decidim
end
it "returns a URL to the variant with the correct extension" do
- expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/representations/redirect/.*/avatar.png$})
+ expect(subject.variant_url(:testing)).to match(%r{^/rails/active_storage/representations/redirect/[^/]+/[^/]+/avatar\.png$})
+ end
+
+ context "and the variant has been processed" do
+ before { subject.variant(:testing).process }
+
+ it "returns a URL to the variant with the correct extension" do
+ expect(subject.variant_url(:testing)).to match(%r{^http://localhost:#{default_port}/rails/active_storage/disk/[^/]+/avatar\.png$})
+ end
end
end
end
@@ -132,6 +141,7 @@ module Decidim
context "with production environment" do
let(:hostname) { "production.decidim.test" }
+ let(:options) { { host: hostname } }
before do
allow(Rails.env).to receive(:development?).and_return(false)
@@ -139,14 +149,14 @@ module Decidim
end
it "returns a default URL" do
- expect(subject.variant_url(:testing)).to match(%r{^http://production.decidim.test/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^http://#{Regexp.escape(hostname)}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
context "and the PORT environment variable is defined as 443" do
let(:local_port) { 443 }
it "returns a URL containing the protocol only" do
- expect(subject.variant_url(:testing)).to match(%r{^https://production.decidim.test/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^https://#{Regexp.escape(hostname)}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
@@ -154,7 +164,7 @@ module Decidim
let(:local_port) { 8080 }
it "returns a URL containing the port" do
- expect(subject.variant_url(:testing)).to match(%r{^http://production.decidim.test:8080/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^http://#{Regexp.escape(hostname)}:8080/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
@@ -162,14 +172,14 @@ module Decidim
include_context "with force_ssl enabled"
it "returns a URL containing the protocol only" do
- expect(subject.variant_url(:testing)).to match(%r{^https://production.decidim.test/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^https://#{Regexp.escape(hostname)}/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
context "and the PORT environment variable is defined as 8080" do
let(:local_port) { 8080 }
it "returns a URL containing the port and protocol" do
- expect(subject.variant_url(:testing)).to match(%r{^https://production.decidim.test:8080/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing, options)).to match(%r{^https://#{Regexp.escape(hostname)}:8080/rails/active_storage/disk/[^/]+/avatar\.jpg$})
end
end
end
@@ -190,7 +200,7 @@ module Decidim
end
it "returns a URL containing the CDN configurations" do
- expect(subject.variant_url(:testing)).to match(%r{^https://cdn.example.org/rails/active_storage/blobs/redirect/.*/avatar.jpg$})
+ expect(subject.variant_url(:testing)).to match(%r{^#{Regexp.escape(cdn_host)}/rails/active_storage/blobs/redirect/[^/]+/avatar\.jpg$})
end
end
end
diff --git a/decidim-dev/lib/decidim/dev/test/rspec_support/activestorage_matchers.rb b/decidim-dev/lib/decidim/dev/test/rspec_support/activestorage_matchers.rb
new file mode 100644
index 000000000000..170f43dea3f7
--- /dev/null
+++ b/decidim-dev/lib/decidim/dev/test/rspec_support/activestorage_matchers.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+module ActiveStorageMatchers
+ def be_blob_url(expected)
+ BeBlobUrl.new(expected)
+ end
+
+ def include_blob_urls(*expected)
+ IncludeBlobUrls.new(expected)
+ end
+
+ class BlobMatch
+ BLOB_URL_MATCHERS = {
+ redirect: %r{/rails/active_storage/blobs/redirect/([^/]+)/([^/]+)$},
+ representation: %r{/rails/active_storage/representations/redirect/([^/]+)/([^/]+)/([^/]+)$},
+ disk: %r{/rails/active_storage/disk/([^/]+)/([^/]+)$}
+ }.freeze
+
+ def initialize(url)
+ @url = url
+ end
+
+ def blob
+ return unless key_match
+
+ @blob ||=
+ case url_type
+ when :redirect, :representation
+ ActiveStorage::Blob.find_signed(key_match)
+ when :disk
+ decoded = ActiveStorage.verifier.verified(key_match, purpose: :blob_key)
+ ActiveStorage::Blob.find_by(key: decoded[:key]) if decoded
+ end
+ end
+
+ def variation
+ return unless variation_match
+
+ blob.representation(variation_match)
+ end
+
+ def key_match
+ return unless match
+
+ match[1]
+ end
+
+ def variation_match
+ return unless match
+
+ match[2] if url_type == :representation
+ end
+
+ def filename_match
+ return unless match
+
+ case url_type
+ when :representation
+ match[3]
+ else
+ match[2]
+ end
+ end
+
+ private
+
+ attr_reader :url, :url_type
+
+ def match
+ return @match if @url_type
+
+ @url_type = :none
+ @match = nil
+ BLOB_URL_MATCHERS.each do |type, matcher|
+ @match = url.match(matcher)
+ if @match
+ @url_type = type
+ break
+ end
+ end
+
+ @match
+ end
+ end
+
+ class BeBlobUrl
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def description
+ "be a blob URL"
+ end
+
+ def matches?(actual)
+ @actual = actual
+ match = BlobMatch.new(actual)
+ match.blob == expected
+ end
+
+ def failure_message
+ "expected #{actual} to match blob with ID #{expected.id}"
+ end
+
+ def failure_message_when_negated
+ "expected #{actual} not to match blob with ID #{expected.id}"
+ end
+
+ private
+
+ attr_reader :expected, :actual
+ end
+
+ class IncludeBlobUrls
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def description
+ "include blob URLs"
+ end
+
+ def matches?(actual)
+ @actual = actual
+
+ actual.all? do |url|
+ match = BlobMatch.new(url)
+ expected.include?(match.blob)
+ end
+ end
+
+ def failure_message
+ "expected #{actual.inspect} to match blobs with ID #{expected.map(&:id).join(", ")}"
+ end
+
+ def failure_message_when_negated
+ "expected #{actual.inspect} not to match blobs with ID #{expected.map(&:id).join(", ")}"
+ end
+
+ private
+
+ attr_reader :expected, :actual
+ end
+end
+
+RSpec.configure do |config|
+ config.include ActiveStorageMatchers
+end
diff --git a/decidim-dev/lib/decidim/dev/test/spec_helper.rb b/decidim-dev/lib/decidim/dev/test/spec_helper.rb
index d22822c11cc6..3a371add381a 100644
--- a/decidim-dev/lib/decidim/dev/test/spec_helper.rb
+++ b/decidim-dev/lib/decidim/dev/test/spec_helper.rb
@@ -45,4 +45,11 @@
"connect-src": %W(#{Decidim::Dev::Test::MapServer.host})
}
end
+
+ config.before do
+ # Ensure that the current host is not set for any spec in order to test that
+ # the automatic current host definition is working correctly in all
+ # situations.
+ ActiveStorage::Current.url_options = {}
+ end
end
diff --git a/decidim-forms/spec/presenters/decidim/admin/questionnaire_answer_presenter_spec.rb b/decidim-forms/spec/presenters/decidim/admin/questionnaire_answer_presenter_spec.rb
index d17a91a260be..bc3d057f5292 100644
--- a/decidim-forms/spec/presenters/decidim/admin/questionnaire_answer_presenter_spec.rb
+++ b/decidim-forms/spec/presenters/decidim/admin/questionnaire_answer_presenter_spec.rb
@@ -79,14 +79,28 @@ module Decidim
let!(:attachment) { create(:attachment, :with_image, attached_to: answer) }
it "returns the download attachment link" do
- expect(subject.body).to eq(%())
+ regexp = %r{^
- ((((?!).)*))
diff --git a/decidim-initiatives/app/views/decidim/initiatives/initiatives/print.html.erb b/decidim-initiatives/app/views/decidim/initiatives/initiatives/print.html.erb
index 324813412987..0e17fa89030b 100644
--- a/decidim-initiatives/app/views/decidim/initiatives/initiatives/print.html.erb
+++ b/decidim-initiatives/app/views/decidim/initiatives/initiatives/print.html.erb
@@ -2,7 +2,7 @@
<% if current_organization.logo.present? %>
- <%= image_tag current_organization.attached_uploader(:logo).path(variant: :medium), title: current_organization_name %>
+ <%= image_tag current_organization.attached_uploader(:logo).variant_url(:medium), title: current_organization_name %>
<% end %>
<%= current_organization_name %>
diff --git a/decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb b/decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb
index e1f5d5ee1119..46b35d2c57a4 100644
--- a/decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb
+++ b/decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb
@@ -1,5 +1,5 @@
<% add_decidim_meta_tags({
- image_url: current_initiative.type.attached_uploader(:banner_image).path,
+ image_url: current_initiative.type.attached_uploader(:banner_image).url,
description: translated_attribute(current_initiative.description),
title: translated_attribute(current_initiative.title),
url: initiative_url(current_initiative.id)
diff --git a/decidim-initiatives/app/views/layouts/decidim/_initiative_header.html.erb b/decidim-initiatives/app/views/layouts/decidim/_initiative_header.html.erb
index f098bce3296d..c91068db1bbb 100644
--- a/decidim-initiatives/app/views/layouts/decidim/_initiative_header.html.erb
+++ b/decidim-initiatives/app/views/layouts/decidim/_initiative_header.html.erb
@@ -1,7 +1,7 @@
+ style="background-image:url('<%= current_participatory_space.type.attached_uploader(:banner_image).url %>');">
diff --git a/decidim-initiatives/app/views/layouts/decidim/initiative.html.erb b/decidim-initiatives/app/views/layouts/decidim/initiative.html.erb
index 16c36ff60024..a49d60de5d8a 100644
--- a/decidim-initiatives/app/views/layouts/decidim/initiative.html.erb
+++ b/decidim-initiatives/app/views/layouts/decidim/initiative.html.erb
@@ -1,7 +1,7 @@
<%= append_javascript_pack_tag "decidim_initiatives" %>
<%= append_stylesheet_pack_tag "decidim_initiatives", media: "all" %>
-<% provide :meta_image_url, current_participatory_space.type.attached_uploader(:banner_image).path %>
+<% provide :meta_image_url, current_participatory_space.type.attached_uploader(:banner_image).url %>
<%= render "layouts/decidim/application" do %>
<%= render layout: "layouts/decidim/shared/layout_center" do %>
diff --git a/decidim-initiatives/app/views/layouts/decidim/initiative_head.html.erb b/decidim-initiatives/app/views/layouts/decidim/initiative_head.html.erb
index 0a59ea4ea6e3..2a644271e767 100644
--- a/decidim-initiatives/app/views/layouts/decidim/initiative_head.html.erb
+++ b/decidim-initiatives/app/views/layouts/decidim/initiative_head.html.erb
@@ -1,7 +1,7 @@
<%= append_javascript_pack_tag "decidim_initiatives" %>
<%= append_stylesheet_pack_tag "decidim_initiatives", media: "all" %>
-<% provide :meta_image_url, current_initiative.type.attached_uploader(:banner_image).path %>
+<% provide :meta_image_url, current_initiative.type.attached_uploader(:banner_image).url %>
<%= render partial: "layouts/decidim/header/follow_space_menu_bar_button", locals: { participatory_space: current_participatory_space } %>
diff --git a/decidim-initiatives/lib/decidim/api/initiative_api_type.rb b/decidim-initiatives/lib/decidim/api/initiative_api_type.rb
index 05cacdbecbd5..63cb06cd8274 100644
--- a/decidim-initiatives/lib/decidim/api/initiative_api_type.rb
+++ b/decidim-initiatives/lib/decidim/api/initiative_api_type.rb
@@ -23,7 +23,7 @@ class InitiativeApiType < Decidim::Api::Types::BaseObject
field :initiatives, [Decidim::Initiatives::InitiativeType, { null: true }], "The initiatives that have this type", null: false
def banner_image
- object.attached_uploader(:banner_image).path
+ object.attached_uploader(:banner_image).url
end
end
end
diff --git a/decidim-initiatives/spec/types/initiative_api_type_spec.rb b/decidim-initiatives/spec/types/initiative_api_type_spec.rb
index e60e8b67d6e9..b735f1bcc43a 100644
--- a/decidim-initiatives/spec/types/initiative_api_type_spec.rb
+++ b/decidim-initiatives/spec/types/initiative_api_type_spec.rb
@@ -54,7 +54,7 @@ module Initiatives
let(:query) { "{ bannerImage }" }
it "returns the banner image field" do
- expect(response["bannerImage"]).to eq(model.attached_uploader(:banner_image).path)
+ expect(response["bannerImage"]).to be_blob_url(model.banner_image.blob)
end
end
diff --git a/decidim-initiatives/spec/types/integration_schema_spec.rb b/decidim-initiatives/spec/types/integration_schema_spec.rb
index 8a19a8c24edd..7e1f227087ab 100644
--- a/decidim-initiatives/spec/types/integration_schema_spec.rb
+++ b/decidim-initiatives/spec/types/integration_schema_spec.rb
@@ -29,22 +29,6 @@
"description" => { "translation" => initiative.description[locale] },
"hashtag" => initiative.hashtag,
"id" => initiative.id.to_s,
- "initiativeType" => {
- "bannerImage" => initiative.type.attached_uploader(:banner_image).path,
- "collectUserExtraFields" => initiative.type.collect_user_extra_fields?,
- "createdAt" => initiative.type.created_at.iso8601.to_s.gsub("Z", "+00:00"),
- "description" => { "translation" => initiative.type.description[locale] },
- "extraFieldsLegalInformation" => initiative.type.extra_fields_legal_information,
- "id" => initiative.type.id.to_s,
- "initiatives" => initiative.type.initiatives.map { |i| { "id" => i.id.to_s } },
- "minimumCommitteeMembers" => initiative.type.minimum_committee_members,
- "promotingCommitteeEnabled" => initiative.type.promoting_committee_enabled,
- "signatureType" => initiative.type.signature_type,
- "title" => { "translation" => initiative.type.title[locale] },
- "undoOnlineSignaturesEnabled" => initiative.type.undo_online_signatures_enabled,
- "updatedAt" => initiative.type.updated_at.iso8601.to_s.gsub("Z", "+00:00"),
- "validateSmsCodeOnVotes" => initiative.type.validate_sms_code_on_votes
- },
"offlineVotes" => initiative.offline_votes_count,
"onlineVotes" => initiative.online_votes_count,
"publishedAt" => initiative.published_at.iso8601.to_s.gsub("Z", "+00:00"),
@@ -61,6 +45,23 @@
}
end
+ let(:initiative_type_data) do
+ {
+ "collectUserExtraFields" => initiative.type.collect_user_extra_fields?,
+ "createdAt" => initiative.type.created_at.iso8601.to_s.gsub("Z", "+00:00"),
+ "description" => { "translation" => initiative.type.description[locale] },
+ "extraFieldsLegalInformation" => initiative.type.extra_fields_legal_information,
+ "id" => initiative.type.id.to_s,
+ "initiatives" => initiative.type.initiatives.map { |i| { "id" => i.id.to_s } },
+ "minimumCommitteeMembers" => initiative.type.minimum_committee_members,
+ "promotingCommitteeEnabled" => initiative.type.promoting_committee_enabled,
+ "signatureType" => initiative.type.signature_type,
+ "title" => { "translation" => initiative.type.title[locale] },
+ "undoOnlineSignaturesEnabled" => initiative.type.undo_online_signatures_enabled,
+ "updatedAt" => initiative.type.updated_at.iso8601.to_s.gsub("Z", "+00:00"),
+ "validateSmsCodeOnVotes" => initiative.type.validate_sms_code_on_votes
+ }
+ end
let(:initiatives) do
%(
@@ -143,7 +144,10 @@
end
it "returns the correct response" do
- expect(response["initiatives"].first).to eq(initiative_data)
+ data = response["initiatives"].first
+ expect(data).to include(initiative_data)
+ expect(data["initiativeType"]).to include(initiative_type_data)
+ expect(data["initiativeType"]["bannerImage"]).to be_blob_url(initiative.type.banner_image.blob)
end
it_behaves_like "implements stats type" do
@@ -161,7 +165,7 @@
end
end
- describe "single assembly" do
+ describe "single initiative" do
let(:initiatives) do
%(
initiative(id: #{initiative.id}){
@@ -234,7 +238,10 @@
end
it "returns the correct response" do
- expect(response["initiative"]).to eq(initiative_data)
+ data = response["initiative"]
+ expect(data).to include(initiative_data)
+ expect(data["initiativeType"]).to include(initiative_type_data)
+ expect(data["initiativeType"]["bannerImage"]).to be_blob_url(initiative.type.banner_image.blob)
end
it_behaves_like "implements stats type" do
diff --git a/decidim-meetings/app/cells/decidim/meetings/meeting_l_cell.rb b/decidim-meetings/app/cells/decidim/meetings/meeting_l_cell.rb
index fea8f0a2a9b0..ac48249eb1e8 100644
--- a/decidim-meetings/app/cells/decidim/meetings/meeting_l_cell.rb
+++ b/decidim-meetings/app/cells/decidim/meetings/meeting_l_cell.rb
@@ -9,10 +9,6 @@ module Meetings
class MeetingLCell < Decidim::CardLCell
alias meeting model
- def has_image?
- true
- end
-
def extra_class
"card__calendar-list__reset"
end
diff --git a/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_g_cell.rb b/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_g_cell.rb
index 1431a9464375..49481561005f 100644
--- a/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_g_cell.rb
+++ b/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_g_cell.rb
@@ -11,8 +11,8 @@ def resource_path
Decidim::ParticipatoryProcesses::Engine.routes.url_helpers.participatory_process_path(model)
end
- def resource_image_path
- model.attached_uploader(:hero_image).path
+ def resource_image_url
+ model.attached_uploader(:hero_image).url
end
def start_date
diff --git a/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_group_g_cell.rb b/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_group_g_cell.rb
index 9c1101a8d901..e75ab09bbef2 100644
--- a/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_group_g_cell.rb
+++ b/decidim-participatory_processes/app/cells/decidim/participatory_processes/process_group_g_cell.rb
@@ -11,8 +11,8 @@ def resource_path
Decidim::ParticipatoryProcesses::Engine.routes.url_helpers.participatory_process_group_path(model)
end
- def resource_image_path
- model.attached_uploader(:hero_image).path
+ def resource_image_url
+ model.attached_uploader(:hero_image).url
end
def metadata_cell
diff --git a/decidim-participatory_processes/app/helpers/decidim/participatory_processes/participatory_process_helper.rb b/decidim-participatory_processes/app/helpers/decidim/participatory_processes/participatory_process_helper.rb
index 32b39a236c29..9f28625cb597 100644
--- a/decidim-participatory_processes/app/helpers/decidim/participatory_processes/participatory_process_helper.rb
+++ b/decidim-participatory_processes/app/helpers/decidim/participatory_processes/participatory_process_helper.rb
@@ -53,7 +53,7 @@ def participatory_process_group_cta_settings(process_group)
OpenStruct.new(
text: translated_attribute(cta_settings.button_text),
path: cta_settings.button_url,
- image_url: block.images_container.attached_uploader(:background_image).path(variant: :big)
+ image_url: block.images_container.attached_uploader(:background_image).variant_url(:big)
)
end
diff --git a/decidim-participatory_processes/app/views/decidim/participatory_processes/participatory_processes/show.html.erb b/decidim-participatory_processes/app/views/decidim/participatory_processes/participatory_processes/show.html.erb
index 44ccefcc2185..7d33d425a6db 100644
--- a/decidim-participatory_processes/app/views/decidim/participatory_processes/participatory_processes/show.html.erb
+++ b/decidim-participatory_processes/app/views/decidim/participatory_processes/participatory_processes/show.html.erb
@@ -1,6 +1,6 @@
<% add_decidim_meta_tags({
title: translated_attribute(current_participatory_space.title),
- image_url: current_participatory_space.attached_uploader(:hero_image).path,
+ image_url: current_participatory_space.attached_uploader(:hero_image).url,
description: translated_attribute(current_participatory_space.short_description),
url: participatory_process_url(current_participatory_space)
}) %>
diff --git a/decidim-participatory_processes/app/views/layouts/decidim/_process_header.html.erb b/decidim-participatory_processes/app/views/layouts/decidim/_process_header.html.erb
index 31ac78eb82f1..3eeb377e44fa 100644
--- a/decidim-participatory_processes/app/views/layouts/decidim/_process_header.html.erb
+++ b/decidim-participatory_processes/app/views/layouts/decidim/_process_header.html.erb
@@ -1,4 +1,4 @@
-
+
diff --git a/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb b/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
index 3c7fdc2f3a84..5bb35ea9f490 100644
--- a/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
+++ b/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
@@ -14,7 +14,7 @@ class ParticipatoryProcessGroupType < Decidim::Api::Types::BaseObject
field :hero_image, GraphQL::Types::String, "The hero image for this participatory process group", null: true
def hero_image
- object.attached_uploader(:hero_image).path
+ object.attached_uploader(:hero_image).url
end
end
end
diff --git a/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb b/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
index db6f068652bb..00f31d79559a 100644
--- a/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
+++ b/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
@@ -47,11 +47,11 @@ class ParticipatoryProcessType < Decidim::Api::Types::BaseObject
description: "The participatory process group in which this process belong to"
def banner_image
- object.attached_uploader(:banner_image).path
+ object.attached_uploader(:banner_image).url
end
def hero_image
- object.attached_uploader(:hero_image).path
+ object.attached_uploader(:hero_image).url
end
end
end
diff --git a/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb b/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
index b0baced444f4..fda26ea52aa5 100644
--- a/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
+++ b/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
@@ -22,7 +22,8 @@ module Decidim::ParticipatoryProcesses
end
it "displays the process hero image" do
- expect(subject).to have_css("img[src='#{model.attached_uploader(:hero_image).path}']")
+ img = subject.find("img")
+ expect(img["src"]).to be_blob_url(model.hero_image.blob)
end
it_behaves_like "process card with metadata", metadata_class: "card__grid-metadata"
diff --git a/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb b/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
index aad80b079dbd..3582ecd1557f 100644
--- a/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
+++ b/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
@@ -22,7 +22,7 @@ module Decidim
context "when image is attached" do
it "returns an URL including the organization domain" do
expect(subject.hero_image_url).to include(process.organization.host)
- expect(subject.hero_image_url).to include(process.attached_uploader(:hero_image).path)
+ expect(subject.hero_image_url).to be_blob_url(process.hero_image.blob)
end
end
end
@@ -41,7 +41,7 @@ module Decidim
context "when image is attached" do
it "returns an URL including the organization domain" do
expect(subject.banner_image_url).to include(process.organization.host)
- expect(subject.banner_image_url).to include(process.attached_uploader(:banner_image).path)
+ expect(subject.banner_image_url).to be_blob_url(process.banner_image.blob)
end
end
end
diff --git a/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb b/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
index f327aa53b896..ab7545c5c279 100644
--- a/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
+++ b/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
@@ -14,29 +14,29 @@ module Decidim::ParticipatoryProcesses
expect(serialized).to be_a(Hash)
- expect(subject.serialize).to include(id: resource.id)
- expect(subject.serialize).to include(title: resource.title)
- expect(subject.serialize).to include(subtitle: resource.subtitle)
- expect(subject.serialize).to include(slug: resource.slug)
- expect(subject.serialize).to include(hashtag: resource.hashtag)
- expect(subject.serialize).to include(short_description: resource.short_description)
- expect(subject.serialize).to include(description: resource.description)
- expect(subject.serialize).to include(announcement: resource.announcement)
- expect(subject.serialize).to include(start_date: resource.start_date)
- expect(subject.serialize).to include(end_date: resource.end_date)
- expect(subject.serialize).to include(remote_hero_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessPresenter.new(resource).hero_image_url)
- expect(subject.serialize).to include(remote_banner_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessPresenter.new(resource).banner_image_url)
- expect(subject.serialize).to include(developer_group: resource.developer_group)
- expect(subject.serialize).to include(local_area: resource.local_area)
- expect(subject.serialize).to include(meta_scope: resource.meta_scope)
- expect(subject.serialize).to include(participatory_scope: resource.participatory_scope)
- expect(subject.serialize).to include(participatory_structure: resource.participatory_structure)
- expect(subject.serialize).to include(target: resource.target)
- expect(subject.serialize).to include(private_space: resource.private_space)
- expect(subject.serialize).to include(promoted: resource.promoted)
- expect(subject.serialize).to include(scopes_enabled: resource.scopes_enabled)
- expect(subject.serialize).to include(show_metrics: resource.show_metrics)
- expect(subject.serialize).to include(show_statistics: resource.show_statistics)
+ expect(serialized).to include(id: resource.id)
+ expect(serialized).to include(title: resource.title)
+ expect(serialized).to include(subtitle: resource.subtitle)
+ expect(serialized).to include(slug: resource.slug)
+ expect(serialized).to include(hashtag: resource.hashtag)
+ expect(serialized).to include(short_description: resource.short_description)
+ expect(serialized).to include(description: resource.description)
+ expect(serialized).to include(announcement: resource.announcement)
+ expect(serialized).to include(start_date: resource.start_date)
+ expect(serialized).to include(end_date: resource.end_date)
+ expect(serialized[:remote_hero_image_url]).to be_blob_url(resource.hero_image.blob)
+ expect(serialized[:remote_banner_image_url]).to be_blob_url(resource.banner_image.blob)
+ expect(serialized).to include(developer_group: resource.developer_group)
+ expect(serialized).to include(local_area: resource.local_area)
+ expect(serialized).to include(meta_scope: resource.meta_scope)
+ expect(serialized).to include(participatory_scope: resource.participatory_scope)
+ expect(serialized).to include(participatory_structure: resource.participatory_structure)
+ expect(serialized).to include(target: resource.target)
+ expect(serialized).to include(private_space: resource.private_space)
+ expect(serialized).to include(promoted: resource.promoted)
+ expect(serialized).to include(scopes_enabled: resource.scopes_enabled)
+ expect(serialized).to include(show_metrics: resource.show_metrics)
+ expect(serialized).to include(show_statistics: resource.show_statistics)
end
context "when process has area" do
@@ -109,7 +109,7 @@ module Decidim::ParticipatoryProcesses
expect(serialized_participatory_process_group).to include(id: resource.participatory_process_group.id)
expect(serialized_participatory_process_group).to include(title: resource.participatory_process_group.title)
expect(serialized_participatory_process_group).to include(description: resource.participatory_process_group.description)
- expect(serialized_participatory_process_group).to include(remote_hero_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessGroupPresenter.new(resource.participatory_process_group).hero_image_url)
+ expect(serialized_participatory_process_group[:remote_hero_image_url]).to be_blob_url(resource.participatory_process_group.hero_image.blob)
end
end
@@ -180,7 +180,7 @@ module Decidim::ParticipatoryProcesses
expect(serialized_participatory_process_attachment).to include(title: attachment.title)
expect(serialized_participatory_process_attachment).to include(weight: attachment.weight)
expect(serialized_participatory_process_attachment).to include(description: attachment.description)
- expect(serialized_participatory_process_attachment).to include(remote_file_url: Decidim::AttachmentPresenter.new(resource.attachments.first).attachment_file_url)
+ expect(serialized_participatory_process_attachment[:remote_file_url]).to be_blob_url(resource.attachments.first.file.blob)
end
end
end
diff --git a/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb b/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
index 411c996ad5e4..2339a34fefd5 100644
--- a/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
+++ b/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
@@ -19,6 +19,9 @@ def invite_user
fill_in "Email", with: email
select role, from: "Role"
click_on "Create"
+ expect(page).to have_content("successfully added to this participatory process")
logout :user
+ visit decidim.root_path
+ expect(page).to have_content(organization.name)
end
end
diff --git a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
index 72f13d67ffe6..9b9bb3f5e509 100644
--- a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
+++ b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
@@ -127,8 +127,18 @@
click_on "Update"
expect(page).to have_admin_callout("successfully")
- expect(page).to have_css("img[src*='#{participatory_process3.attached_uploader(:hero_image).path}']")
- expect(page).to have_css("img[src*='#{participatory_process3.attached_uploader(:banner_image).path}']")
+
+ hero_blob = participatory_process3.hero_image.blob
+ within %([data-active-uploads] [data-filename="#{hero_blob.filename}"]) do
+ src = page.find("img")["src"]
+ expect(src).to be_blob_url(hero_blob)
+ end
+
+ banner_blob = participatory_process3.banner_image.blob
+ within %([data-active-uploads] [data-filename="#{banner_blob.filename}"]) do
+ src = page.find("img")["src"]
+ expect(src).to be_blob_url(banner_blob)
+ end
end
end
end
diff --git a/decidim-participatory_processes/spec/types/integration_schema_spec.rb b/decidim-participatory_processes/spec/types/integration_schema_spec.rb
index 16592513b5dc..7960800ca67a 100644
--- a/decidim-participatory_processes/spec/types/integration_schema_spec.rb
+++ b/decidim-participatory_processes/spec/types/integration_schema_spec.rb
@@ -159,7 +159,6 @@
"translation" => participatory_process.announcement[locale]
},
"attachments" => [],
- "bannerImage" => participatory_process.attached_uploader(:banner_image).path.sub(Rails.public_path.to_s, ""),
"categories" => [],
"components" => components,
"createdAt" => participatory_process.created_at.iso8601.to_s.gsub("Z", "+00:00"),
@@ -167,7 +166,6 @@
"developerGroup" => { "translation" => participatory_process.developer_group[locale] },
"endDate" => participatory_process.end_date.to_s,
"hashtag" => "",
- "heroImage" => participatory_process.attached_uploader(:hero_image).path.sub(Rails.public_path.to_s, ""),
"id" => participatory_process.id.to_s,
"linkedParticipatorySpaces" => [],
"localArea" => { "translation" => participatory_process.local_area[locale] },
@@ -203,10 +201,15 @@
describe "valid query" do
it "executes successfully" do
- expect { response }.not_to raise_error(StandardError)
+ expect { response }.not_to raise_error
end
- it { expect(response["participatoryProcess"]).to eq(participatory_process_response) }
+ it "returns the correct response" do
+ data = response["participatoryProcess"]
+ expect(data).to include(participatory_process_response)
+ expect(data["heroImage"]).to be_blob_url(participatory_process.hero_image.blob)
+ expect(data["bannerImage"]).to be_blob_url(participatory_process.banner_image.blob)
+ end
it_behaves_like "implements stats type" do
let(:participatory_process_query) do
diff --git a/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb b/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
index edebf3f4c80c..88645ddfdcbf 100644
--- a/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
+++ b/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
@@ -38,7 +38,7 @@ module ParticipatoryProcesses
let(:query) { "{ heroImage }" }
it "returns the hero image of the process" do
- expect(response["heroImage"]).to eq(model.attached_uploader(:hero_image).path)
+ expect(response["heroImage"]).to be_blob_url(model.hero_image.blob)
end
end
diff --git a/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb b/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
index c83ae3c278d7..8f07aee31837 100644
--- a/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
+++ b/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
@@ -115,7 +115,7 @@ module ParticipatoryProcesses
let(:query) { "{ bannerImage }" }
it "returns the banner image of the process" do
- expect(response["bannerImage"]).to eq(model.attached_uploader(:banner_image).path)
+ expect(response["bannerImage"]).to be_blob_url(model.banner_image.blob)
end
end
@@ -123,7 +123,7 @@ module ParticipatoryProcesses
let(:query) { "{ heroImage }" }
it "returns the hero image of the process" do
- expect(response["heroImage"]).to eq(model.attached_uploader(:hero_image).path)
+ expect(response["heroImage"]).to be_blob_url(model.hero_image.blob)
end
end
diff --git a/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb b/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
index 00d093807862..ca06e9ed8f9e 100644
--- a/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
+++ b/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
@@ -27,7 +27,7 @@ def cache_hash
hash << model.endorsements_count
hash << model.comments_count
hash << Digest::MD5.hexdigest(model.component.cache_key_with_version)
- hash << Digest::MD5.hexdigest(resource_image_path) if resource_image_path
+ hash << Digest::MD5.hexdigest(resource_image_url) if resource_image_url
hash << render_space? ? 1 : 0
if current_user
hash << current_user.cache_key_with_version
diff --git a/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb b/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
index ee86dd209801..3fcd3a4afe22 100644
--- a/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
+++ b/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
@@ -257,7 +257,8 @@
go_to_admin_proposal_page(proposal)
within "#photos" do
- expect(page).to have_xpath("//img[@src=\"#{image.thumbnail_url}\"]")
+ img = page.find("img")
+ expect(img["src"]).to be_blob_url(image.file.blob)
end
end
end
diff --git a/decidim-system/app/controllers/decidim/system/application_controller.rb b/decidim-system/app/controllers/decidim/system/application_controller.rb
index e9de268ecbae..1bfc0f1e9da8 100644
--- a/decidim-system/app/controllers/decidim/system/application_controller.rb
+++ b/decidim-system/app/controllers/decidim/system/application_controller.rb
@@ -8,6 +8,7 @@ class ApplicationController < ActionController::Base
include PayloadInfo
include Headers::HttpCachingDisabler
include DisableRedirectionToExternalHost
+ include ActiveStorage::SetCurrent
protect_from_forgery with: :exception, prepend: true
diff --git a/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb b/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
index 11ff5007504a..c8c33329a7ae 100644
--- a/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
+++ b/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
@@ -20,7 +20,7 @@
<% @oauth_applications.each do |oauth_application| %>
diff --git a/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb b/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
index 3c7fdc2f3a84..5bb35ea9f490 100644
--- a/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
+++ b/decidim-participatory_processes/lib/decidim/api/participatory_process_group_type.rb
@@ -14,7 +14,7 @@ class ParticipatoryProcessGroupType < Decidim::Api::Types::BaseObject
field :hero_image, GraphQL::Types::String, "The hero image for this participatory process group", null: true
def hero_image
- object.attached_uploader(:hero_image).path
+ object.attached_uploader(:hero_image).url
end
end
end
diff --git a/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb b/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
index db6f068652bb..00f31d79559a 100644
--- a/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
+++ b/decidim-participatory_processes/lib/decidim/api/participatory_process_type.rb
@@ -47,11 +47,11 @@ class ParticipatoryProcessType < Decidim::Api::Types::BaseObject
description: "The participatory process group in which this process belong to"
def banner_image
- object.attached_uploader(:banner_image).path
+ object.attached_uploader(:banner_image).url
end
def hero_image
- object.attached_uploader(:hero_image).path
+ object.attached_uploader(:hero_image).url
end
end
end
diff --git a/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb b/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
index b0baced444f4..fda26ea52aa5 100644
--- a/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
+++ b/decidim-participatory_processes/spec/cells/decidim/participatory_processes/participatory_process_g_cell_spec.rb
@@ -22,7 +22,8 @@ module Decidim::ParticipatoryProcesses
end
it "displays the process hero image" do
- expect(subject).to have_css("img[src='#{model.attached_uploader(:hero_image).path}']")
+ img = subject.find("img")
+ expect(img["src"]).to be_blob_url(model.hero_image.blob)
end
it_behaves_like "process card with metadata", metadata_class: "card__grid-metadata"
diff --git a/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb b/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
index aad80b079dbd..3582ecd1557f 100644
--- a/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
+++ b/decidim-participatory_processes/spec/presenters/decidim/participatory_processes/participatory_process_presenter_spec.rb
@@ -22,7 +22,7 @@ module Decidim
context "when image is attached" do
it "returns an URL including the organization domain" do
expect(subject.hero_image_url).to include(process.organization.host)
- expect(subject.hero_image_url).to include(process.attached_uploader(:hero_image).path)
+ expect(subject.hero_image_url).to be_blob_url(process.hero_image.blob)
end
end
end
@@ -41,7 +41,7 @@ module Decidim
context "when image is attached" do
it "returns an URL including the organization domain" do
expect(subject.banner_image_url).to include(process.organization.host)
- expect(subject.banner_image_url).to include(process.attached_uploader(:banner_image).path)
+ expect(subject.banner_image_url).to be_blob_url(process.banner_image.blob)
end
end
end
diff --git a/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb b/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
index f327aa53b896..ab7545c5c279 100644
--- a/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
+++ b/decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_serializer_spec.rb
@@ -14,29 +14,29 @@ module Decidim::ParticipatoryProcesses
expect(serialized).to be_a(Hash)
- expect(subject.serialize).to include(id: resource.id)
- expect(subject.serialize).to include(title: resource.title)
- expect(subject.serialize).to include(subtitle: resource.subtitle)
- expect(subject.serialize).to include(slug: resource.slug)
- expect(subject.serialize).to include(hashtag: resource.hashtag)
- expect(subject.serialize).to include(short_description: resource.short_description)
- expect(subject.serialize).to include(description: resource.description)
- expect(subject.serialize).to include(announcement: resource.announcement)
- expect(subject.serialize).to include(start_date: resource.start_date)
- expect(subject.serialize).to include(end_date: resource.end_date)
- expect(subject.serialize).to include(remote_hero_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessPresenter.new(resource).hero_image_url)
- expect(subject.serialize).to include(remote_banner_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessPresenter.new(resource).banner_image_url)
- expect(subject.serialize).to include(developer_group: resource.developer_group)
- expect(subject.serialize).to include(local_area: resource.local_area)
- expect(subject.serialize).to include(meta_scope: resource.meta_scope)
- expect(subject.serialize).to include(participatory_scope: resource.participatory_scope)
- expect(subject.serialize).to include(participatory_structure: resource.participatory_structure)
- expect(subject.serialize).to include(target: resource.target)
- expect(subject.serialize).to include(private_space: resource.private_space)
- expect(subject.serialize).to include(promoted: resource.promoted)
- expect(subject.serialize).to include(scopes_enabled: resource.scopes_enabled)
- expect(subject.serialize).to include(show_metrics: resource.show_metrics)
- expect(subject.serialize).to include(show_statistics: resource.show_statistics)
+ expect(serialized).to include(id: resource.id)
+ expect(serialized).to include(title: resource.title)
+ expect(serialized).to include(subtitle: resource.subtitle)
+ expect(serialized).to include(slug: resource.slug)
+ expect(serialized).to include(hashtag: resource.hashtag)
+ expect(serialized).to include(short_description: resource.short_description)
+ expect(serialized).to include(description: resource.description)
+ expect(serialized).to include(announcement: resource.announcement)
+ expect(serialized).to include(start_date: resource.start_date)
+ expect(serialized).to include(end_date: resource.end_date)
+ expect(serialized[:remote_hero_image_url]).to be_blob_url(resource.hero_image.blob)
+ expect(serialized[:remote_banner_image_url]).to be_blob_url(resource.banner_image.blob)
+ expect(serialized).to include(developer_group: resource.developer_group)
+ expect(serialized).to include(local_area: resource.local_area)
+ expect(serialized).to include(meta_scope: resource.meta_scope)
+ expect(serialized).to include(participatory_scope: resource.participatory_scope)
+ expect(serialized).to include(participatory_structure: resource.participatory_structure)
+ expect(serialized).to include(target: resource.target)
+ expect(serialized).to include(private_space: resource.private_space)
+ expect(serialized).to include(promoted: resource.promoted)
+ expect(serialized).to include(scopes_enabled: resource.scopes_enabled)
+ expect(serialized).to include(show_metrics: resource.show_metrics)
+ expect(serialized).to include(show_statistics: resource.show_statistics)
end
context "when process has area" do
@@ -109,7 +109,7 @@ module Decidim::ParticipatoryProcesses
expect(serialized_participatory_process_group).to include(id: resource.participatory_process_group.id)
expect(serialized_participatory_process_group).to include(title: resource.participatory_process_group.title)
expect(serialized_participatory_process_group).to include(description: resource.participatory_process_group.description)
- expect(serialized_participatory_process_group).to include(remote_hero_image_url: Decidim::ParticipatoryProcesses::ParticipatoryProcessGroupPresenter.new(resource.participatory_process_group).hero_image_url)
+ expect(serialized_participatory_process_group[:remote_hero_image_url]).to be_blob_url(resource.participatory_process_group.hero_image.blob)
end
end
@@ -180,7 +180,7 @@ module Decidim::ParticipatoryProcesses
expect(serialized_participatory_process_attachment).to include(title: attachment.title)
expect(serialized_participatory_process_attachment).to include(weight: attachment.weight)
expect(serialized_participatory_process_attachment).to include(description: attachment.description)
- expect(serialized_participatory_process_attachment).to include(remote_file_url: Decidim::AttachmentPresenter.new(resource.attachments.first).attachment_file_url)
+ expect(serialized_participatory_process_attachment[:remote_file_url]).to be_blob_url(resource.attachments.first.file.blob)
end
end
end
diff --git a/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb b/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
index 411c996ad5e4..2339a34fefd5 100644
--- a/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
+++ b/decidim-participatory_processes/spec/shared/invite_process_users_shared_context.rb
@@ -19,6 +19,9 @@ def invite_user
fill_in "Email", with: email
select role, from: "Role"
click_on "Create"
+ expect(page).to have_content("successfully added to this participatory process")
logout :user
+ visit decidim.root_path
+ expect(page).to have_content(organization.name)
end
end
diff --git a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
index 72f13d67ffe6..9b9bb3f5e509 100644
--- a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
+++ b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_processes_spec.rb
@@ -127,8 +127,18 @@
click_on "Update"
expect(page).to have_admin_callout("successfully")
- expect(page).to have_css("img[src*='#{participatory_process3.attached_uploader(:hero_image).path}']")
- expect(page).to have_css("img[src*='#{participatory_process3.attached_uploader(:banner_image).path}']")
+
+ hero_blob = participatory_process3.hero_image.blob
+ within %([data-active-uploads] [data-filename="#{hero_blob.filename}"]) do
+ src = page.find("img")["src"]
+ expect(src).to be_blob_url(hero_blob)
+ end
+
+ banner_blob = participatory_process3.banner_image.blob
+ within %([data-active-uploads] [data-filename="#{banner_blob.filename}"]) do
+ src = page.find("img")["src"]
+ expect(src).to be_blob_url(banner_blob)
+ end
end
end
end
diff --git a/decidim-participatory_processes/spec/types/integration_schema_spec.rb b/decidim-participatory_processes/spec/types/integration_schema_spec.rb
index 16592513b5dc..7960800ca67a 100644
--- a/decidim-participatory_processes/spec/types/integration_schema_spec.rb
+++ b/decidim-participatory_processes/spec/types/integration_schema_spec.rb
@@ -159,7 +159,6 @@
"translation" => participatory_process.announcement[locale]
},
"attachments" => [],
- "bannerImage" => participatory_process.attached_uploader(:banner_image).path.sub(Rails.public_path.to_s, ""),
"categories" => [],
"components" => components,
"createdAt" => participatory_process.created_at.iso8601.to_s.gsub("Z", "+00:00"),
@@ -167,7 +166,6 @@
"developerGroup" => { "translation" => participatory_process.developer_group[locale] },
"endDate" => participatory_process.end_date.to_s,
"hashtag" => "",
- "heroImage" => participatory_process.attached_uploader(:hero_image).path.sub(Rails.public_path.to_s, ""),
"id" => participatory_process.id.to_s,
"linkedParticipatorySpaces" => [],
"localArea" => { "translation" => participatory_process.local_area[locale] },
@@ -203,10 +201,15 @@
describe "valid query" do
it "executes successfully" do
- expect { response }.not_to raise_error(StandardError)
+ expect { response }.not_to raise_error
end
- it { expect(response["participatoryProcess"]).to eq(participatory_process_response) }
+ it "returns the correct response" do
+ data = response["participatoryProcess"]
+ expect(data).to include(participatory_process_response)
+ expect(data["heroImage"]).to be_blob_url(participatory_process.hero_image.blob)
+ expect(data["bannerImage"]).to be_blob_url(participatory_process.banner_image.blob)
+ end
it_behaves_like "implements stats type" do
let(:participatory_process_query) do
diff --git a/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb b/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
index edebf3f4c80c..88645ddfdcbf 100644
--- a/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
+++ b/decidim-participatory_processes/spec/types/participatory_process_group_type_spec.rb
@@ -38,7 +38,7 @@ module ParticipatoryProcesses
let(:query) { "{ heroImage }" }
it "returns the hero image of the process" do
- expect(response["heroImage"]).to eq(model.attached_uploader(:hero_image).path)
+ expect(response["heroImage"]).to be_blob_url(model.hero_image.blob)
end
end
diff --git a/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb b/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
index c83ae3c278d7..8f07aee31837 100644
--- a/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
+++ b/decidim-participatory_processes/spec/types/participatory_process_type_spec.rb
@@ -115,7 +115,7 @@ module ParticipatoryProcesses
let(:query) { "{ bannerImage }" }
it "returns the banner image of the process" do
- expect(response["bannerImage"]).to eq(model.attached_uploader(:banner_image).path)
+ expect(response["bannerImage"]).to be_blob_url(model.banner_image.blob)
end
end
@@ -123,7 +123,7 @@ module ParticipatoryProcesses
let(:query) { "{ heroImage }" }
it "returns the hero image of the process" do
- expect(response["heroImage"]).to eq(model.attached_uploader(:hero_image).path)
+ expect(response["heroImage"]).to be_blob_url(model.hero_image.blob)
end
end
diff --git a/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb b/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
index 00d093807862..ca06e9ed8f9e 100644
--- a/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
+++ b/decidim-proposals/app/cells/decidim/proposals/proposal_l_cell.rb
@@ -27,7 +27,7 @@ def cache_hash
hash << model.endorsements_count
hash << model.comments_count
hash << Digest::MD5.hexdigest(model.component.cache_key_with_version)
- hash << Digest::MD5.hexdigest(resource_image_path) if resource_image_path
+ hash << Digest::MD5.hexdigest(resource_image_url) if resource_image_url
hash << render_space? ? 1 : 0
if current_user
hash << current_user.cache_key_with_version
diff --git a/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb b/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
index ee86dd209801..3fcd3a4afe22 100644
--- a/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
+++ b/decidim-proposals/spec/system/admin/view_proposal_details_from_admin_spec.rb
@@ -257,7 +257,8 @@
go_to_admin_proposal_page(proposal)
within "#photos" do
- expect(page).to have_xpath("//img[@src=\"#{image.thumbnail_url}\"]")
+ img = page.find("img")
+ expect(img["src"]).to be_blob_url(image.file.blob)
end
end
end
diff --git a/decidim-system/app/controllers/decidim/system/application_controller.rb b/decidim-system/app/controllers/decidim/system/application_controller.rb
index e9de268ecbae..1bfc0f1e9da8 100644
--- a/decidim-system/app/controllers/decidim/system/application_controller.rb
+++ b/decidim-system/app/controllers/decidim/system/application_controller.rb
@@ -8,6 +8,7 @@ class ApplicationController < ActionController::Base
include PayloadInfo
include Headers::HttpCachingDisabler
include DisableRedirectionToExternalHost
+ include ActiveStorage::SetCurrent
protect_from_forgery with: :exception, prepend: true
diff --git a/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb b/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
index 11ff5007504a..c8c33329a7ae 100644
--- a/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
+++ b/decidim-system/app/views/decidim/system/oauth_applications/index.html.erb
@@ -20,7 +20,7 @@
<% @oauth_applications.each do |oauth_application| %>
- <%= image_tag oauth_application.attached_uploader(:organization_logo).path, class: "max-w-md" %>
+ <%= image_tag oauth_application.attached_uploader(:organization_logo).url, class: "max-w-md" %>
<%= link_to oauth_application.name, oauth_application %>
diff --git a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb
index 45e39771848d..a3f2cccd6979 100644
--- a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb
+++ b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb
@@ -17,7 +17,7 @@
- <%= image_tag @pending_authorization.attached_uploader(:verification_attachment).variant_path(:big), class: "thumbnail" %>
+ <%= image_tag @pending_authorization.attached_uploader(:verification_attachment).variant_url(:big), class: "thumbnail" %>
diff --git a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
index 98841178a1c3..4249ddd24747 100644
--- a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
+++ b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
@@ -24,7 +24,7 @@
<%= link_to new_pending_authorization_confirmation_path(authorization.id) do %>
- <%= image_tag authorization.attached_uploader(:verification_attachment).variant_path(:thumbnail), class: "thumbnail-list" %>
+ <%= image_tag authorization.attached_uploader(:verification_attachment).variant_url(:thumbnail), class: "thumbnail-list" %>
<% end %>
diff --git a/decidim-verifications/spec/system/id_documents/id_document_online_review_spec.rb b/decidim-verifications/spec/system/id_documents/id_document_online_review_spec.rb
index ce7ae2556d4b..1c8a8dc81fb5 100644
--- a/decidim-verifications/spec/system/id_documents/id_document_online_review_spec.rb
+++ b/decidim-verifications/spec/system/id_documents/id_document_online_review_spec.rb
@@ -58,6 +58,7 @@
context "and the user logs back in" do
before do
+ expect(page).to have_content("Verification rejected. Participant will be prompted to amend their documents")
relogin_as user, scope: :user
visit decidim_verifications.authorizations_path
click_on "Identity documents"
diff --git a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb index 45e39771848d..a3f2cccd6979 100644 --- a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb +++ b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb @@ -17,7 +17,7 @@
- <%= image_tag @pending_authorization.attached_uploader(:verification_attachment).variant_path(:big), class: "thumbnail" %>
+ <%= image_tag @pending_authorization.attached_uploader(:verification_attachment).variant_url(:big), class: "thumbnail" %>
diff --git a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
index 98841178a1c3..4249ddd24747 100644
--- a/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
+++ b/decidim-verifications/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb
@@ -24,7 +24,7 @@