diff --git a/app/components/attachable/fields_component.html.erb b/app/components/attachable/fields_component.html.erb
new file mode 100644
index 000000000..ef329a31e
--- /dev/null
+++ b/app/components/attachable/fields_component.html.erb
@@ -0,0 +1,31 @@
+
+ <%= f.hidden_field :id %>
+ <%= f.hidden_field :user_id, value: current_user.id %>
+ <%= f.hidden_field :cached_attachment %>
+
+
+ <%= f.text_field :title, placeholder: t("#{plural_name}.form.title_placeholder") %>
+
+
+ <% if attachable.attachment.exists? && attachable.attachment.styles.keys.include?(:thumb) %>
+ <%= render_image(attachable, :thumb, false) %>
+ <% end %>
+
+
+
<%= file_name %>
+
+
+ <%= file_field %>
+
+
+
+ <%= destroy_link %>
+
+
+
+
+
+
+
diff --git a/app/components/attachable/fields_component.rb b/app/components/attachable/fields_component.rb
new file mode 100644
index 000000000..ae217456a
--- /dev/null
+++ b/app/components/attachable/fields_component.rb
@@ -0,0 +1,70 @@
+class Attachable::FieldsComponent < ApplicationComponent
+ attr_reader :f, :resource_type, :resource_id, :relation_name
+ delegate :current_user, :render_image, to: :helpers
+
+ def initialize(f, resource_type:, resource_id:, relation_name:)
+ @f = f
+ @resource_type = resource_type
+ @resource_id = resource_id
+ @relation_name = relation_name
+ end
+
+ private
+
+ def attachable
+ f.object
+ end
+
+ def singular_name
+ attachable.model_name.singular
+ end
+
+ def plural_name
+ attachable.model_name.plural
+ end
+
+ def file_name
+ attachable.attachment_file_name
+ end
+
+ def destroy_link
+ if !attachable.persisted? && attachable.cached_attachment.present?
+ link_to t("#{plural_name}.form.delete_button"), "#", class: "delete remove-cached-attachment"
+ else
+ link_to_remove_association remove_association_text, f, class: "delete remove-#{singular_name}"
+ end
+ end
+
+ def remove_association_text
+ if attachable.new_record?
+ t("documents.form.cancel_button")
+ else
+ t("#{plural_name}.form.delete_button")
+ end
+ end
+
+ def file_field
+ klass = attachable.persisted? || attachable.cached_attachment.present? ? " hide" : ""
+ f.file_field :attachment,
+ label_options: { class: "button hollow #{klass}" },
+ accept: accepted_content_types_extensions,
+ class: "js-#{singular_name}-attachment",
+ data: { url: direct_upload_path }
+ end
+
+ def direct_upload_path
+ direct_uploads_path("direct_upload[resource_type]": resource_type,
+ "direct_upload[resource_id]": resource_id,
+ "direct_upload[resource_relation]": relation_name)
+ end
+
+ def accepted_content_types_extensions
+ Setting.accepted_content_types_for(plural_name).map do |content_type|
+ if content_type == "jpg"
+ ".jpg,.jpeg"
+ else
+ ".#{content_type}"
+ end
+ end.join(",")
+ end
+end
diff --git a/app/components/documents/fields_component.html.erb b/app/components/documents/fields_component.html.erb
index 5822f89ba..4520d7527 100644
--- a/app/components/documents/fields_component.html.erb
+++ b/app/components/documents/fields_component.html.erb
@@ -1,28 +1,6 @@
-
- <%= f.hidden_field :id %>
- <%= f.hidden_field :user_id, value: current_user.id %>
- <%= f.hidden_field :cached_attachment %>
-
-
- <%= f.text_field :title, placeholder: t("documents.form.title_placeholder") %>
-
-
-
-
<%= file_name %>
-
-
- <%= file_field %>
-
-
-
- <%= destroy_link %>
-
-
-
-
-
-
-
-
+<%= render Attachable::FieldsComponent.new(
+ f,
+ resource_type: document.documentable_type,
+ resource_id: document.documentable_id,
+ relation_name: "documents"
+) %>
diff --git a/app/components/documents/fields_component.rb b/app/components/documents/fields_component.rb
index 1ef9febea..01da058c0 100644
--- a/app/components/documents/fields_component.rb
+++ b/app/components/documents/fields_component.rb
@@ -1,6 +1,5 @@
class Documents::FieldsComponent < ApplicationComponent
attr_reader :f
- delegate :current_user, to: :helpers
def initialize(f)
@f = f
@@ -11,35 +10,4 @@ class Documents::FieldsComponent < ApplicationComponent
def document
f.object
end
-
- def file_name
- document.attachment_file_name
- end
-
- def destroy_link
- if !document.persisted? && document.cached_attachment.present?
- link_to t("documents.form.delete_button"), "#", class: "delete remove-cached-attachment"
- else
- link_to_remove_association document.new_record? ? t("documents.form.cancel_button") : t("documents.form.delete_button"), f, class: "delete remove-document"
- end
- end
-
- def file_field
- klass = document.persisted? || document.cached_attachment.present? ? " hide" : ""
- f.file_field :attachment,
- label_options: { class: "button hollow #{klass}" },
- accept: accepted_content_types_extensions,
- class: "js-document-attachment",
- data: { url: direct_upload_path }
- end
-
- def direct_upload_path
- direct_uploads_path("direct_upload[resource_type]": document.documentable_type,
- "direct_upload[resource_id]": document.documentable_id,
- "direct_upload[resource_relation]": "documents")
- end
-
- def accepted_content_types_extensions
- Setting.accepted_content_types_for("documents").map { |content_type| ".#{content_type}" }.join(",")
- end
end
diff --git a/app/components/documents/nested_component.rb b/app/components/documents/nested_component.rb
index a37587e81..2b4f6f909 100644
--- a/app/components/documents/nested_component.rb
+++ b/app/components/documents/nested_component.rb
@@ -1,6 +1,5 @@
class Documents::NestedComponent < ApplicationComponent
attr_reader :f
- delegate :documentable_humanized_accepted_content_types, :max_file_size, to: :helpers
def initialize(f)
@f = f
@@ -18,8 +17,8 @@ class Documents::NestedComponent < ApplicationComponent
def note
t "documents.form.note", max_documents_allowed: max_documents_allowed,
- accepted_content_types: documentable_humanized_accepted_content_types(documentable.class),
- max_file_size: max_file_size(documentable.class)
+ accepted_content_types: Document.humanized_accepted_content_types,
+ max_file_size: documentable.class.max_file_size
end
def max_documents_allowed?
diff --git a/app/components/images/fields_component.html.erb b/app/components/images/fields_component.html.erb
index 49d8f1fd8..4a2df33af 100644
--- a/app/components/images/fields_component.html.erb
+++ b/app/components/images/fields_component.html.erb
@@ -1,29 +1,6 @@
-
- <%= f.hidden_field :id %>
- <%= f.hidden_field :user_id, value: current_user.id %>
- <%= f.hidden_field :cached_attachment %>
-
-
- <%= f.text_field :title, placeholder: t("images.form.title_placeholder") %>
-
-
- <%= render_image(image, :thumb, false) if image.attachment.exists? %>
-
-
-
<%= file_name %>
-
-
- <%= file_field %>
-
-
-
- <%= destroy_link %>
-
-
-
-
-
-
-
+<%= render Attachable::FieldsComponent.new(
+ f,
+ resource_type: imageable.class.name,
+ resource_id: imageable.id,
+ relation_name: "image"
+) %>
diff --git a/app/components/images/fields_component.rb b/app/components/images/fields_component.rb
index ddf60fa45..44c3ad0a1 100644
--- a/app/components/images/fields_component.rb
+++ b/app/components/images/fields_component.rb
@@ -1,52 +1,8 @@
class Images::FieldsComponent < ApplicationComponent
attr_reader :f, :imageable
- delegate :current_user, :render_image, to: :helpers
def initialize(f, imageable:)
@f = f
@imageable = imageable
end
-
- private
-
- def image
- f.object
- end
-
- def file_name
- image.attachment_file_name
- end
-
- def destroy_link
- if !image.persisted? && image.cached_attachment.present?
- link_to t("images.form.delete_button"), "#", class: "delete remove-cached-attachment"
- else
- link_to_remove_association t("images.form.delete_button"), f, class: "delete remove-image"
- end
- end
-
- def file_field
- klass = image.persisted? || image.cached_attachment.present? ? " hide" : ""
- f.file_field :attachment,
- label_options: { class: "button hollow #{klass}" },
- accept: accepted_content_types_extensions,
- class: "js-image-attachment",
- data: { url: direct_upload_path }
- end
-
- def direct_upload_path
- direct_uploads_path("direct_upload[resource_type]": imageable.class.name,
- "direct_upload[resource_id]": imageable.id,
- "direct_upload[resource_relation]": "image")
- end
-
- def accepted_content_types_extensions
- Setting.accepted_content_types_for("images").map do |content_type|
- if content_type == "jpg"
- ".jpg,.jpeg"
- else
- ".#{content_type}"
- end
- end.join(",")
- end
end
diff --git a/app/components/images/nested_component.rb b/app/components/images/nested_component.rb
index 61087bcff..768f4d880 100644
--- a/app/components/images/nested_component.rb
+++ b/app/components/images/nested_component.rb
@@ -1,6 +1,5 @@
class Images::NestedComponent < ApplicationComponent
attr_reader :f, :image_fields
- delegate :imageable_humanized_accepted_content_types, :imageable_max_file_size, to: :helpers
def initialize(f, image_fields: :image)
@f = f
@@ -14,7 +13,7 @@ class Images::NestedComponent < ApplicationComponent
end
def note
- t "images.form.note", accepted_content_types: imageable_humanized_accepted_content_types,
- max_file_size: imageable_max_file_size
+ t "images.form.note", accepted_content_types: Image.humanized_accepted_content_types,
+ max_file_size: Image.max_file_size
end
end
diff --git a/app/helpers/documentables_helper.rb b/app/helpers/documentables_helper.rb
deleted file mode 100644
index 5dcffacb3..000000000
--- a/app/helpers/documentables_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module DocumentablesHelper
- def max_file_size(documentable_class)
- documentable_class.max_file_size / Numeric::MEGABYTE
- end
-
- def accepted_content_types(documentable_class)
- documentable_class.accepted_content_types
- end
-
- def documentable_humanized_accepted_content_types(documentable_class)
- Setting.accepted_content_types_for("documents").join(", ")
- end
-end
diff --git a/app/helpers/imageables_helper.rb b/app/helpers/imageables_helper.rb
index c0e4c4893..930342025 100644
--- a/app/helpers/imageables_helper.rb
+++ b/app/helpers/imageables_helper.rb
@@ -2,16 +2,4 @@ module ImageablesHelper
def can_destroy_image?(imageable)
imageable.image.present? && can?(:destroy, imageable.image)
end
-
- def imageable_max_file_size
- Setting["uploads.images.max_size"].to_i
- end
-
- def imageable_accepted_content_types
- Setting["uploads.images.content_types"]&.split(" ") || ["image/jpeg"]
- end
-
- def imageable_humanized_accepted_content_types
- Setting.accepted_content_types_for("images").join(", ")
- end
end
diff --git a/app/models/concerns/attachable.rb b/app/models/concerns/attachable.rb
new file mode 100644
index 000000000..fbc29a055
--- /dev/null
+++ b/app/models/concerns/attachable.rb
@@ -0,0 +1,75 @@
+module Attachable
+ extend ActiveSupport::Concern
+
+ included do
+ attr_accessor :cached_attachment
+
+ # Disable paperclip security validation due to polymorphic configuration
+ # Paperclip do not allow to use Procs on valiations definition
+ do_not_validate_attachment_file_type :attachment
+ validate :attachment_presence
+ validate :validate_attachment_content_type, if: -> { attachment.present? }
+ validate :validate_attachment_size, if: -> { attachment.present? }
+
+ before_save :set_attachment_from_cached_attachment, if: -> { cached_attachment.present? }
+
+ Paperclip.interpolates :prefix do |attachment, style|
+ attachment.instance.prefix(attachment, style)
+ end
+ end
+
+ def association_class
+ type = send("#{association_name}_type")
+
+ type.constantize if type.present?
+ end
+
+ def set_cached_attachment_from_attachment
+ self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
+ attachment.path
+ else
+ attachment.url
+ end
+ end
+
+ def set_attachment_from_cached_attachment
+ self.attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
+ File.open(cached_attachment)
+ else
+ URI.parse(cached_attachment)
+ end
+ end
+
+ def prefix(attachment, _style)
+ if attachment.instance.persisted?
+ ":attachment/:id_partition"
+ else
+ "cached_attachments/user/#{attachment.instance.user_id}"
+ end
+ end
+
+ private
+
+ def validate_attachment_size
+ if association_class && attachment_file_size > max_file_size.megabytes
+ errors.add(:attachment, I18n.t("#{model_name.plural}.errors.messages.in_between",
+ min: "0 Bytes",
+ max: "#{max_file_size} MB"))
+ end
+ end
+
+ def validate_attachment_content_type
+ if association_class && !accepted_content_types.include?(attachment_content_type)
+ message = I18n.t("#{model_name.plural}.errors.messages.wrong_content_type",
+ content_type: attachment_content_type,
+ accepted_content_types: self.class.humanized_accepted_content_types)
+ errors.add(:attachment, message)
+ end
+ end
+
+ def attachment_presence
+ if attachment.blank? && cached_attachment.blank?
+ errors.add(:attachment, I18n.t("errors.messages.blank"))
+ end
+ end
+end
diff --git a/app/models/concerns/documentable.rb b/app/models/concerns/documentable.rb
index dcff3461d..dd4aa531a 100644
--- a/app/models/concerns/documentable.rb
+++ b/app/models/concerns/documentable.rb
@@ -12,7 +12,7 @@ module Documentable
end
def max_file_size
- Setting["uploads.documents.max_size"].to_i.megabytes
+ Setting["uploads.documents.max_size"].to_i
end
def accepted_content_types
diff --git a/app/models/document.rb b/app/models/document.rb
index da9bbfd08..cc24ef871 100644
--- a/app/models/document.rb
+++ b/app/models/document.rb
@@ -1,60 +1,27 @@
class Document < ApplicationRecord
- include DocumentsHelper
- include DocumentablesHelper
+ include Attachable
+
has_attached_file :attachment, url: "/system/:class/:prefix/:style/:hash.:extension",
hash_data: ":class/:style/:custom_hash_data",
use_timestamp: false,
hash_secret: Rails.application.secrets.secret_key_base
- attr_accessor :cached_attachment
belongs_to :user
belongs_to :documentable, polymorphic: true
- # Disable paperclip security validation due to polymorphic configuration
- # Paperclip do not allow to use Procs on valiations definition
- do_not_validate_attachment_file_type :attachment
- validate :attachment_presence
- validate :validate_attachment_content_type, if: -> { attachment.present? }
- validate :validate_attachment_size, if: -> { attachment.present? }
validates :title, presence: true
validates :user_id, presence: true
validates :documentable_id, presence: true, if: -> { persisted? }
validates :documentable_type, presence: true, if: -> { persisted? }
- before_save :set_attachment_from_cached_attachment, if: -> { cached_attachment.present? }
-
scope :admin, -> { where(admin: true) }
- def set_cached_attachment_from_attachment
- self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
- attachment.path
- else
- attachment.url
- end
- end
-
- def set_attachment_from_cached_attachment
- self.attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
- File.open(cached_attachment)
- else
- URI.parse(cached_attachment)
- end
- end
-
- Paperclip.interpolates :prefix do |attachment, style|
- attachment.instance.prefix(attachment, style)
- end
-
Paperclip.interpolates :custom_hash_data do |attachment, _style|
attachment.instance.custom_hash_data(attachment)
end
- def prefix(attachment, _style)
- if attachment.instance.persisted?
- ":attachment/:id_partition"
- else
- "cached_attachments/user/#{attachment.instance.user_id}"
- end
+ def self.humanized_accepted_content_types
+ Setting.accepted_content_types_for("documents").join(", ")
end
def custom_hash_data(attachment)
@@ -70,35 +37,21 @@ class Document < ApplicationRecord
attachment_content_type.split("/").last.upcase
end
+ def max_file_size
+ documentable_class.max_file_size
+ end
+
+ def accepted_content_types
+ documentable_class.accepted_content_types
+ end
+
private
+ def association_name
+ :documentable
+ end
+
def documentable_class
- documentable_type.constantize if documentable_type.present?
- end
-
- def validate_attachment_size
- if documentable_class.present? &&
- attachment_file_size > documentable_class.max_file_size
- errors.add(:attachment, I18n.t("documents.errors.messages.in_between",
- min: "0 Bytes",
- max: "#{max_file_size(documentable_class)} MB"))
- end
- end
-
- def validate_attachment_content_type
- if documentable_class &&
- !accepted_content_types(documentable_class).include?(attachment_content_type)
- accepted_content_types = documentable_humanized_accepted_content_types(documentable_class)
- message = I18n.t("documents.errors.messages.wrong_content_type",
- content_type: attachment_content_type,
- accepted_content_types: accepted_content_types)
- errors.add(:attachment, message)
- end
- end
-
- def attachment_presence
- if attachment.blank? && cached_attachment.blank?
- errors.add(:attachment, I18n.t("errors.messages.blank"))
- end
+ association_class
end
end
diff --git a/app/models/image.rb b/app/models/image.rb
index 961307b23..19825d629 100644
--- a/app/models/image.rb
+++ b/app/models/image.rb
@@ -1,6 +1,5 @@
class Image < ApplicationRecord
- include ImagesHelper
- include ImageablesHelper
+ include Attachable
has_attached_file :attachment, styles: {
large: "x#{Setting["uploads.images.min_height"]}",
@@ -11,17 +10,10 @@ class Image < ApplicationRecord
hash_data: ":class/:style",
use_timestamp: false,
hash_secret: Rails.application.secrets.secret_key_base
- attr_accessor :cached_attachment
belongs_to :user
belongs_to :imageable, polymorphic: true
- # Disable paperclip security validation due to polymorphic configuration
- # Paperclip do not allow to use Procs on valiations definition
- do_not_validate_attachment_file_type :attachment
- validate :attachment_presence
- validate :validate_attachment_content_type, if: -> { attachment.present? }
- validate :validate_attachment_size, if: -> { attachment.present? }
validates :title, presence: true
validate :validate_title_length
validates :user_id, presence: true
@@ -29,40 +21,34 @@ class Image < ApplicationRecord
validates :imageable_type, presence: true, if: -> { persisted? }
validate :validate_image_dimensions, if: -> { attachment.present? && attachment.dirty? }
- before_save :set_attachment_from_cached_attachment, if: -> { cached_attachment.present? }
-
- def set_cached_attachment_from_attachment
- self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
- attachment.path
- else
- attachment.url
- end
+ def self.max_file_size
+ Setting["uploads.images.max_size"].to_i
end
- def set_attachment_from_cached_attachment
- self.attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
- File.open(cached_attachment)
- else
- URI.parse(cached_attachment)
- end
+ def self.accepted_content_types
+ Setting["uploads.images.content_types"]&.split(" ") || ["image/jpeg"]
end
- Paperclip.interpolates :prefix do |attachment, style|
- attachment.instance.prefix(attachment, style)
+ def self.humanized_accepted_content_types
+ Setting.accepted_content_types_for("images").join(", ")
end
- def prefix(attachment, _style)
- if attachment.instance.persisted?
- ":attachment/:id_partition"
- else
- "cached_attachments/user/#{attachment.instance.user_id}"
- end
+ def max_file_size
+ self.class.max_file_size
+ end
+
+ def accepted_content_types
+ self.class.accepted_content_types
end
private
+ def association_name
+ :imageable
+ end
+
def imageable_class
- imageable_type.constantize if imageable_type.present?
+ association_class
end
def validate_image_dimensions
@@ -77,15 +63,6 @@ class Image < ApplicationRecord
end
end
- def validate_attachment_size
- if imageable_class &&
- attachment_file_size > Setting["uploads.images.max_size"].to_i.megabytes
- errors.add(:attachment, I18n.t("images.errors.messages.in_between",
- min: "0 Bytes",
- max: "#{imageable_max_file_size} MB"))
- end
- end
-
def validate_title_length
if title.present?
title_min_length = Setting["uploads.images.title.min_length"].to_i
@@ -101,22 +78,7 @@ class Image < ApplicationRecord
end
end
- def validate_attachment_content_type
- if imageable_class && !attachment_of_valid_content_type?
- message = I18n.t("images.errors.messages.wrong_content_type",
- content_type: attachment_content_type,
- accepted_content_types: imageable_humanized_accepted_content_types)
- errors.add(:attachment, message)
- end
- end
-
- def attachment_presence
- if attachment.blank? && cached_attachment.blank?
- errors.add(:attachment, I18n.t("errors.messages.blank"))
- end
- end
-
def attachment_of_valid_content_type?
- attachment.present? && imageable_accepted_content_types.include?(attachment_content_type)
+ attachment.present? && accepted_content_types.include?(attachment_content_type)
end
end
diff --git a/spec/shared/models/document_validations.rb b/spec/shared/models/document_validations.rb
index 57daf7cd1..194c8fbba 100644
--- a/spec/shared/models/document_validations.rb
+++ b/spec/shared/models/document_validations.rb
@@ -1,9 +1,7 @@
shared_examples "document validations" do |documentable_factory|
- include DocumentablesHelper
-
let!(:document) { build(:document, documentable_factory.to_sym) }
- let!(:maxfilesize) { max_file_size(document.documentable.class) }
- let!(:acceptedcontenttypes) { accepted_content_types(document.documentable.class) }
+ let!(:maxfilesize) { document.max_file_size }
+ let!(:acceptedcontenttypes) { document.accepted_content_types }
it "is valid" do
expect(document).to be_valid
diff --git a/spec/shared/models/image_validations.rb b/spec/shared/models/image_validations.rb
index 27cc26da8..092372550 100644
--- a/spec/shared/models/image_validations.rb
+++ b/spec/shared/models/image_validations.rb
@@ -1,8 +1,6 @@
shared_examples "image validations" do |imageable_factory|
- include ImageablesHelper
-
let!(:image) { build(:image, imageable_factory.to_sym) }
- let!(:acceptedcontenttypes) { imageable_accepted_content_types }
+ let!(:acceptedcontenttypes) { Image.accepted_content_types }
it "is valid" do
expect(image).to be_valid