Skip to content

Instantly share code, notes, and snippets.

@carlosramireziii
Last active September 22, 2023 21:39
Show Gist options
  • Save carlosramireziii/6d0ca6b414d8a6af08371c30ba4dedcd to your computer and use it in GitHub Desktop.
Save carlosramireziii/6d0ca6b414d8a6af08371c30ba4dedcd to your computer and use it in GitHub Desktop.
A validator and RSpec matcher for restricting an attachment’s content type using Active Storage
require "rspec/expectations"
RSpec::Matchers.define :allow_content_type do |*content_types|
match do |record|
matcher.matches?(record, content_types)
end
chain :for do |attr_name|
matcher.for(attr_name)
end
chain :with_message do |message|
matcher.with_message(message)
end
private
def matcher
@matcher ||= AllowContentTypeMatcher.new
end
class AllowContentTypeMatcher
def for(attr_name)
@attr_name = attr_name
end
def with_message(message)
@message = message
end
def matches?(record, content_types)
Array.wrap(content_types).all? do |content_type|
record.send(attr_name).attach attachment_for(content_type)
record.valid?
!record.errors[attr_name].include? message
end
end
private
attr_reader :attr_name
def attachment_for(content_type)
suffix = content_type.to_s.split("/").last
{ io: StringIO.new("Hello world!"), filename: "test.#{suffix}", content_type: content_type }
end
def message
@message || I18n.translate("activerecord.errors.messages.content_type")
end
end
end
RSpec::Matchers.alias_matcher :allow_content_types, :allow_content_type
class ContentTypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.attached? && value.content_type.in?(content_types)
value.purge if record.new_record? # Only purge the offending blob if the record is new
record.errors.add(attribute, :content_type, options)
end
end
private
def content_types
options.fetch(:in)
end
end
en:
activerecord:
errors:
messages:
content_type: is not a valid file format
@douglaspetrin
Copy link

douglaspetrin commented Sep 22, 2023

I get FrozenError: can't modify frozen Hash. Not sure where it's coming from.

@thelucid Yes, I got a similar error with the @meara approach. can't modify frozen attributes - Were you able to fix it? I added the check back and it worked as well:

def validate_each(record, attribute, value)

    return unless value.attached?
    return if value.content_type.in?(content_types)

    value.purge if record.new_record?
    record.errors.add(attribute, :content_type, **options)
  end

@carlosramireziii Thank you, it works nicely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment