Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
@murdoch

This comment has been minimized.

Copy link

murdoch commented Oct 6, 2018

Nice! How come you only purge the photo if it's a new record?

@efojs

This comment has been minimized.

Copy link

efojs commented Dec 28, 2018

Hi! Thank you!
I don't understand much in Rails, so please excuse me if the question is lame.
I watch table — active_storage_blobs — and records are not deleted (active_storage_attachments is OK).
Am I doing something wrong?

@meara

This comment has been minimized.

Copy link

meara commented Feb 22, 2019

This is great, thank you!

I modified this slightly for my use case such that it will allow the attachment to be nil.

class ContentTypeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return unless value.attached?
    return if value.content_type.in?(content_types)

    value.purge
    record.errors.add(attribute, :content_type, options)
  end

  private

  def content_types
    options.fetch(:in)
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.