{"record":{"id":"82d5fd17ecf4cc76","repo":"instructure/canvas-lms","slug":"thumbnails-option-should-be-a-hash-e-g-thumbnails-foo-50x50","errorCode":null,"errorMessage":":thumbnails option should be a hash: e.g. :thumbnails => { :foo => '50x50' }","messagePattern":":thumbnails option should be a hash: e\\.g\\. :thumbnails => (.+?)","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"gems/attachment_fu/lib/attachment_fu.rb","lineNumber":107,"sourceCode":"    #   has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }\n    #   has_attachment :storage => :file_system, :path_prefix => 'public/files'\n    #   has_attachment :storage => :file_system, :path_prefix => 'public/files',\n    #     :content_type => :image, :resize_to => [50,50]\n    #   has_attachment :storage => :file_system, :path_prefix => 'public/files',\n    #     :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }\n    #   has_attachment :storage => :s3\n    def has_attachment(options = {})\n      # this allows you to redefine the acts' options for each subclass, however\n      options[:min_size]         ||= 1\n      options[:max_size]         ||= 1.megabyte\n      options[:size]             ||= (options[:min_size]..options[:max_size])\n      options[:thumbnails]       ||= {}\n      options[:thumbnail_class]  ||= self\n      options[:s3_access]        ||= \"public-read\"\n      options[:content_type] = [options[:content_type]].flatten.collect! { |t| (t == :image) ? AttachmentFu.content_types : t }.flatten unless options[:content_type].nil?\n\n      unless options[:thumbnails].is_a?(Hash)\n        raise ArgumentError, \":thumbnails option should be a hash: e.g. :thumbnails => { :foo => '50x50' }\"\n      end\n\n      extend ClassMethods unless (class << self; included_modules; end).include?(ClassMethods)\n      include InstanceMethods unless include?(InstanceMethods)\n\n      parent_options = attachment_options || {}\n      # doing these shenanigans so that #attachment_options is available to processors and backends\n      self.attachment_options = options\n\n      attr_accessor :thumbnail_resize_options\n\n      attachment_options[:storage]     ||= (attachment_options[:file_system_path] || attachment_options[:path_prefix]) ? :file_system : :db_file\n      attachment_options[:storage]     ||= parent_options[:storage]\n      attachment_options[:path_prefix] ||= attachment_options[:file_system_path]\n      if attachment_options[:path_prefix].nil?\n        attachment_options[:path_prefix] = (attachment_options[:storage] == :s3) ? table_name : File.join(\"public\", table_name)\n      end\n      attachment_options[:path_prefix] = attachment_options[:path_prefix][1..] if options[:path_prefix].first == \"/\"","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/gems/attachment_fu/lib/attachment_fu.rb#L89-L125","documentation":"attachment_fu's has_attachment validates its options: the :thumbnails option must be a Hash mapping thumbnail names to size strings (e.g. { :thumb => '50x50' }). Passing a non-Hash (string, array, nil handled separately by ||={}) raises ArgumentError at class-definition time, so the model fails to load.","triggerScenarios":"Declaring `has_attachment :thumbnails => '100x100'` (a String), `:thumbnails => [:thumb]` (an Array), or building the value dynamically into something that isn't a Hash in a Thumbnail/attachment model.","commonSituations":"Copy-paste from docs where the braces were lost; config stored in YAML/DB returning a string; refactoring that swapped the hash for a list of names; typos like :thumbnail (singular) leaking into :thumbnails.","solutions":["Wrap the value in a hash: :thumbnails => { :thumb => '100x100' }","If sizes come from config, coerce: Array(cfg).to_h or JSON parse then verify is_a?(Hash)","Use :thumbnail_class / single-thumbnail options if you only need one thumbnail and no hash","Freeze/validate the constant feeding :thumbnails in an initializer test so model load fails fast in CI"],"exampleFix":"// before\nhas_attachment :thumbnails => '50x50'\n// after\nhas_attachment :thumbnails => { :thumb => '50x50' }","handlingStrategy":"type-guard","validationCode":"thumbs = opts[:thumbnails]\nraise ':thumbnails must be a Hash' unless thumbs.nil? || thumbs.is_a?(Hash)\nhas_attachment :thumbnails => thumbs","typeGuard":"def valid_thumbnails?(opts)\n  opts[:thumbnails].nil? || opts[:thumbnails].is_a?(Hash)\nend","tryCatchPattern":"begin\n  ModelWithBadAttachment # model loads has_attachment at class time\nrescue ArgumentError => e\n  raise if e.message.exclude?(':thumbnails option')\n  Rails.logger.error('fix :thumbnails to a Hash of name => size')\nend","preventionTips":["Always pass :thumbnails as { name => 'WxH' } hash literal","Validate config-sourced thumbnail settings parse to a Hash before loading models","Add a smoke test that eager-loads attachment models so bad options fail in CI"],"tags":["ruby","attachment-fu","argument-error","configuration"],"backgroundTag":"invalid-argument-value","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}