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

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

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

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
@fydelio

This comment has been minimized.

Copy link

commented Mar 26, 2019

Where (directory structure) in rails would I store this validator?

@thelucid

This comment has been minimized.

Copy link

commented May 7, 2019

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

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.