[
  {
    "path": ".codeclimate.yml",
    "content": "---\nengines:\n  duplication:\n    enabled: true\n    config:\n      languages:\n      - ruby\n  fixme:\n    enabled: true\n  rubocop:\n    enabled: true\nratings:\n  paths:\n  - \"**.rb\"\nexclude_paths:\n- features/\n- spec/\n"
  },
  {
    "path": ".github/issue_template.md",
    "content": "## Deprecation notice\n\nPaperclip is currently undergoing [deprecation in favor of ActiveStorage](https://github.com/thoughtbot/paperclip/blob/master/MIGRATING.md). Maintainers of this repository will no longer be tending to new issues. We're leaving the issues page open so Paperclip users can still see & search through old issues, and continue existing discussions if they wish.\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.swp\n.rvmrc\n.bundle\ntmp\n.DS_Store\n\n*.log\n\npublic\npaperclip*.gem\ncapybara*.html\n\n*.rbc\n.rbx\n\n*SPIKE*\n*emfile.lock\ntags\n"
  },
  {
    "path": ".hound.yml",
    "content": "AllCops:\n  Include:\n    - \"**/*.gemspec\"\n    - \"**/*.podspec\"\n    - \"**/*.jbuilder\"\n    - \"**/*.rake\"\n    - \"**/*.opal\"\n    - \"**/Gemfile\"\n    - \"**/Rakefile\"\n    - \"**/Capfile\"\n    - \"**/Guardfile\"\n    - \"**/Podfile\"\n    - \"**/Thorfile\"\n    - \"**/Vagrantfile\"\n    - \"**/Berksfile\"\n    - \"**/Cheffile\"\n    - \"**/Vagabondfile\"\n  Exclude:\n    - \"vendor/**/*\"\n    - \"db/schema.rb\"\n    - 'vendor/**/*'\n    - 'gemfiles/vendor/**/*'\n  Rails:\n    Enabled: false\n  DisplayCopNames: false\n  StyleGuideCopsOnly: false\nStyle/AccessModifierIndentation:\n  Description: Check indentation of private/protected visibility modifiers.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected\n  Enabled: true\n  EnforcedStyle: indent\n  SupportedStyles:\n  - outdent\n  - indent\nStyle/AlignHash:\n  Description: Align the elements of a hash literal if they span more than one line.\n  Enabled: true\n  EnforcedHashRocketStyle: key\n  EnforcedColonStyle: key\n  EnforcedLastArgumentHashStyle: always_inspect\n  SupportedLastArgumentHashStyles:\n  - always_inspect\n  - always_ignore\n  - ignore_implicit\n  - ignore_explicit\nStyle/AlignParameters:\n  Description: Align the parameters of a method call if they span more than one line.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-double-indent\n  Enabled: true\n  EnforcedStyle: with_first_parameter\n  SupportedStyles:\n  - with_first_parameter\n  - with_fixed_indentation\nStyle/AndOr:\n  Description: Use &&/|| instead of and/or.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-and-or-or\n  Enabled: true\n  EnforcedStyle: always\n  SupportedStyles:\n  - always\n  - conditionals\nStyle/BarePercentLiterals:\n  Description: Checks if usage of %() or %Q() matches configuration.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand\n  Enabled: true\n  EnforcedStyle: bare_percent\n  SupportedStyles:\n  - percent_q\n  - bare_percent\nStyle/BracesAroundHashParameters:\n  Description: Enforce braces style around hash parameters.\n  Enabled: true\n  EnforcedStyle: no_braces\n  SupportedStyles:\n  - braces\n  - no_braces\n  - context_dependent\nStyle/CaseIndentation:\n  Description: Indentation of when in a case/when/[else/]end.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#indent-when-to-case\n  Enabled: true\n  IndentWhenRelativeTo: case\n  SupportedStyles:\n  - case\n  - end\n  IndentOneStep: false\nStyle/ClassAndModuleChildren:\n  Description: Checks style of children classes and modules.\n  Enabled: false\n  EnforcedStyle: nested\n  SupportedStyles:\n  - nested\n  - compact\nStyle/ClassCheck:\n  Description: Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.\n  Enabled: true\n  EnforcedStyle: is_a?\n  SupportedStyles:\n  - is_a?\n  - kind_of?\nStyle/CollectionMethods:\n  Description: Preferred collection methods.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#map-find-select-reduce-size\n  Enabled: true\n  PreferredMethods:\n    collect: map\n    collect!: map!\n    find: detect\n    find_all: select\n    reduce: inject\nStyle/CommentAnnotation:\n  Description: Checks formatting of special comments (TODO, FIXME, OPTIMIZE, HACK,\n    REVIEW).\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#annotate-keywords\n  Enabled: false\n  Keywords:\n  - TODO\n  - FIXME\n  - OPTIMIZE\n  - HACK\n  - REVIEW\nStyle/DotPosition:\n  Description: Checks the position of the dot in multi-line method calls.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains\n  Enabled: true\n  EnforcedStyle: trailing\n  SupportedStyles:\n  - leading\n  - trailing\nStyle/EmptyLineBetweenDefs:\n  Description: Use empty lines between defs.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods\n  Enabled: true\n  AllowAdjacentOneLineDefs: false\nStyle/EmptyLinesAroundBlockBody:\n  Description: Keeps track of empty lines around block bodies.\n  Enabled: true\n  EnforcedStyle: no_empty_lines\n  SupportedStyles:\n  - empty_lines\n  - no_empty_lines\nStyle/EmptyLinesAroundClassBody:\n  Description: Keeps track of empty lines around class bodies.\n  Enabled: true\n  EnforcedStyle: no_empty_lines\n  SupportedStyles:\n  - empty_lines\n  - no_empty_lines\nStyle/EmptyLinesAroundModuleBody:\n  Description: Keeps track of empty lines around module bodies.\n  Enabled: true\n  EnforcedStyle: no_empty_lines\n  SupportedStyles:\n  - empty_lines\n  - no_empty_lines\nStyle/Encoding:\n  Description: Use UTF-8 as the source file encoding.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#utf-8\n  Enabled: false\n  EnforcedStyle: always\n  SupportedStyles:\n  - when_needed\n  - always\nStyle/FileName:\n  Description: Use snake_case for source file names.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files\n  Enabled: false\n  Exclude: []\nStyle/FirstParameterIndentation:\n  Description: Checks the indentation of the first parameter in a method call.\n  Enabled: true\n  EnforcedStyle: special_for_inner_method_call_in_parentheses\n  SupportedStyles:\n  - consistent\n  - special_for_inner_method_call\n  - special_for_inner_method_call_in_parentheses\nStyle/For:\n  Description: Checks use of for or each in multiline loops.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-for-loops\n  Enabled: true\n  EnforcedStyle: each\n  SupportedStyles:\n  - for\n  - each\nStyle/FormatString:\n  Description: Enforce the use of Kernel#sprintf, Kernel#format or String#%.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#sprintf\n  Enabled: false\n  EnforcedStyle: format\n  SupportedStyles:\n  - format\n  - sprintf\n  - percent\nStyle/GlobalVars:\n  Description: Do not introduce global variables.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#instance-vars\n  Enabled: false\n  AllowedVariables: []\nStyle/GuardClause:\n  Description: Check for conditionals that can be replaced with guard clauses\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals\n  Enabled: false\n  MinBodyLength: 1\nStyle/HashSyntax:\n  Description: 'Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax { :a =>\n    1, :b => 2 }.'\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#hash-literals\n  Enabled: true\n  EnforcedStyle: ruby19\n  SupportedStyles:\n  - ruby19\n  - hash_rockets\nStyle/IfUnlessModifier:\n  Description: Favor modifier if/unless usage when you have a single-line body.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier\n  Enabled: false\n  MaxLineLength: 80\nStyle/IndentationWidth:\n  Description: Use 2 spaces for indentation.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-indentation\n  Enabled: true\n  Width: 2\nStyle/IndentHash:\n  Description: Checks the indentation of the first key in a hash literal.\n  Enabled: true\n  EnforcedStyle: special_inside_parentheses\n  SupportedStyles:\n  - special_inside_parentheses\n  - consistent\nStyle/LambdaCall:\n  Description: Use lambda.call(...) instead of lambda.(...).\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#proc-call\n  Enabled: false\n  EnforcedStyle: call\n  SupportedStyles:\n  - call\n  - braces\nStyle/Next:\n  Description: Use `next` to skip iteration instead of a condition at the end.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals\n  Enabled: false\n  EnforcedStyle: skip_modifier_ifs\n  MinBodyLength: 3\n  SupportedStyles:\n  - skip_modifier_ifs\n  - always\nStyle/NonNilCheck:\n  Description: Checks for redundant nil checks.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks\n  Enabled: true\n  IncludeSemanticChanges: false\nStyle/MethodDefParentheses:\n  Description: Checks if the method definitions have or don't have parentheses.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#method-parens\n  Enabled: true\n  EnforcedStyle: require_parentheses\n  SupportedStyles:\n  - require_parentheses\n  - require_no_parentheses\nStyle/MethodName:\n  Description: Use the configured style when naming methods.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars\n  Enabled: true\n  EnforcedStyle: snake_case\n  SupportedStyles:\n  - snake_case\n  - camelCase\nStyle/MultilineOperationIndentation:\n  Description: Checks indentation of binary operations that span more than one line.\n  Enabled: true\n  EnforcedStyle: aligned\n  SupportedStyles:\n  - aligned\n  - indented\nStyle/NumericLiterals:\n  Description: Add underscores to large numeric literals to improve their readability.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics\n  Enabled: false\n  MinDigits: 5\nStyle/ParenthesesAroundCondition:\n  Description: Don't use parentheses around the condition of an if/unless/while.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-parens-if\n  Enabled: true\n  AllowSafeAssignment: true\nStyle/PercentLiteralDelimiters:\n  Description: Use `%`-literal delimiters consistently\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces\n  Enabled: false\n  PreferredDelimiters:\n    \"%\": \"()\"\n    \"%i\": \"()\"\n    \"%q\": \"()\"\n    \"%Q\": \"()\"\n    \"%r\": \"{}\"\n    \"%s\": \"()\"\n    \"%w\": \"()\"\n    \"%W\": \"()\"\n    \"%x\": \"()\"\nStyle/PercentQLiterals:\n  Description: Checks if uses of %Q/%q match the configured preference.\n  Enabled: true\n  EnforcedStyle: lower_case_q\n  SupportedStyles:\n  - lower_case_q\n  - upper_case_q\nStyle/PredicateName:\n  Description: Check the names of predicate methods.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark\n  Enabled: true\n  NamePrefix:\n  - is_\n  - has_\n  - have_\n  NamePrefixBlacklist:\n  - is_\nStyle/RaiseArgs:\n  Description: Checks the arguments passed to raise/fail.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages\n  Enabled: false\n  EnforcedStyle: exploded\n  SupportedStyles:\n  - compact\n  - exploded\nStyle/RedundantReturn:\n  Description: Don't use return where it's not required.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-explicit-return\n  Enabled: true\n  AllowMultipleReturnValues: false\nStyle/RegexpLiteral:\n  Description: Use %r for regular expressions matching more than `MaxSlashes` '/'\n    characters. Use %r only for regular expressions matching more than `MaxSlashes`\n    '/' character.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-r\n  Enabled: false\n  MaxSlashes: 1\nStyle/Semicolon:\n  Description: Don't use semicolons to terminate expressions.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-semicolon\n  Enabled: true\n  AllowAsExpressionSeparator: false\nStyle/SignalException:\n  Description: Checks for proper usage of fail and raise.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method\n  Enabled: false\n  EnforcedStyle: semantic\n  SupportedStyles:\n  - only_raise\n  - only_fail\n  - semantic\nStyle/SingleLineBlockParams:\n  Description: Enforces the names of some block params.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks\n  Enabled: false\n  Methods:\n  - reduce:\n    - a\n    - e\n  - inject:\n    - a\n    - e\nStyle/SingleLineMethods:\n  Description: Avoid single-line methods.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods\n  Enabled: false\n  AllowIfMethodIsEmpty: true\nStyle/StringLiterals:\n  Description: Checks if uses of quotes match the configured preference.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-string-literals\n  Enabled: true\n  EnforcedStyle: double_quotes\n  SupportedStyles:\n  - single_quotes\n  - double_quotes\nStyle/StringLiteralsInInterpolation:\n  Description: Checks if uses of quotes inside expressions in interpolated strings\n    match the configured preference.\n  Enabled: true\n  EnforcedStyle: single_quotes\n  SupportedStyles:\n  - single_quotes\n  - double_quotes\nStyle/SpaceAroundBlockParameters:\n  Description: Checks the spacing inside and after block parameters pipes.\n  Enabled: true\n  EnforcedStyleInsidePipes: no_space\n  SupportedStyles:\n  - space\n  - no_space\nStyle/SpaceAroundEqualsInParameterDefault:\n  Description: Checks that the equals signs in parameter default assignments have\n    or don't have surrounding space depending on configuration.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-around-equals\n  Enabled: true\n  EnforcedStyle: space\n  SupportedStyles:\n  - space\n  - no_space\nStyle/SpaceBeforeBlockBraces:\n  Description: Checks that the left block brace has or doesn't have space before it.\n  Enabled: true\n  EnforcedStyle: space\n  SupportedStyles:\n  - space\n  - no_space\nStyle/SpaceInsideBlockBraces:\n  Description: Checks that block braces have or don't have surrounding space. For\n    blocks taking parameters, checks that the left brace has or doesn't have trailing\n    space.\n  Enabled: true\n  EnforcedStyle: space\n  SupportedStyles:\n  - space\n  - no_space\n  EnforcedStyleForEmptyBraces: no_space\n  SpaceBeforeBlockParameters: true\nStyle/SpaceInsideHashLiteralBraces:\n  Description: Use spaces inside hash literal braces - or don't.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators\n  Enabled: true\n  EnforcedStyle: space\n  EnforcedStyleForEmptyBraces: no_space\n  SupportedStyles:\n  - space\n  - no_space\nStyle/SymbolProc:\n  Description: Use symbols as procs instead of blocks when possible.\n  Enabled: true\n  IgnoredMethods:\n  - respond_to\nStyle/TrailingBlankLines:\n  Description: Checks trailing blank lines and final newline.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#newline-eof\n  Enabled: true\n  EnforcedStyle: final_newline\n  SupportedStyles:\n  - final_newline\n  - final_blank_line\nStyle/TrailingCommaInLiteral:\n  Description: Checks for trailing comma in parameter lists and literals.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas\n  Enabled: false\n  EnforcedStyleForMultiline: no_comma\n  SupportedStyles:\n  - comma\n  - no_comma\nStyle/TrivialAccessors:\n  Description: Prefer attr_* methods to trivial readers/writers.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#attr_family\n  Enabled: false\n  ExactNameMatch: false\n  AllowPredicates: false\n  AllowDSLWriters: false\n  Whitelist:\n  - to_ary\n  - to_a\n  - to_c\n  - to_enum\n  - to_h\n  - to_hash\n  - to_i\n  - to_int\n  - to_io\n  - to_open\n  - to_path\n  - to_proc\n  - to_r\n  - to_regexp\n  - to_str\n  - to_s\n  - to_sym\nStyle/VariableName:\n  Description: Use the configured style when naming variables.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars\n  Enabled: true\n  EnforcedStyle: snake_case\n  SupportedStyles:\n  - snake_case\n  - camelCase\nStyle/WhileUntilModifier:\n  Description: Favor modifier while/until usage when you have a single-line body.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier\n  Enabled: false\n  MaxLineLength: 80\nStyle/WordArray:\n  Description: Use %w or %W for arrays of words.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-w\n  Enabled: false\n  MinSize: 0\n  WordRegex: !ruby/regexp /\\A[\\p{Word}]+\\z/\nMetrics/AbcSize:\n  Description: A calculated magnitude based on number of assignments, branches, and\n    conditions.\n  Enabled: true\n  Max: 15\nMetrics/BlockNesting:\n  Description: Avoid excessive block nesting\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count\n  Enabled: false\n  Max: 3\nMetrics/ClassLength:\n  Description: Avoid classes longer than 100 lines of code.\n  Enabled: false\n  CountComments: false\n  Max: 100\nMetrics/CyclomaticComplexity:\n  Description: A complexity metric that is strongly correlated to the number of test\n    cases needed to validate a method.\n  Enabled: false\n  Max: 6\nMetrics/LineLength:\n  Description: Limit lines to 80 characters.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits\n  Enabled: true\n  Max: 80\n  AllowURI: true\n  URISchemes:\n  - http\n  - https\nMetrics/MethodLength:\n  Description: Avoid methods longer than 10 lines of code.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods\n  Enabled: false\n  CountComments: false\n  Max: 10\nMetrics/ParameterLists:\n  Description: Avoid parameter lists longer than three or four parameters.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params\n  Enabled: false\n  Max: 5\n  CountKeywordArgs: true\nMetrics/PerceivedComplexity:\n  Description: A complexity metric geared towards measuring complexity for a human\n    reader.\n  Enabled: false\n  Max: 7\nLint/AssignmentInCondition:\n  Description: Don't use assignment in conditions.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition\n  Enabled: false\n  AllowSafeAssignment: true\nLint/EndAlignment:\n  Description: Align ends correctly.\n  Enabled: true\n  AlignWith: keyword\n  SupportedStyles:\n  - keyword\n  - variable\nLint/DefEndAlignment:\n  Description: Align ends corresponding to defs correctly.\n  Enabled: true\n  AlignWith: start_of_line\n  SupportedStyles:\n  - start_of_line\n  - def\nRails/ActionFilter:\n  Description: Enforces consistent use of action filter methods.\n  Enabled: false\n  EnforcedStyle: action\n  SupportedStyles:\n  - action\n  - filter\n  Include:\n  - app/controllers/**/*.rb\nRails/HasAndBelongsToMany:\n  Description: Prefer has_many :through to has_and_belongs_to_many.\n  Enabled: true\n  Include:\n  - app/models/**/*.rb\nRails/Output:\n  Description: Checks for calls to puts, print, etc.\n  Enabled: true\n  Include:\n  - app/**/*.rb\n  - config/**/*.rb\n  - db/**/*.rb\n  - lib/**/*.rb\nRails/ReadWriteAttribute:\n  Description: Checks for read_attribute(:attr) and write_attribute(:attr, val).\n  Enabled: true\n  Include:\n  - app/models/**/*.rb\nRails/ScopeArgs:\n  Description: Checks the arguments of ActiveRecord scopes.\n  Enabled: true\n  Include:\n  - app/models/**/*.rb\nRails/Validation:\n  Description: Use validates :attribute, hash of validations.\n  Enabled: true\n  Include:\n  - app/models/**/*.rb\nStyle/InlineComment:\n  Description: Avoid inline comments.\n  Enabled: false\nStyle/MethodCalledOnDoEndBlock:\n  Description: Avoid chaining a method call on a do...end block.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#single-line-blocks\n  Enabled: false\nStyle/SymbolArray:\n  Description: Use %i or %I for arrays of symbols.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-i\n  Enabled: false\nStyle/ExtraSpacing:\n  Description: Do not use unnecessary spacing.\n  Enabled: true\nStyle/AccessorMethodName:\n  Description: Check the naming of accessor methods for get_/set_.\n  Enabled: false\nStyle/Alias:\n  Description: Use alias_method instead of alias.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method\n  Enabled: false\nStyle/AlignArray:\n  Description: Align the elements of an array literal if they span more than one line.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays\n  Enabled: true\nStyle/ArrayJoin:\n  Description: Use Array#join instead of Array#*.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#array-join\n  Enabled: false\nStyle/AsciiComments:\n  Description: Use only ascii symbols in comments.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#english-comments\n  Enabled: false\nStyle/AsciiIdentifiers:\n  Description: Use only ascii symbols in identifiers.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#english-identifiers\n  Enabled: false\nStyle/Attr:\n  Description: Checks for uses of Module#attr.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#attr\n  Enabled: false\nStyle/BeginBlock:\n  Description: Avoid the use of BEGIN blocks.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks\n  Enabled: true\nStyle/BlockComments:\n  Description: Do not use block comments.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-block-comments\n  Enabled: true\nStyle/BlockEndNewline:\n  Description: Put end statement of multiline block on its own line.\n  Enabled: true\nStyle/Blocks:\n  Description: Avoid using {...} for multi-line blocks (multiline chaining is always\n    ugly). Prefer {...} over do...end for single-line blocks.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#single-line-blocks\n  Enabled: true\nStyle/CaseEquality:\n  Description: Avoid explicit use of the case equality operator(===).\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-case-equality\n  Enabled: false\nStyle/CharacterLiteral:\n  Description: Checks for uses of character literals.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-character-literals\n  Enabled: false\nStyle/ClassAndModuleCamelCase:\n  Description: Use CamelCase for classes and modules.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#camelcase-classes\n  Enabled: true\nStyle/ClassMethods:\n  Description: Use self when defining module/class methods.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#def-self-singletons\n  Enabled: true\nStyle/ClassVars:\n  Description: Avoid the use of class variables.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-class-vars\n  Enabled: false\nStyle/ColonMethodCall:\n  Description: 'Do not use :: for method call.'\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#double-colons\n  Enabled: false\nStyle/CommentIndentation:\n  Description: Indentation of comments.\n  Enabled: true\nStyle/ConstantName:\n  Description: Constants should use SCREAMING_SNAKE_CASE.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#screaming-snake-case\n  Enabled: true\nStyle/DefWithParentheses:\n  Description: Use def with parentheses when there are arguments.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#method-parens\n  Enabled: true\nStyle/Documentation:\n  Description: Document classes and non-namespace modules.\n  Enabled: false\nStyle/DoubleNegation:\n  Description: Checks for uses of double negation (!!).\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang\n  Enabled: false\nStyle/EachWithObject:\n  Description: Prefer `each_with_object` over `inject` or `reduce`.\n  Enabled: false\nStyle/ElseAlignment:\n  Description: Align elses and elsifs correctly.\n  Enabled: true\nStyle/EmptyElse:\n  Description: Avoid empty else-clauses.\n  Enabled: true\nStyle/EmptyLines:\n  Description: Don't use several empty lines in a row.\n  Enabled: true\nStyle/EmptyLinesAroundAccessModifier:\n  Description: Keep blank lines around access modifiers.\n  Enabled: true\nStyle/EmptyLinesAroundMethodBody:\n  Description: Keeps track of empty lines around method bodies.\n  Enabled: true\nStyle/EmptyLiteral:\n  Description: Prefer literals to Array.new/Hash.new/String.new.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash\n  Enabled: false\nStyle/EndBlock:\n  Description: Avoid the use of END blocks.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-END-blocks\n  Enabled: true\nStyle/EndOfLine:\n  Description: Use Unix-style line endings.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#crlf\n  Enabled: true\nStyle/EvenOdd:\n  Description: Favor the use of Fixnum#even? && Fixnum#odd?\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#predicate-methods\n  Enabled: false\nStyle/FlipFlop:\n  Description: Checks for flip flops\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-flip-flops\n  Enabled: false\nStyle/IfWithSemicolon:\n  Description: Do not use if x; .... Use the ternary operator instead.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs\n  Enabled: false\nStyle/IndentationConsistency:\n  Description: Keep indentation straight.\n  Enabled: true\nStyle/IndentArray:\n  Description: Checks the indentation of the first element in an array literal.\n  Enabled: true\nStyle/InfiniteLoop:\n  Description: Use Kernel#loop for infinite loops.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#infinite-loop\n  Enabled: true\nStyle/Lambda:\n  Description: Use the new lambda literal syntax for single-line blocks.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#lambda-multi-line\n  Enabled: false\nStyle/LeadingCommentSpace:\n  Description: Comments should start with a space.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#hash-space\n  Enabled: true\nStyle/LineEndConcatenation:\n  Description: Use \\ instead of + or << to concatenate two string literals at line\n    end.\n  Enabled: false\nStyle/MethodCallParentheses:\n  Description: Do not use parentheses for method calls with no arguments.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-args-no-parens\n  Enabled: true\nStyle/ModuleFunction:\n  Description: Checks for usage of `extend self` in modules.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function\n  Enabled: false\nStyle/MultilineBlockChain:\n  Description: Avoid multi-line chains of blocks.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#single-line-blocks\n  Enabled: true\nStyle/MultilineBlockLayout:\n  Description: Ensures newlines after multiline block do statements.\n  Enabled: true\nStyle/MultilineIfThen:\n  Description: Do not use then for multi-line if/unless.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-then\n  Enabled: true\nStyle/MultilineTernaryOperator:\n  Description: 'Avoid multi-line ?: (the ternary operator); use if/unless instead.'\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary\n  Enabled: true\nStyle/NegatedIf:\n  Description: Favor unless over if for negative conditions (or control flow or).\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#unless-for-negatives\n  Enabled: false\nStyle/NegatedWhile:\n  Description: Favor until over while for negative conditions.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#until-for-negatives\n  Enabled: false\nStyle/NestedTernaryOperator:\n  Description: Use one expression per branch in a ternary operator.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-ternary\n  Enabled: true\nStyle/NilComparison:\n  Description: Prefer x.nil? to x == nil.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#predicate-methods\n  Enabled: false\nStyle/Not:\n  Description: Use ! instead of not.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#bang-not-not\n  Enabled: false\nStyle/OneLineConditional:\n  Description: Favor the ternary operator(?:) over if/then/else/end constructs.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator\n  Enabled: false\nStyle/OpMethod:\n  Description: When defining binary operators, name the argument other.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#other-arg\n  Enabled: false\nStyle/PerlBackrefs:\n  Description: Avoid Perl-style regex back references.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers\n  Enabled: false\nStyle/Proc:\n  Description: Use proc instead of Proc.new.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#proc\n  Enabled: false\nStyle/RedundantBegin:\n  Description: Don't use begin blocks when they are not needed.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#begin-implicit\n  Enabled: true\nStyle/RedundantException:\n  Description: Checks for an obsolete RuntimeException argument in raise/fail.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror\n  Enabled: true\nStyle/RedundantSelf:\n  Description: Don't use self where it's not needed.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-self-unless-required\n  Enabled: true\nStyle/RescueModifier:\n  Description: Avoid using rescue in its modifier form.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers\n  Enabled: true\nStyle/SelfAssignment:\n  Description: Checks for places where self-assignment shorthand should have been\n    used.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#self-assignment\n  Enabled: false\nStyle/SpaceBeforeFirstArg:\n  Description: Checks that exactly one space is used between a method name and the\n    first argument for method calls without parentheses.\n  Enabled: true\nStyle/SpaceAfterColon:\n  Description: Use spaces after colons.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators\n  Enabled: true\nStyle/SpaceAfterComma:\n  Description: Use spaces after commas.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators\n  Enabled: true\nStyle/SpaceAroundKeyword:\n  Description: Use spaces after if/elsif/unless/while/until/case/when.\n  Enabled: true\nStyle/SpaceAfterMethodName:\n  Description: Do not put a space between a method name and the opening parenthesis\n    in a method definition.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#parens-no-spaces\n  Enabled: true\nStyle/SpaceAfterNot:\n  Description: Tracks redundant space after the ! operator.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-space-bang\n  Enabled: true\nStyle/SpaceAfterSemicolon:\n  Description: Use spaces after semicolons.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators\n  Enabled: true\nStyle/SpaceBeforeComma:\n  Description: No spaces before commas.\n  Enabled: true\nStyle/SpaceBeforeComment:\n  Description: Checks for missing space between code and a comment on the same line.\n  Enabled: true\nStyle/SpaceBeforeSemicolon:\n  Description: No spaces before semicolons.\n  Enabled: true\nStyle/SpaceAroundOperators:\n  Description: Use spaces around operators.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-operators\n  Enabled: true\nStyle/SpaceInsideBrackets:\n  Description: No spaces after [ or before ].\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-spaces-braces\n  Enabled: true\nStyle/SpaceInsideParens:\n  Description: No spaces after ( or before ).\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-spaces-braces\n  Enabled: true\nStyle/SpaceInsideRangeLiteral:\n  Description: No spaces inside range literals.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals\n  Enabled: true\nStyle/SpecialGlobalVars:\n  Description: Avoid Perl-style global variables.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms\n  Enabled: false\nStyle/StructInheritance:\n  Description: Checks for inheritance from Struct.new.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new\n  Enabled: true\nStyle/Tab:\n  Description: No hard tabs.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#spaces-indentation\n  Enabled: true\nStyle/TrailingWhitespace:\n  Description: Avoid trailing whitespace.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace\n  Enabled: true\nStyle/UnlessElse:\n  Description: Do not use unless with else. Rewrite these with the positive case first.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-else-with-unless\n  Enabled: true\nStyle/UnneededCapitalW:\n  Description: Checks for %W when interpolation is not needed.\n  Enabled: true\nStyle/UnneededPercentQ:\n  Description: Checks for %q/%Q when single quotes or double quotes would do.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-q\n  Enabled: true\nStyle/UnneededPercentX:\n  Description: Checks for %x when `` would do.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-x\n  Enabled: true\nStyle/VariableInterpolation:\n  Description: Don't interpolate global, instance and class variables directly in\n    strings.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate\n  Enabled: false\nStyle/WhenThen:\n  Description: Use when x then ... for one-line cases.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases\n  Enabled: false\nStyle/WhileUntilDo:\n  Description: Checks for redundant do after while or until.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do\n  Enabled: true\nLint/AmbiguousOperator:\n  Description: Checks for ambiguous operators in the first argument of a method invocation\n    without parentheses.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#parens-as-args\n  Enabled: false\nLint/AmbiguousRegexpLiteral:\n  Description: Checks for ambiguous regexp literals in the first argument of a method\n    invocation without parenthesis.\n  Enabled: false\nLint/BlockAlignment:\n  Description: Align block ends correctly.\n  Enabled: true\nLint/ConditionPosition:\n  Description: Checks for condition placed in a confusing position relative to the\n    keyword.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#same-line-condition\n  Enabled: false\nLint/Debugger:\n  Description: Check for debugger calls.\n  Enabled: true\nLint/DeprecatedClassMethods:\n  Description: Check for deprecated class method calls.\n  Enabled: false\nLint/DuplicateMethods:\n  Description: Check for duplicate methods calls.\n  Enabled: true\nLint/ElseLayout:\n  Description: Check for odd code arrangement in an else block.\n  Enabled: false\nLint/EmptyEnsure:\n  Description: Checks for empty ensure block.\n  Enabled: true\nLint/EmptyInterpolation:\n  Description: Checks for empty string interpolation.\n  Enabled: true\nLint/EndInMethod:\n  Description: END blocks should not be placed inside method definitions.\n  Enabled: true\nLint/EnsureReturn:\n  Description: Do not use return in an ensure block.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-return-ensure\n  Enabled: true\nLint/Eval:\n  Description: The use of eval represents a serious security risk.\n  Enabled: true\nLint/HandleExceptions:\n  Description: Don't suppress exception.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions\n  Enabled: false\nLint/InvalidCharacterLiteral:\n  Description: Checks for invalid character literals with a non-escaped whitespace\n    character.\n  Enabled: false\nLint/LiteralInCondition:\n  Description: Checks of literals used in conditions.\n  Enabled: false\nLint/LiteralInInterpolation:\n  Description: Checks for literals used in interpolation.\n  Enabled: false\nLint/Loop:\n  Description: Use Kernel#loop with break rather than begin/end/until or begin/end/while\n    for post-loop tests.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#loop-with-break\n  Enabled: false\nLint/ParenthesesAsGroupedExpression:\n  Description: Checks for method calls with a space before the opening parenthesis.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#parens-no-spaces\n  Enabled: false\nLint/RequireParentheses:\n  Description: Use parentheses in the method call to avoid confusion about precedence.\n  Enabled: false\nLint/RescueException:\n  Description: Avoid rescuing the Exception class.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-blind-rescues\n  Enabled: true\nLint/ShadowingOuterLocalVariable:\n  Description: Do not use the same name as outer local variable for block arguments\n    or block local variables.\n  Enabled: true\nLint/SpaceBeforeFirstArg:\n  Description: Put a space between a method name and the first argument in a method\n    call without parentheses.\n  Enabled: true\nLint/StringConversionInInterpolation:\n  Description: Checks for Object#to_s usage in string interpolation.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-to-s\n  Enabled: true\nLint/UnderscorePrefixedVariableName:\n  Description: Do not use prefix `_` for a variable that is used.\n  Enabled: false\nLint/UnusedBlockArgument:\n  Description: Checks for unused block arguments.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars\n  Enabled: true\nLint/UnusedMethodArgument:\n  Description: Checks for unused method arguments.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars\n  Enabled: true\nLint/UnreachableCode:\n  Description: Unreachable code.\n  Enabled: true\nLint/UselessAccessModifier:\n  Description: Checks for useless access modifiers.\n  Enabled: true\nLint/UselessAssignment:\n  Description: Checks for useless assignment to a local variable.\n  StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars\n  Enabled: true\nLint/UselessComparison:\n  Description: Checks for comparison of something with itself.\n  Enabled: true\nLint/UselessElseWithoutRescue:\n  Description: Checks for useless `else` in `begin..end` without `rescue`.\n  Enabled: true\nLint/UselessSetterCall:\n  Description: Checks for useless setter call to a local variable.\n  Enabled: true\nLint/Void:\n  Description: Possible use of operator/literal/variable in void context.\n  Enabled: false\nRails/Delegate:\n  Description: Prefer delegate method for delegations.\n  Enabled: false\n\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "inherit_from: .hound.yml\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: ruby\nsudo: false\n\nrvm:\n  - 2.1\n  - 2.2\n  - 2.3\n  - 2.4\n\nscript: \"bundle exec rake clean spec cucumber\"\n\naddons:\n  apt:\n    packages:\n    - ghostscript\n\ngemfile:\n  - gemfiles/4.2.gemfile\n  - gemfiles/5.0.gemfile\n\nmatrix:\n  fast_finish: true\n  exclude:\n    - gemfile: gemfiles/5.0.gemfile\n      rvm: 2.1\n"
  },
  {
    "path": "Appraisals",
    "content": "appraise \"4.2\" do\n  gem \"rails\", \"~> 4.2.0\"\nend\n\nappraise \"5.0\" do\n  gem \"rails\", \"~> 5.0.0\"\nend\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing\n============\n\nWe love pull requests from everyone. By participating in this project, you agree\nto abide by the thoughtbot [code of conduct].\n\n[code of conduct]: https://thoughtbot.com/open-source-code-of-conduct\n\nHere's a quick guide for contributing:\n\n1. Fork the repo.\n\n1. Make sure you have ImageMagick and Ghostscript installed. See [this section]\n(./README.md#image-processor) of the README.\n\n1. Run the tests. We only take pull requests with passing tests, and it's great\nto know that you have a clean slate: `bundle && bundle exec rake`\n\n1. Add a test for your change. Only refactoring and documentation changes\nrequire no new tests. If you are adding functionality or fixing a bug, we need\na test!\n\n1. Make the test pass.\n\n1. Mention how your changes affect the project to other developers and users in\n   the `NEWS.md` file.\n\n1. Push to your fork and submit a pull request.\n\nAt this point you're waiting on us. We like to at least comment on, if not\naccept, pull requests within seven business days (most of the work on Paperclip\ngets done on Fridays). We may suggest some changes or improvements or\nalternatives.\n\nSome things that will increase the chance that your pull request is accepted,\ntaken straight from the Ruby on Rails guide:\n\n* Use Rails idioms and helpers\n* Include tests that fail without your code, and pass with it\n* Update the documentation, the surrounding one, examples elsewhere, guides,\n  whatever is affected by your contribution\n\nRunning Tests\n-------------\n\nPaperclip uses [Appraisal](https://github.com/thoughtbot/appraisal) to aid\ntesting against multiple version of Ruby on Rails. This helps us to make sure\nthat Paperclip performs correctly with them.\n\nPaperclip also uses [RSpec](http://rspec.info) for its unit tests. If you submit\ntests that are not written for Cucumber or RSpec without a very good reason, you\nwill be asked to rewrite them before we'll accept.\n\n### Bootstrapping your test suite:\n\n    bundle install\n    bundle exec appraisal install\n\nThis will install all the required gems that requires to test against each\nversion of Rails, which defined in `gemfiles/*.gemfile`.\n\n### To run a full test suite:\n\n    bundle exec appraisal rake\n\nThis will run RSpec and Cucumber against all version of Rails\n\n### To run single Test::Unit or Cucumber test\n\nYou need to specify a `BUNDLE_GEMFILE` pointing to the gemfile before running\nthe normal test command:\n\n    BUNDLE_GEMFILE=gemfiles/4.1.gemfile rspec spec/paperclip/attachment_spec.rb\n    BUNDLE_GEMFILE=gemfiles/4.1.gemfile cucumber features/basic_integration.feature\n\nSyntax\n------\n\n* Two spaces, no tabs.\n* No trailing whitespace. Blank lines should not have any space.\n* Prefer &&/|| over and/or.\n* MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg.\n* a = b and not a=b.\n* Follow the conventions you see used in the source already.\n\nAnd in case we didn't emphasize it enough: we love tests!\n"
  },
  {
    "path": "Gemfile",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'sqlite3', '~> 1.3.8', :platforms => :ruby\ngem 'pry'\n\n# Hinting at development dependencies\n# Prevents bundler from taking a long-time to resolve\ngroup :development, :test do\n  gem 'activerecord-import'\n  gem 'mime-types'\n  gem 'builder'\n  gem 'rubocop', require: false\n  gem 'rspec'\nend\n"
  },
  {
    "path": "LICENSE",
    "content": "\nLICENSE\n\nThe MIT License\n\nCopyright (c) 2008-2016 Jon Yurek and thoughtbot, inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MIGRATING-ES.md",
    "content": "# Migrando de Paperclip a ActiveStorage\n\nPaperclip y ActiveStorage resuelven problemas similares con soluciones\nsimilares, por lo que pasar de uno a otro es simple.\n\nEl proceso de ir desde Paperclip hacia ActiveStorage es como sigue:\n\n1. Implementa las migraciones a la base de datos de ActiveStorage.\n2. Configura el almacenamiento.\n3. Copia la base de datos.\n4. Copia los archivos.\n5. Actualiza tus pruebas.\n6. Actualiza tus vistas.\n7. Actualiza tus controladores.\n8. Actualiza tus modelos.\n\n## Implementa las migraciones a la base de datos de ActiveStorage\n\nSigue [las instrucciones para instalar ActiveStorage]. Muy probablemente vas a\nquerer agregar la gema `mini_magick` a tu Gemfile.\n\n\n```sh\nrails active_storage:install\n```\n\n[las instrucciones para instalar ActiveStorage]: https://github.com/rails/rails/blob/master/activestorage/README.md#installation\n\n## Configura el almacenamiento\n\nDe nuevo, sigue [las instrucciones para configurar ActiveStorage].\n\n[las instrucciones para configurar ActiveStorage]: http://edgeguides.rubyonrails.org/active_storage_overview.html#setup\n\n## Copia la base de datos.\n\nLas tablas `active_storage_blobs` y`active_storage_attachments` son en donde\nActiveStorage espera encontrar los metadatos del archivo. Paperclip almacena los\nmetadatos del archivo directamente en en la tabla del objeto asociado.\n\nVas a necesitar escribir una migración para esta conversión. Proveer un script\nsimple, es complicado porque están involucrados tus modelos. ¡Pero lo\nintentaremos!\n\nAsí sería para un `User` con un `avatar` en Paperclip:\n\n```ruby\nclass User < ApplicationRecord\n  has_attached_file :avatar\nend\n```\n\nTus migraciones de Paperclip producirán una tabla como la siguiente:\n\n```ruby\ncreate_table \"users\", force: :cascade do |t|\n  t.string \"avatar_file_name\"\n  t.string \"avatar_content_type\"\n  t.integer \"avatar_file_size\"\n  t.datetime \"avatar_updated_at\"\nend\n```\n\nY tu la convertirás en estas tablas:\n\n```ruby\ncreate_table \"active_storage_attachments\", force: :cascade do |t|\n  t.string \"name\", null: false\n  t.string \"record_type\", null: false\n  t.integer \"record_id\", null: false\n  t.integer \"blob_id\", null: false\n  t.datetime \"created_at\", null: false\n  t.index [\"blob_id\"], name: \"index_active_storage_attachments_on_blob_id\"\n  t.index [\"record_type\", \"record_id\", \"name\", \"blob_id\"], name: \"index_active_storage_attachments_uniqueness\", unique: true\nend\n```\n\n```ruby\ncreate_table \"active_storage_blobs\", force: :cascade do |t|\n  t.string \"key\", null: false\n  t.string \"filename\", null: false\n  t.string \"content_type\"\n  t.text \"metadata\"\n  t.bigint \"byte_size\", null: false\n  t.string \"checksum\", null: false\n  t.datetime \"created_at\", null: false\n  t.index [\"key\"], name: \"index_active_storage_blobs_on_key\", unique: true\nend\n```\n\nAsí que asumiendo que quieres dejar los archivos en el mismo lugar, _esta es tu\nmigración_. De otra forma, ve la siguiente sección primero y modifica la\nmigración como corresponda.\n\n```ruby\nDir[Rails.root.join(\"app/models/**/*.rb\")].sort.each { |file| require file }\n\nclass ConvertToActiveStorage < ActiveRecord::Migration[5.2]\n  require 'open-uri'\n\n  def up\n    # postgres\n    get_blob_id = 'LASTVAL()'\n    # mariadb\n    # get_blob_id = 'LAST_INSERT_ID()'\n    # sqlite\n    # get_blob_id = 'LAST_INSERT_ROWID()'\n\n    active_storage_blob_statement = ActiveRecord::Base.connection.raw_connection.prepare(<<-SQL)\n      INSERT INTO active_storage_blobs (\n        key, filename, content_type, metadata, byte_size,\n        checksum, created_at\n      ) VALUES (?, ?, ?, '{}', ?, ?, ?)\n    SQL\n\n    active_storage_attachment_statement = ActiveRecord::Base.connection.raw_connection.prepare(<<-SQL)\n      INSERT INTO active_storage_attachments (\n        name, record_type, record_id, blob_id, created_at\n      ) VALUES (?, ?, ?, #{get_blob_id}, ?)\n    SQL\n\n    models = ActiveRecord::Base.descendants.reject(&:abstract_class?)\n\n    transaction do\n      models.each do |model|\n        attachments = model.column_names.map do |c|\n          if c =~ /(.+)_file_name$/\n            $1\n          end\n        end.compact\n\n        model.find_each.each do |instance|\n          attachments.each do |attachment|\n            active_storage_blob_statement.execute(\n              key(instance, attachment),\n              instance.send(\"#{attachment}_file_name\"),\n              instance.send(\"#{attachment}_content_type\"),\n              instance.send(\"#{attachment}_file_size\"),\n              checksum(instance.send(attachment)),\n              instance.updated_at.iso8601\n            )\n\n            active_storage_attachment_statement.\n              execute(attachment, model.name, instance.id, instance.updated_at.iso8601)\n          end\n        end\n      end\n    end\n\n    active_storage_attachment_statement.close\n    active_storage_blob_statement.close\n  end\n\n  def down\n    raise ActiveRecord::IrreversibleMigration\n  end\n\n  private\n\n  def key(instance, attachment)\n    SecureRandom.uuid\n    # Alternativamente:\n    # instance.send(\"#{attachment}_file_name\")\n  end\n\n  def checksum(attachment)\n    # archivos locales almacenados en disco:\n    url = attachment.path\n    Digest::MD5.base64digest(File.read(url))\n\n    # archivos remotos almacenados en la computadora de alguién más:\n    # url = attachment.url\n    # Digest::MD5.base64digest(Net::HTTP.get(URI(url)))\n  end\nend\n```\n\n## Copia los archivos\n\nLa migración de arriba deja los archivos como estaban. Sin embargo,\nlos servicios de Paperclip y ActiveStorage utilizan diferentes ubicaciones.\n\nPor defecto, Paperclip se ve así:\n\n```\npublic/system/users/avatars/000/000/004/original/the-mystery-of-life.png\n```\n\nY ActiveStorage se ve así:\n\n```\nstorage/xM/RX/xMRXuT6nqpoiConJFQJFt6c9\n```\n\nEse `xMRXuT6nqpoiConJFQJFt6c9` es el valor de `active_storage_blobs.key`. En la\nmigración de arriba usamos simplemente el nombre del archivo, pero tal vez\nquieras usar un UUID.\n\nMigrando los archivos en un hospedaje externo (S3, Azure Storage, GCS, etc.)\nestá fuera del alcance de este documento inicial. Así es como se vería para un\nalmacenamiento local:\n\n```ruby\n#!bin/rails runner\n\nclass ActiveStorageBlob < ActiveRecord::Base\nend\n\nclass ActiveStorageAttachment < ActiveRecord::Base\n  belongs_to :blob, class_name: 'ActiveStorageBlob'\n  belongs_to :record, polymorphic: true\nend\n\nActiveStorageAttachment.find_each do |attachment|\n  name = attachment.name\n\n  source = attachment.record.send(name).path\n  dest_dir = File.join(\n    \"storage\",\n    attachment.blob.key.first(2),\n    attachment.blob.key.first(4).last(2))\n  dest = File.join(dest_dir, attachment.blob.key)\n\n  FileUtils.mkdir_p(dest_dir)\n  puts \"Moving #{source} to #{dest}\"\n  FileUtils.cp(source, dest)\nend\n```\n\n## Actualiza tus pruebas\n\nEn lugar de utilizar `have_attached_file`, será necesario que escribas tu propio\nmatcher. Aquí hay un matcher similar _en espíritu_ al que Paperclip provee:\n\n\n```ruby\nRSpec::Matchers.define :have_attached_file do |name|\n  matches do |record|\n    file = record.send(name)\n    file.respond_to?(:variant) && file.respond_to?(:attach)\n  end\nend\n```\n\n## Actualiza tus vistas\n\nEn Paperclip se ven así:\n\n```ruby\nimage_tag @user.avatar.url(:medium)\n```\n\nEn ActiveStorage se ven así:\n\n```ruby\nimage_tag @user.avatar.variant(resize: \"250x250\")\n```\n\n## Actualiza tus controladores\n\nEsto no debería _requerir_ ningúna actualización. Sin embargo, si te fijas en\nel schema de tu base de datos, notaras un join.\n\nPor ejemplo si tu controlador tiene:\n\n```ruby\ndef index\n  @users = User.all.order(:name)\nend\n```\n\nY tu vista tiene:\n\n```\n<ul>\n  <% @users.each do |user| %>\n    <li><%= image_tag user.avatar.variant(resize: \"10x10\"), alt: user.name %></li>\n  <% end %>\n</ul>\n```\n\nVas a terminar con un n+1, ya que descargas cada archivo adjunto dentro del\nbucle.\n\nAsí que mientras que el controlador y el modelo funcionarán sin ningún cambio,\ntal vez quieras revisar dos veces tus bucles y agregar `includes` en dónde haga\nfalta.\n\nActiveStorage agrega `avatar_attachment` y `avatar_blob` a las relaciones del\ntipo `has-one`, así como `avatar_attachments` y `avatar_blobs` a las relaciones\nde tipo `has-many`:\n\n```ruby\ndef index\n  @users = User.all.order(:name).includes(:avatar_attachment)\nend\n```\n\n## Actualiza tus modelos\n\nSigue [la guía sobre cómo adjuntar archivos a los registros]. Por ejemplo, un\n`User` con un `avatar` se representa como:\n\n```ruby\nclass User < ApplicationRecord\n  has_one_attached :avatar\nend\n```\n\nCualquier cambio de tamaño se hace en la vista como un `variant`.\n\n[la guía sobre cómo adjuntar archivos a los registros]: http://edgeguides.rubyonrails.org/active_storage_overview.html#attaching-files-to-records\n\n## Quita Paperclip\n\nQuita la gema de tu `Gemfile` y corre `bundle`. Corre tus pruebas porque ya\nterminaste!\n"
  },
  {
    "path": "MIGRATING.md",
    "content": "# Migrating from Paperclip to ActiveStorage\n\n* [Video presentation](https://www.youtube.com/watch?v=tZ_WNUytO9o).\n* [En español](https://github.com/thoughtbot/paperclip/blob/master/MIGRATING-ES.md).\n\nPaperclip and ActiveStorage solve similar problems with similar solutions, so\ntransitioning from one to the other is straightforward data re-writing.\n\nThe process of going from Paperclip to ActiveStorage is as follows:\n\n1. Apply the ActiveStorage database migrations.\n2. Configure storage.\n3. Copy the database data over.\n4. Copy the files over.\n5. Update your tests.\n6. Update your views.\n7. Update your controllers.\n8. Update your models.\n\n## Apply the ActiveStorage database migrations\n\nYou'll very likely want to add the `mini_magick` gem to your Gemfile.\n\nMake sure your `config/application.rb` requires the ActiveStorage engine:\n\n```rb\n# config/application.rb\nrequire \"active_storage/engine\"\n```\n\nThen, follow [the instructions for installing ActiveStorage].\n\n\n```sh\nrails active_storage:install\n```\n\n[the instructions for installing ActiveStorage]: https://github.com/rails/rails/tree/5-2-stable/activestorage#installation\n\n## Configure storage\n\nAgain, follow [the instructions for configuring ActiveStorage].\n\nIt's worth highlighting that, by default, ActiveStorage's\n[`DiskService`][active-storage-service] will store files locally in\n`Rails.root.join(\"storage\")`. When storing files locally, Paperclip, by default,\nwrites to `Rails.root.join(\"public\", \"system\")`.\n\nMake sure to exclude your locally stored files from version control.\n\nFor instance, if you're using Git, add `storage/` to your `.gitignore`.\n\n```diff\n  !.keep\n  /.bundle\n  /.byebug_history\n  /.tmp/*\n  /log/*\n  /public/system/\n+ storage/\n```\n\n[the instructions for configuring ActiveStorage]: https://guides.rubyonrails.org/v5.2/active_storage_overview.html#setup\n[active-storage-service]: https://api.rubyonrails.org/v5.2/classes/ActiveStorage/Service.html\n\n## Copy the database data over\n\nThe `active_storage_blobs` and `active_storage_attachments` tables are where\nActiveStorage expects to find file metadata. Paperclip stores the file metadata\ndirectly on the associated object's table.\n\nYou'll need to write a migration for this conversion. Because the models for\nyour domain are involved, it's tricky to supply a simple script. But we'll try!\n\nHere's how it would go for a `User` with an `avatar`, that is this in\nPaperclip:\n\n```ruby\nclass User < ApplicationRecord\n  has_attached_file :avatar\nend\n```\n\nYour Paperclip migrations will produce a table like so:\n\n```ruby\ncreate_table \"users\", force: :cascade do |t|\n  t.string \"avatar_file_name\"\n  t.string \"avatar_content_type\"\n  t.integer \"avatar_file_size\"\n  t.datetime \"avatar_updated_at\"\nend\n```\n\nAnd you'll be converting into these tables:\n\n```ruby\ncreate_table \"active_storage_attachments\", force: :cascade do |t|\n  t.string \"name\", null: false\n  t.string \"record_type\", null: false\n  t.integer \"record_id\", null: false\n  t.integer \"blob_id\", null: false\n  t.datetime \"created_at\", null: false\n  t.index [\"blob_id\"], name: \"index_active_storage_attachments_on_blob_id\"\n  t.index [\"record_type\", \"record_id\", \"name\", \"blob_id\"], name: \"index_active_storage_attachments_uniqueness\", unique: true\nend\n```\n\n```ruby\ncreate_table \"active_storage_blobs\", force: :cascade do |t|\n  t.string \"key\", null: false\n  t.string \"filename\", null: false\n  t.string \"content_type\"\n  t.text \"metadata\"\n  t.bigint \"byte_size\", null: false\n  t.string \"checksum\", null: false\n  t.datetime \"created_at\", null: false\n  t.index [\"key\"], name: \"index_active_storage_blobs_on_key\", unique: true\nend\n```\n\nSo, assuming you want to leave the files in the exact same place, _this is\nyour migration_. Otherwise, see the next section first and modify the migration\nto taste.\n\n```ruby\nclass ConvertToActiveStorage < ActiveRecord::Migration[5.2]\n  require 'open-uri'\n\n  def up\n    # postgres\n    get_blob_id = 'LASTVAL()'\n    # mariadb\n    # get_blob_id = 'LAST_INSERT_ID()'\n    # sqlite\n    # get_blob_id = 'LAST_INSERT_ROWID()'\n\n    active_storage_blob_statement = ActiveRecord::Base.connection.raw_connection.prepare('active_storage_blob_statement', <<-SQL)\n      INSERT INTO active_storage_blobs (\n        `key`, filename, content_type, metadata, byte_size, checksum, created_at\n      ) VALUES ($1, $2, $3, '{}', $4, $5, $6)\n    SQL\n\n    active_storage_attachment_statement = ActiveRecord::Base.connection.raw_connection.prepare('active_storage_attachment_statement', <<-SQL)\n      INSERT INTO active_storage_attachments (\n        name, record_type, record_id, blob_id, created_at\n      ) VALUES ($1, $2, $3, #{get_blob_id}, $4)\n    SQL\n\n    Rails.application.eager_load!\n    models = ActiveRecord::Base.descendants.reject(&:abstract_class?)\n\n    transaction do\n      models.each do |model|\n        attachments = model.column_names.map do |c|\n          if c =~ /(.+)_file_name$/\n            $1\n          end\n        end.compact\n\n        if attachments.blank?\n          next\n        end\n\n        model.find_each.each do |instance|\n          attachments.each do |attachment|\n            if instance.send(attachment).path.blank?\n              next\n            end\n\n            ActiveRecord::Base.connection.execute_prepared(\n              'active_storage_blob_statement', [\n                key(instance, attachment),\n                instance.send(\"#{attachment}_file_name\"),\n                instance.send(\"#{attachment}_content_type\"),\n                instance.send(\"#{attachment}_file_size\"),\n                checksum(instance.send(attachment)),\n                instance.updated_at.iso8601\n              ])\n\n            ActiveRecord::Base.connection.execute_prepared(\n              'active_storage_attachment_statement', [\n                attachment,\n                model.name,\n                instance.id,\n                instance.updated_at.iso8601,\n              ])\n          end\n        end\n      end\n    end\n  end\n\n  def down\n    raise ActiveRecord::IrreversibleMigration\n  end\n\n  private\n\n  def key(instance, attachment)\n    SecureRandom.uuid\n    # Alternatively:\n    # instance.send(\"#{attachment}_file_name\")\n  end\n\n  def checksum(attachment)\n    # local files stored on disk:\n    url = attachment.path\n    Digest::MD5.base64digest(File.read(url))\n\n    # remote files stored on another person's computer:\n    # url = attachment.url\n    # Digest::MD5.base64digest(Net::HTTP.get(URI(url)))\n  end\nend\n```\n\n## Copy the files over\n\nThe above migration leaves the files as they are. However, the default\nPaperclip and ActiveStorage storage services use different locations.\n\nBy default, Paperclip looks like this:\n\n```\npublic/system/users/avatars/000/000/004/original/the-mystery-of-life.png\n```\n\nAnd ActiveStorage looks like this:\n\n```\nstorage/xM/RX/xMRXuT6nqpoiConJFQJFt6c9\n```\n\nThat `xMRXuT6nqpoiConJFQJFt6c9` is the `active_storage_blobs.key` value. In the\nmigration above we simply used the filename but you may wish to use a UUID\ninstead.\n\n\n### Moving local storage files\n\n```ruby\n#!bin/rails runner\n\nActiveStorage::Attachment.find_each do |attachment|\n  name = attachment.name\n\n  source = attachment.record.send(name).path\n  dest_dir = File.join(\n    \"storage\",\n    attachment.blob.key.first(2),\n    attachment.blob.key.first(4).last(2))\n  dest = File.join(dest_dir, attachment.blob.key)\n\n  FileUtils.mkdir_p(dest_dir)\n  puts \"Moving #{source} to #{dest}\"\n  FileUtils.cp(source, dest)\nend\n```\n\n### Moving files on a remote host (S3, Azure Storage, GCS, etc.)\n\nOne of the most straightforward ways to move assets stored on a remote host is\nto use a rake task that regenerates the file names and places them in the\nproper file structure/hierarchy.\n\nAssuming you have a model configured similarly to the example below:\n\n```ruby\nclass Organization < ApplicationRecord\n  # New ActiveStorage declaration\n  has_one_attached :logo\n\n  # Old Paperclip config\n  # must be removed BEFORE to running the rake task so that\n  # all of the new ActiveStorage goodness can be used when\n  # calling organization.logo\n  has_attached_file :logo,\n                    path: \"/organizations/:id/:basename_:style.:extension\",\n                    default_url: \"https://s3.amazonaws.com/xxxxx/organizations/missing_:style.jpg\",\n                    default_style: :normal,\n                    styles: { thumb: \"64x64#\", normal: \"400x400>\" },\n                    convert_options: { thumb: \"-quality 100 -strip\", normal: \"-quality 75 -strip\" }\nend\n```\n\nThe following rake task would migrate all of your assets:\n\n```ruby\nnamespace :organizations do\n  task migrate_to_active_storage: :environment do\n    Organization.where.not(logo_file_name: nil).find_each do |organization|\n      # This step helps us catch any attachments we might have uploaded that\n      # don't have an explicit file extension in the filename\n      image = organization.logo_file_name\n      ext = File.extname(image)\n      image_original = CGI.unescape(image.gsub(ext, \"_original#{ext}\"))\n\n      # this url pattern can be changed to reflect whatever service you use\n      logo_url = \"https://s3.amazonaws.com/xxxxx/organizations/#{organization.id}/#{image_original}\"\n      organization.logo.attach(io: open(logo_url),\n                                   filename: organization.logo_file_name,\n                                   content_type: organization.logo_content_type)\n    end\n  end\nend\n```\n\nAn added advantage of this method is that you're creating a copy of all assets,\nwhich is handy in the event you need to rollback your deploy.\n\nThis also means that you can run the rake task from your development machine\nand completely migrate the assets before your deploy, minimizing the chances\nthat you'll have a timed-out deployment.\n\nThe main drawback of this method is the same as its benefit - you are\nessentially duplicating all of your assets. These days storage and bandwidth\nare relatively cheap, but in some instances where you have a huge volume of\nfiles, or very large file sizes, this might get a little less feasible.\n\nIn my experience I was able to move tens of thousands of images in a matter of\na couple of hours, just by running the migration overnight on my MacBook Pro.\n\nOnce you've confirmed that the migration and deploy have gone successfully you\ncan safely delete the old assets from your remote host.\n\n## Update your tests\n\nInstead of the `have_attached_file` matcher, you'll need to write your own.\nHere's one that is similar in spirit to the Paperclip-supplied matcher:\n\n```ruby\nRSpec::Matchers.define :have_attached_file do |name|\n  match do |record|\n    file = record.send(name)\n    file.respond_to?(:variant) && file.respond_to?(:attach)\n  end\nend\n```\n\nIf you were using a Factory or a Fixture that set the Paperclip-generated\ncolumns' values directly, you'll likely need to attach the Files instead.\n\nFor example, you could replace a `FactoryBot` factory definition's Paperclip\nattributes with File I/O using\n[`ActiveSupport::Testing::FixtureFiles#file_fixture`][file-fixture]:\n\n```diff\nfactory :user do\n  trait :with_avatar do\n-    avatar_file_name { \"avatar.jpg\" }\n-    avatar_file_type { \"image/jpg\" }\n-    avatar_file_size { 1024 }\n+   transient do\n+     avatar_file { file_fixture(\"avatar.jpg\") }\n+\n+     after :build do |user, evaluator|\n+       user.avatar.attach(\n+         io: evaluator.avatar_file.open,\n+         filename: evaluator.avatar_file.basename.to_s,\n+       )\n+     end\n+   end\n  end\nend\n```\n\n[file-fixture]: https://api.rubyonrails.org/v5.2/classes/ActiveSupport/Testing/FileFixtures.html\n\n## Update your views\n\nIn Paperclip it looks like this:\n\n```ruby\nimage_tag @user.avatar.url(:medium)\n```\n\nIn ActiveStorage it looks like this:\n\n```ruby\nimage_tag @user.avatar.variant(resize: \"250x250\")\n```\n\n## Update your controllers\n\nThis should _require_ no update. However, if you glance back at the database\nschema above, you may notice a join.\n\nFor example, if your controller has\n\n```ruby\ndef index\n  @users = User.all.order(:name)\nend\n```\n\nAnd your view has\n\n```\n<ul>\n  <% @users.each do |user| %>\n    <li><%= image_tag user.avatar.variant(resize: \"10x10\"), alt: user.name %></li>\n  <% end %>\n</ul>\n```\n\nThen you'll end up with an n+1 as you load each attachment in the loop.\n\nSo while the controller and model will work without change, you will want to\ndouble-check your loops and add `includes` as needed.\n\nActiveStorage automatically declares `ActiveStorage::Attachment` and\n`ActiveStorage::Blob` relationships to your models, along with eager-loading\nscopes.\n\nFor example, a `has_one_attached :avatar` declaration will generate a `has_one\n:avatar_attachment` relationship along with a\n[`.with_attached_avatar`][has-one-eager-loading-scope] scope for eager loading\nattachments and blobs.\n\nA `has_many_attached :avatars` declaration will generate a `has_many\n:avatar_attachments` relationship along with a\n[`.with_attached_avatars`][has-many-eager-loading-scope] scope for eager loading\nattachments and blobs.\n\nWhen eager-loading transitive relationships, you'll need to specify the\nrelationship names directly, like `includes(avatar_attachment: :blob)` or\n`includes(avatar_attachments: :blob)`:\n\n```ruby\ndef index\n  @users = User.all.order(:name).includes(avatar_attachment: :blob)\nend\n```\n\n[has-one-eager-loading-scope]: https://api.rubyonrails.org/v5.2/classes/ActiveStorage/Attached/Macros.html#method-i-has_one_attached\n[has-many-eager-loading-scope]: https://api.rubyonrails.org/v5.2/classes/ActiveStorage/Attached/Macros.html#method-i-has_many_attached\n\n## Update your models\n\nFollow [the guide on attaching files to records]. For example, a `User` with an\n`avatar` is represented as:\n\n```ruby\nclass User < ApplicationRecord\n  has_one_attached :avatar\nend\n```\n\nAny resizing is done in the view as a variant.\n\n[the guide on attaching files to records]: https://guides.rubyonrails.org/v5.2/active_storage_overview.html#attaching-files-to-records\n\n### Validations\n\nUnlike Paperclip, [which shipped with built-in attachment\nvalidations][paperclip-validations], ActiveStorage does not have built-in support\nfor validating an attachment's content type or file size (which can be useful for\n[preventing content type spoofing][security-validations]).\n\nThere are alternatives that support some of Paperclip's file validations.\n\nFor instance, here are some changes you could make to migrate a\nPaperclip-enabled model to use validations provided by the [`file_validators`\ngem][file-validators]:\n\n\n```diff\nclass User < ApplicationRecord\n  # ...\n\n-  validates_attachment_content_type :avatar, content_type: /\\Aimage/\n-  validates_attachment_file_name :avatar, matches: /jpe?g\\z/\n+  validates :avatar, file_content_type: {\n+    allow: [\"image/jpeg\", \"image/png\"],\n+    if: -> { avatar.attached? },\n+  }\n```\n\n[paperclip-validations]: https://github.com/thoughtbot/paperclip/tree/v6.1.0#validations\n[security-validations]: https://github.com/thoughtbot/paperclip/tree/v6.1.0#security-validations\n[file-validators]: https://github.com/musaffa/file_validators/tree/v2.3.0#examples\n\n## Remove Paperclip\n\nMake sure to delete any files Paperclip was storing locally. You can also update\nyour version control to no longer ignore the directory.\n\nFor instance, if you're using Git, remove `public/system/` from your\n`.gitignore`.\n\n```diff\n  !.keep\n  /.bundle\n  /.byebug_history\n  /.tmp/*\n  /log/*\n- /public/system/\n  storage/\n```\n\nRemove the Gem from your `Gemfile` and run `bundle`. Run your tests because\nyou're done!\n"
  },
  {
    "path": "NEWS",
    "content": "6.1.0 (2018-07-27):\n\n* BUGFIX: Don't double-encode URLs (Roderick Monje).\n* BUGFIX: Only use the content_type when it exists (Jean-Philippe Doyle).\n* STABILITY: Better handling of the content-disposition header. Now supports\n  file name that is either enclosed or not in double quotes and is case\n  insensitive as per RC6266 grammar (Hasan Kumar, Yves Riel).\n* STABILITY: Change database column type of attachment file size from unsigned 4-byte\n  `integer` to unsigned 8-byte `bigint`. The former type limits attachment size\n  to just over 2GB, which can easily be exceeded by a large video file (Laurent\n  Arnoud, Alen Zamanyan).\n* STABILITY: Better error message when thumbnail processing errors (Hayden Ball).\n* STABILITY: Fix file linking issues around Windows (Akihiko Odaki).\n* STABILITY: Files without an extension will now be checked for spoofing attempts\n  (George Walters II).\n* STABILITY: Manually close Tempfiles when we are done with them (Erkki Eilonen).\n\n6.0.0 (2018-03-09):\n\n* Improvement: Depend only on `aws-sdk-s3` instead of `aws-sdk` (https://github.com/thoughtbot/paperclip/pull/2481)\n\n5.3.0 (2018-03-09):\n\n* Improvement: Use `FactoryBot` instead of `FactoryGirl` (https://github.com/thoughtbot/paperclip/pull/2501)\n* Improvement: README updates (https://github.com/thoughtbot/paperclip/pull/2411, https://github.com/thoughtbot/paperclip/pull/2433, https://github.com/thoughtbot/paperclip/pull/2374, https://github.com/thoughtbot/paperclip/pull/2417, https://github.com/thoughtbot/paperclip/pull/2536)\n* Improvement: Remove Ruby 2.4 deprecation warning (https://github.com/thoughtbot/paperclip/pull/2401)\n* Improvement: Rails 5 migration compatibility (https://github.com/thoughtbot/paperclip/pull/2470)\n* Improvement: Documentation around post processing (https://github.com/thoughtbot/paperclip/pull/2381)\n* Improvement: S3 hostname example documentation (https://github.com/thoughtbot/paperclip/pull/2379)\n* Bugfix: Allow paperclip to load in IRB (https://github.com/thoughtbot/paperclip/pull/2369)\n* Bugfix: MIME type detection (https://github.com/thoughtbot/paperclip/issues/2527)\n* Bugfix: Bad tempfile state after symlink failure (https://github.com/thoughtbot/paperclip/pull/2540)\n* Bugfix: Rewind file after Fog bucket creation (https://github.com/thoughtbot/paperclip/pull/2572)\n* Improvement: Use `Terrapin` instead of `Cocaine` (https://github.com/thoughtbot/paperclip/pull/2553)\n\n5.2.1 (2018-01-25):\n\n* Bugfix: Fix copying files on Windows. (#2532)\n\n5.2.0 (2018-01-23):\n\n* Security: Remove the automatic loading of URI adapters. Some of these\n  adapters can be specially crafted to expose your network topology. (#2435)\n* Bugfix: The rake task no longer rescues `Exception`. (#2476)\n* Bugfix: Handle malformed `Content-Disposition` headers (#2283)\n* Bugfix: The `:only_process` option works when passed a lambda again. (#2289)\n* Improvement: Added `:use_accelerate_endpoint` option when using S3 to enable\n  [Amazon S3 Transfer Acceleration](http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html)\n  (#2291)\n* Improvement: Make the fingerprint digest configurable per attachment. The\n  default remains MD5. Making this configurable means it can change in a future\n  version because it is not considered secure anymore against intentional file\n  corruption. For more info, see https://en.wikipedia.org/wiki/MD5#Security\n\n  You can change the digest used for an attachment by adding the\n  `:adapter_options` parameter to the `has_attached_file` options like this:\n  `has_attached_file :avatar, adapter_options: { hash_digest: Digest::SHA256 }`\n\n  Use the rake task to regenerate fingerprints with the new digest for a given\n  class. Note that this does **not** check the file integrity using the old\n  fingerprint. Run the following command to regenerate fingerprints for all\n  User attachments:\n  `CLASS=User rake paperclip:refresh:fingerprints`\n  You can optionally limit the attachment that will be processed, e.g:\n  `CLASS=User ATTACHMENT=avatar rake paperclip:refresh:fingerprints` (#2229)\n* Improvement: The new `frame_index` option on the thumbnail processor allows\n  you to select a specific frame from an animated upload to use as a thumbnail.\n  Initial support is for mkv, avi, MP4, mov, MPEG, and GIF. (#2155)\n* Improvement: Instead of copying files, use hard links. This is an\n  optimization. (#2120)\n* Improvement: S3 storage option `:s3_prefixes_in_alias`. (#2287)\n* Improvement: Fog option `:fog_public` can be a lambda. (#2302)\n* Improvement: One fewer warning on JRuby. (#2352)\n* Ruby 2.4.0 compatibility (doesn't use Fixnum anymore)\n\n5.1.0 (2016-08-19):\n\n* Add default `content_type_detector` to `UploadedFileAdapter` (#2270)\n* Default S3 protocol to empty string (#2038)\n* Don't write original file if it wasn't reprocessed (#1993)\n* Disallow trailing newlines in regular expressions (#2266)\n* Support for readbyte in Paperclip attachments (#2034)\n* (port from 4.3) Uri io adapter uses the content-disposition filename (#2250)\n* General refactors and documentation improvements\n\n5.0.0 (2016-07-01):\n\n* Improvement: Add `read_timeout` configuration for URI Adapter download_content method.\n* README adjustments for Ruby beginners (add links, elucidate model in Quick Start)\n* Bugfix: Now it's possible to save images from URLs with special characters [#1932]\n* Bugfix: Return false when file to copy is not present in cloud storage [#2173]\n* Automatically close file while checking mime type [#2016]\n* Add `read_timeout` option to `UriAdapter#download_content` method [#2232]\n* Fix a nil error in content type validation matcher [#1910]\n* Documentation improvements\n\n5.0.0.beta2 (2016-04-01):\n\n* Bugfix: Dynamic fog directory option is now respected\n* Bugfix: Fixes cocaine duplicated paths [#2169]\n* Removal of dead code (older versions of Rails and AWS SDK)\n* README adjustments\n\n5.0.0.beta1 (2016-03-13):\n\n* Bug Fix: megabytes of mime-types info in logs when a spoofed media type is detected.\n* Drop support to end-of-life'd ruby 2.0.\n* Drop support for end-of-life'd Rails 3.2 and 4.1\n* Drop support for AWS v1\n* Remove tests for JRuby and Rubinius from Travis CI (they were failing)\n* Improvement: Add `fog_options` configuration to send options to fog when\n  storing files.\n* Extracted repository for locales only:  https://github.com/thoughtbot/paperclip-i18n\n* Bugfix: Original file could be unlinked during `post_process_style`, producing failures\n* Bugfix for image magick scaling images up\n* Memory consumption improvements\n* `url` on a unpersisted record returns `default_url` rather than `nil`\n* Improvement: aws-sdk v2 support\n  https://github.com/thoughtbot/paperclip/pull/1903\n\n  If your Gemfile contains aws-sdk (>= 2.0.0) and aws-sdk-v1, paperclip will use\n  aws-sdk v2. With aws-sdk v2, S3 storage requires you to set the s3_region.\n  s3_region may be nested in s3_credentials, and (if not nested in\n  s3_credentials) it may be a Proc.\n\n4.3\n\nSee patch versions in v4.3 NEWS:\nhttps://github.com/thoughtbot/paperclip/blob/v4.3/NEWS\n\n4.3.0 (2015-06-18):\n\n* Improvement: Update aws-sdk and cucumber gem versions.\n* Improvement: Add `length` alias for `size` method in AbstractAdapter.\n* Improvement: Removed some cruft\n* Improvement: deep_merge! Attachment definitions\n* Improvement: Switch to mimemagic gem for content-type detection\n* Improvement: Allows multiple content types for spoof detector\n* Bug Fix: Don't assume we have Rails.env if we have Rails\n* Performance: Decrease Memory footprint\n* Ruby Versioning: Drop support for 1.9.3 (EOL'ed)\n* Rails Versioning: Drop support for 4.0.0 (EOL'ed)\n\n4.2.4 (2015-06-05):\n\n* Rollback backwards incompatible change, allowing paperclip to run on\n  Ruby >= 1.9.2.\n\n4.2.3:\n\n* Fix dependency specifications (didn't work with Rails 4.1)\n* Fix paperclip tests in CI\n\n4.2.2:\n\n* Security fix: Fix a potential security issue with spoofing\n\n4.2.1:\n\n* Improvement: Added `validate_media_type` options to allow/bypass spoof check\n* Improvement: Added incremental backoff when AWS gives us a SlowDown error.\n* Improvement: Stream downloads when usign aws-sdk.\n* Improvement: Documentation fixes, includes Windows instructions.\n* Improvement: Added pt-BR, zh-HK, zh-CN, zh-TW, and ja-JP locales.\n* Improvement: Better escaping for characters in URLs\n* Improvement: Honor `fog_credentials[:scheme]`\n* Improvement: Also look for custom processors in lib/paperclip\n* Improvement: id partitioning for string IDs works like integer id\n* Improvement: Can pass options to DB adapters in migrations\n* Improvement: Update expiring_url creation for later versions of fog\n* Improvement: `path` can be a Proc in S3 attachments\n* Test Fix: Improves speed and reliability of the specs\n* Bug Fix: #original_filename= does not error when passed `nil`\n\n4.2.0:\n\n* Improvement: Converted test suite from test/unit to RSpec\n* Improvement: Refactored Paperclip::Attachment#assign\n* Improvement: Added Spanish and German locales\n* Improvement: Required Validators accept validator subclasses\n* Improvement: EXIF orientation checking can be turned off for performance\n* Improvement: Documentation updates\n* Improvement: Better #human_size method for AttachmentSizeValidators\n* Bug Fix: Allow MIME-types with dots in them\n* Improvement: Travis CI updates\n* Improvement: Validators can take multiple messages\n* Improvement: Per-style options for S3 storage\n* Improvement: Allow `nil` geometry strings\n* Improvement: Use `eager_load!`\n\n4.1.1:\n\n* Improvement: Add default translations for spoof validation\n* Bug Fix: Don't check for spoofs if the file hasn't changed\n* Bug Fix: Callback chain terminator is different in Rails 4.1, remove warnings\n* Improvement: Fixed various Ruby warnings\n* Bug Fix: Give bundler a hint, so it doesn't run forever on a fresh bundle\n* Improvement: Documentation fixes\n* Improvement: Allow travis-ci to finish-fast\n\n\n4.1.0:\n\n* Improvement: Add :content_type_mappings to correct for missing spoof types\n* Improvement: Credit Egor Homakov with discovering the content_type spoof bug\n* Improvement: Memoize calls to identify in the thumbnail processor\n* Improvement: Make MIME type optional for Data URIs.\n* Improvement: Add default format for styles\n\n4.0.0:\n\n* Security: Attachments are checked to make sure they're not pulling a fast one.\n* Security: It is now *enforced* that every attachment has a file/mime validation.\n* Bug Fix: Removed a call to IOAdapter#close that was causing issues.\n* Improvement: Added bullets to the 3.5.3 list of changes. Very important.\n* Improvement: Updated the copyright to 2014\n\n3.5.3:\n\n* Improvement: After three long, hard years... we know how to upgrade\n* Bug Fix: #expiring_url returns 'missing' urls if nothing is attached\n* Improvement: Lots of documentation fixes\n* Improvement: Lots of fixes for Ruby warnings\n* Improvement: Test the most appropriate Ruby/Rails comobinations on Travis\n* Improvement: Delegate more IO methods through IOAdapters\n* Improvement: Remove Rails 4 deprecations\n* Improvement: Both S3's and Fog's #expiring_url can take a Time or Int\n* Bug Fix: Both S3's and Fog's expiring_url respect style when missing the file\n* Bug Fix: Timefiles will have a reasonable-length name. They're all MD5 hashes now\n* Bug Fix: Don't delete files off S3 when reprocessing due to AWS inconsistencies\n* Bug Fix: \"swallow_stream\" isn't thread dafe. Use :swallow_stderr\n* Improvement: Regexps use \\A and \\Z instead of ^ and $\n* Improvement: :s3_credentials can take a lambda as an argument\n* Improvement: Search up the class heirarchy for attachments\n* Improvement: deep_merge options instead of regular merge\n* Bug Fix: Prevent file deletion on transaction rollback\n* Test Improvement: Ensure more files are properly closed during tests\n* Test Bug Fix: Return the gemfile's syntax to normal\n\n3.5.2:\n\n* Security: Force cocaine to at least 0.5.3 to include a security fix\n* Improvement: Fixed some README exmaples\n* Feature: Added HTTP URL Proxy Adapter, can assign string URLs as attachments\n* Improvement: Put validation errors on the base attribute and the sub-attribute\n\n3.5.1:\n\n* Bug Fix: Returned the class-level `attachment_definitions` method for compatability.\n* Improvement: Ensured compatability with Rails 4\n* Improvement: Added Rails 4 to the Appraisals\n* Bug Fix: #1296, where validations were generating errors\n* Improvement: Specify MIT license in the gemspec\n\n3.5.0:\n\n* Feature: Handle Base64-encoded data URIs as uploads\n* Feature: Add a FilenameCleaner class to allow custom filename sanitation\n* Improvement: Satisfied Mocha deprecation warnings\n* Bug Fix: Allow empty string to be submitted and ignored, as some forms do this\n* Improvement: Make #expiring_url behavior consistent with #url\n* Bug Fix: \"Validate\" attachments without invoking AR's validations\n* Improvement: Various refactorings for a cleaner codebase\n* Improvement: Be agnostic, use ActiveModel when appropriate\n* Improvement: Add validation errors to the base attachment attribute\n* Improvement: Handle errors in rake tasks\n* Improvement: Largely refactor has_attached_file into a new class\n* Improvement: Added Ruby 2.0.0 as a supported platform and removed 1.8.7\n* Improvement: Fixed some incompatabilities in the test suite\n\n3.4.2:\n\n* Improvement: Use https for Gemfile urls\n* Improvement: Updated and more correct documentation\n* Improvement: Use the -optimize flag on animated GIFs\n* Improvement: Remove the Gemfile.lock\n* Improvement: Add #expiring_url as an alias for #url until the storage defines it\n* Improvement: Remove path clash checking, as it's unnecessary\n* Bug Fix: Do not rely on checking version numbers for aws-sdk\n\n3.4.1:\n\n* Improvement: Various documentation fixes and improvements\n* Bug Fix: Clearing an attachment with `preserve_files` on should still clear the attachment\n* Bug Fix: Instances are #changed? when a new file is assigned\n* Bug Fix: Correctly deal with S3 styles when using a lambda\n* Improvement: Accept and pass :credential_provider option to AWS-SDK\n* Bug Fix: Sanitize original_filename more correctly in IO Adapters\n* Improvement: s3_host_name can be a lambda\n* Improvement: Cache some interpolations for speed\n* Improvement: Update to latest cocaine\n* Improvement: Update copyrights, various typos\n\n3.4.0:\n\n* Bug Fix: Allow UploadedFileAdapter to force the use of `file`\n* Bug Fix: Close the file handle when dealing with URIs\n* Bug Fix: Ensure files are closed for writing when we're done.\n* Bug Fix: Fixed 'type' being nil on Windows 7 error.\n* Bug Fix: Fixed nil access when no s3 headers are defined\n* Bug Fix: Fixes auto_orientation\n* Bug Fix: Prevent a missing method error when switching from aws_sdk to fog\n* Bug Fix: Properly fail to process invalid attachments\n* Bug Fix: Server-side encryption is specified correctly\n* Bug Fix: fog_public returned to true by default\n* Bug Fix: Check attachment paths for duplicates, not URLs\n* Feature: Add Attachment#blank?\n* Feature: Add support for blacklisting certain content_types\n* Feature: Add support for style-specific s3 headers and meta data\n* Feature: Allow only_process to be a lambda\n* Feature: Allow setting of escape url as a default option\n* Feature: Create :override_file_permissions option for filesystem attachments\n* Improvement: Add Attachment#as_json\n* Improvement: Evaluate lambdas for fog_file properties\n* Improvement: Extract geometry parsing into factories\n* Improvement: Fixed various typos\n* Improvement: Refactored some tests\n* Improvement: Reuse S3 connections\n\nNew In 3.3.1:\n\n* Bug Fix: Moved Filesystem's copy_to_local_file to the right place.\n\n3.3.0:\n\n* Improvement: Upgrade cocaine to 0.4\n\n3.2.0:\n\n* Bug Fix: Use the new correct Amazon S3 encryption header.\n* Bug Fix: The rake task respects the updated_at column.\n* Bug Fix: Strip newline from content type.\n* Feature: Fog file visibility can be specified per style.\n* Feature: Automatically rotate images.\n* Feature: Reduce class-oriented programming of the attachment definitions.\n\n3.1.4:\n\n* Bug Fix: Allow user to be able to set path without `:style` attribute and not raising an error.\n  This is a regression introduced in 3.1.3, and that feature will be postponed to another minor\n  release instead.\n* Feature: Allow for URI Adapter as an optional paperclip io adapter.\n\n3.1.3:\n\n* Bug Fix: Copy empty attachment between instances is now working.\n* Bug Fix: Correctly rescue Fog error.\n* Bug Fix: Using default path and url options in Fog storage now work as expected.\n* Bug Fix: `Attachment#s3_protocol` now returns a protocol without colon suffix.\n* Feature: Paperclip will now raise an error if multiple styles are defined but no `:style`\n  interpolation exists in `:path`.\n* Feature: Add support for `#{attachment}_created_at` field\n* Bug Fix: Paperclip now gracefully handles msising file command.\n* Bug Fix: `StringIOAdapter` now accepts content type.\n\n3.1.2:\n\n* Bug Fix: #remove_attachment on 3.1.0 and 3.1.1 mistakenly trying to remove the column that has\n  the same name as data type (such as :string, :datetime, :interger.) You're advised to update to\n  Paperclip 3.1.2 as soon as possible.\n\n3.1.1:\n\n* Bug Fix: Paperclip will only load Paperclip::Schema only when Active Record is available.\n\n3.1.0:\n\n* Feature: Paperclip now support new migration syntax (sexy migration) that reads better:\n\n      class AddAttachmentToUsers < ActiveRecord::Migration\n        def self.up\n          create_table :users do |t|\n            t.attachment :avatar\n          end\n        end\n      end\n\n  Also, schema-definition level syntax has been added:\n\n      add_attachment :users, :avatar\n      remove_attachment :users, :avatar\n\n* Feature: Migration now support Rails 3.2+ `change` method.\n* API CHANGE: Old `t.has_attached_file` and `drop_attached_file` are now deprecated. You're advised\n  to update your migration file before the next MAJOR version.\n* Bug Fix: Tempfile now rewinded before generating fingerprint\n* API CHANGE: Tempfiles are now unlinked after `after_flush_writes`\n\n  If you need to interact with the generated tempfiles, please define an `after_flush_writes` method\n  in your model. You'll be able to access files via `@queue_for_write` instance variable.\n\n* Bug Fix: `:s3_protocol` can now be defined as either String or Symbol\n* Bug Fix: Tempfiles are now rewinded before get passed into `after_flush_writes`\n* Feature: Added expiring_url method to Fog Storage\n* API CHANGE: Paperclip now tested against AWS::SDK 1.5.2 onward\n* Bug Fix: Improved the output of the content_type validator so the actual failure is displayed\n* Feature: Animated formats now identified using ImageMagick.\n* Feature: AttachmentAdapter now support fetching attachment with specific style.\n* Feature: Paperclip default options can now be configured in Rails.configuration.\n* Feature: add Geometry#resize_to to calculate dimensions of new source.\n* Bug Fix: Fixed a bug whereby a file type with multiple mime types but no official type would cause\n  the best_content_type to throw an error on trying nil.content_type.\n* Bug Fix: Fix problem when the gem cannot be installed on the system that has Asepsis installed.\n\n3.0.4:\n\n* Feature: Adds support for S3 scheme-less URL generation.\n\n3.0.3:\n\n* Bug Fix: ThumbnailProcessor now correctly detects and preserve animated GIF.\n* Bug Fix: File extension is now preserved in generated Tempfile from adapter.\n* Bug Fix: Uploading file with unicode file name now won't raise an error when\n  logging in the AWS is turned on.\n* Bug Fix: Task \"paperclip:refresh:missing_styles\" now work correctly.\n* Bug Fix: Handle the case when :restricted_characters is nil.\n* Bug Fix: Don't delete all the existing styles if we reprocess.\n* Bug Fix: Content type is now ensured to not having a new line character.\n* API CHANGE: Non-Rails usage should include Paperclip::Glue directly.\n\n  `Paperclip::Railtie` was intended to be used with Ruby on Rails only. If you're\n  using Paperclip without Rails, you should include `Paperclip::Glue` into\n  `ActiveRecord::Base` instead of requiring `paperclip/railtie`:\n\n      ActiveRecord::Base.send :include, Paperclip::Glue\n\n* Bug Fix: AttachmentContentTypeValidator now allow you to specify :allow_blank/:allow_nil\n* Bug Fix: Make sure content type always a String.\n* Bug Fix: Fix attachment.reprocess! when using storage providers fog and s3.\n* Bug Fix: Fix a problem with incorrect content_type detected with 'file' command for an empty file on Mac.\n\n3.0.2:\n\n* API CHANGE: Generated migration class name is now plural (AddAttachmentToUsers instead of AddAttachmentToUser)\n* API CHANGE: Remove Rails plugin initialization code.\n* API CHANGE: Explicitly require Ruby 1.9.2 in the Gemfile.\n* Bug Fix: Fixes AWS::S3::Errors::RequestTimeout on Model#save.\n* Bug Fix: Fix a problem when there's no logger specified.\n* Bug Fix: Fix a problem when attaching Rack::Test::UploadedFile instance.\n\n3.0.1:\n\n* Feature: Introduce Paperlip IO adapter.\n* Bug Fix: Regression in AttachmentContentTypeValidator has been fixed.\n* API CHANGE: #to_file has been removed. Use the #copy_to_local_file method instead.\n\n3.0.0:\n\n* API CHANGE: Paperclip now requires at least Ruby on Rails version 3.0.0\n* API CHANGE: The default :url and :path have changed. The new scheme avoids\n  filesystem conflicts and scales to handle larger numbers of uploads.\n\n  The easiest way to upgrade is to add an explicit :url and :path to your\n  has_attached_file calls:\n\n      has_attached_file :avatar,\n        :path => \":rails_root/public/system/:attachment/:id/:style/:filename\",\n        :url => \"/system/:attachment/:id/:style/:filename\"\n\n* Feature: Adding Rails 3 style validators, and adding `validates_attachment` method as a shorthand.\n* Bug Fix: Paperclip's rake tasks now loading records in batch.\n* Bug Fix: Attachment style name with leading number now not raising an error.\n* Bug Fix: File given to S3 and Fog storage will now be rewinded after flush_write.\n* Feature: You can now pass addional parameter to S3 expiring URL, such as :content_type.\n\n2.7.0:\n\n* Bug Fix: Checking the existence of a file on S3 handles all AWS errors.\n* Bug Fix: Clear the fingerprint when removing an attachment.\n* Bug Fix: Attachment size validation message reads more nicely now.\n* Feature: Style names can be either symbols or strings.\n* Compatibility: Support for ActiveSupport < 2.3.12.\n* Compatibility: Support for Rails 3.2.\n\n2.6.0:\n\n* Bug Fix: Files are re-wound after reading.\n* Feature: Remove Rails dependency from specs that need Paperclip.\n* Feature: Validation matchers support conditionals.\n\n2.5.2:\n\n* Bug Fix: Can be installed on Windows.\n* Feature: The Fog bucket name, authentication, and host can be determined at runtime via Proc.\n* Feature: Special characters are replaced with underscores in #url and #path.\n\n2.5.1:\n\n* Feature: After we've computed the content type, pass it to Fog.\n* Feature: S3 encryption with the new :s3_server_side_encryption option.\n* Feature: Works without ActiveRecord, allowing for e.g. mongo backends.\n\n2.5.0:\n\n* Performance: Only connect to S3 when absolutely needed.\n* Bug Fix: STI with cached classes respect new options.\n* Bug Fix: conditional validations broke, and now work again.\n* Feature: URL generation is now parameterized and can be changed with plugins or custom code.\n* Feature: :convert_options and :source_file_options to control the ImageMagick processing.\n* Performance: String geometry specifications now parse more quickly.\n* Bug Fix: Handle files with question marks in the filename.\n* Bug Fix: Don't raise an error when generating an expiring URL on an unassigned attachment.\n* Bug Fix: The rake task runs over all instances of an ActiveRecord model, ignoring default scopes.\n* Feature: DB migration has_attached_file and drop_attached_file methods.\n* Bug Fix: Switch from AWS::S3 to AWS::SDK for the S3 backend.\n* Bug Fix: URL generator uses '?' in the URL unless it already appears and there is no prior '='.\n* Bug Fix: Always convert the content type to a string before stripping blanks.\n* Feature: The :keep_old_files option preserves the files in storage even when the attachment is cleared or changed.\n* Performance: Optimize Fog's public_url access by avoiding it when possible.\n* Bug Fix: Avoid a runtime error when generating the ID partition for an unsaved attachment.\n* Performance: Do not calculate the fingerprint if it is never persisted.\n* Bug Fix: Process the :original style before all others, in case of a dependency.\n* Feature: S3 headers can be set at runtime by passing a proc object as the value.\n* Bug Fix: Generating missing attachment styles for a model which has had its attachment changed should not raise.\n* Bug Fix: Do not collide with the built-in Ruby hashing method.\n"
  },
  {
    "path": "README.md",
    "content": "Paperclip\n=========\n\n# Deprecated\n\n**[Paperclip is deprecated]**.\n\nFor new projects, we recommend Rails' own [ActiveStorage].\n\nFor existing projects, please consult and contribute to the migration guide,\navailable [in English], [en español], and as [a video] recorded at RailsConf\n2019. You may also prefer [an alternative migration tutorial used by\nDoorkeeper][].\n\nAlternatively, for existing projects, [Kreeti] is maintaining [kt-paperclip],\nan ongoing [fork of Paperclip].\n\nWe will leave the Issues open as a discussion forum _only_. We do _not_\nguarantee a response from us in the Issues. All bug reports should go to\nkt-paperclip.\n\nWe are no longer accepting pull requests _except_ pull requests against the\nmigration guide. All other pull requests will be closed without merging.\n\n[Paperclip is deprecated]: https://robots.thoughtbot.com/closing-the-trombone\n[ActiveStorage]: http://guides.rubyonrails.org/active_storage_overview.html\n[in English]: https://github.com/thoughtbot/paperclip/blob/master/MIGRATING.md\n[en español]: https://github.com/thoughtbot/paperclip/blob/master/MIGRATING-ES.md\n[a video]: https://www.youtube.com/watch?v=tZ_WNUytO9o\n[Kreeti]: https://www.kreeti.com/\n[kt-paperclip]: https://rubygems.org/gems/kt-paperclip\n[fork of Paperclip]: https://github.com/kreeti/kt-paperclip\n[an alternative migration tutorial used by Doorkeeper]: https://www.tokyodev.com/2021/03/23/paperclip-activestorage/\n\n# Existing documentation\n\n## Documentation valid for `master` branch\n\nPlease check the documentation for the paperclip version you are using:\nhttps://github.com/thoughtbot/paperclip/releases\n\n---\n\n[![Build Status](https://secure.travis-ci.org/thoughtbot/paperclip.svg?branch=master)](http://travis-ci.org/thoughtbot/paperclip)\n[![Dependency Status](https://gemnasium.com/thoughtbot/paperclip.svg?travis)](https://gemnasium.com/thoughtbot/paperclip)\n[![Code Climate](https://codeclimate.com/github/thoughtbot/paperclip.svg)](https://codeclimate.com/github/thoughtbot/paperclip)\n[![Inline docs](http://inch-ci.org/github/thoughtbot/paperclip.svg)](http://inch-ci.org/github/thoughtbot/paperclip)\n[![Security](https://hakiri.io/github/thoughtbot/paperclip/master.svg)](https://hakiri.io/github/thoughtbot/paperclip/master)\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Requirements](#requirements)\n  - [Ruby and Rails](#ruby-and-rails)\n  - [Image Processor](#image-processor)\n  - [`file`](#file)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n  - [Models](#models)\n  - [Migrations](#migrations)\n  - [Edit and New Views](#edit-and-new-views)\n  - [Edit and New Views with Simple Form](#edit-and-new-views-with-simple-form)\n  - [Controller](#controller)\n  - [View Helpers](#view-helpers)\n  - [Checking a File Exists](#checking-a-file-exists)\n  - [Deleting an Attachment](#deleting-an-attachment)\n- [Usage](#usage)\n- [Validations](#validations)\n- [Internationalization (I18n)](#internationalization-i18n)\n- [Security Validations](#security-validations)\n- [Defaults](#defaults)\n- [Migrations](#migrations-1)\n  - [Add Attachment Column To A Table](#add-attachment-column-to-a-table)\n  - [Schema Definition](#schema-definition)\n  - [Vintage Syntax](#vintage-syntax)\n- [Storage](#storage)\n  - [Understanding Storage](#understanding-storage)\n- [IO Adapters](#io-adapters)\n- [Post Processing](#post-processing)\n- [Custom Attachment Processors](#custom-attachment-processors)\n- [Events](#events)\n- [URI Obfuscation](#uri-obfuscation)\n- [Checksum / Fingerprint](#checksum--fingerprint)\n- [File Preservation for Soft-Delete](#file-preservation-for-soft-delete)\n- [Dynamic Configuration](#dynamic-configuration)\n  - [Dynamic Styles:](#dynamic-styles)\n  - [Dynamic Processors:](#dynamic-processors)\n- [Logging](#logging)\n- [Deployment](#deployment)\n  - [Attachment Styles](#attachment-styles)\n- [Testing](#testing)\n- [Contributing](#contributing)\n- [License](#license)\n- [About thoughtbot](#about-thoughtbot)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\nPaperclip is intended as an easy file attachment library for ActiveRecord. The\nintent behind it was to keep setup as easy as possible and to treat files as\nmuch like other attributes as possible. This means they aren't saved to their\nfinal locations on disk, nor are they deleted if set to nil, until\nActiveRecord::Base#save is called. It manages validations based on size and\npresence, if required. It can transform its assigned image into thumbnails if\nneeded, and the prerequisites are as simple as installing ImageMagick (which,\nfor most modern Unix-based systems, is as easy as installing the right\npackages). Attached files are saved to the filesystem and referenced in the\nbrowser by an easily understandable specification, which has sensible and\nuseful defaults.\n\nSee the documentation for `has_attached_file` in [`Paperclip::ClassMethods`](http://www.rubydoc.info/gems/paperclip/Paperclip/ClassMethods) for\nmore detailed options.\n\nThe complete [RDoc](http://www.rubydoc.info/gems/paperclip) is online.\n\n---\n\nRequirements\n------------\n\n### Ruby and Rails\n\nPaperclip now requires Ruby version **>= 2.1** and Rails version **>= 4.2**\n(only if you're going to use Paperclip with Ruby on Rails).\n\n### Image Processor\n\n[ImageMagick](http://www.imagemagick.org) must be installed and Paperclip must have access to it. To ensure\nthat it does, on your command line, run `which convert` (one of the ImageMagick\nutilities). This will give you the path where that utility is installed. For\nexample, it might return `/usr/local/bin/convert`.\n\nThen, in your environment config file, let Paperclip know to look there by adding that\ndirectory to its path.\n\nIn development mode, you might add this line to `config/environments/development.rb)`:\n\n```ruby\nPaperclip.options[:command_path] = \"/usr/local/bin/\"\n```\n\nIf you're on Mac OS X, you'll want to run the following with [Homebrew](http://www.brew.sh):\n\n    brew install imagemagick\n\nIf you are dealing with pdf uploads or running the test suite, you'll also need\nto install GhostScript. On Mac OS X, you can also install that using Homebrew:\n\n    brew install gs\n\nIf you are on Ubuntu (or any Debian base Linux distribution), you'll want to run\nthe following with apt-get:\n\n    sudo apt-get install imagemagick -y\n\n### `file`\n\nThe Unix [`file` command](https://en.wikipedia.org/wiki/File_(command)) is required for content-type checking.\nThis utility isn't available in Windows, but comes bundled with Ruby [Devkit](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit),\nso Windows users must make sure that the devkit is installed and added to the system `PATH`.\n\n**Manual Installation**\n\nIf you're using Windows 7+ as a development environment, you may need to install the `file.exe` application manually. The `file spoofing` system in Paperclip 4+ relies on this; if you don't have it working, you'll receive `Validation failed: Upload file has an extension that does not match its contents.` errors.\n\nTo manually install, you should perform the following:\n\n> **Download & install `file` from [this URL](http://gnuwin32.sourceforge.net/packages/file.htm)**\n\nTo test, you can use the image below:\n![untitled](https://cloud.githubusercontent.com/assets/1104431/4524452/a1f8cce4-4d44-11e4-872e-17adb96f79c9.png)\n\nNext, you need to integrate with your environment - preferably through the `PATH` variable, or by changing your `config/environments/development.rb` file\n\n**PATH**\n\n    1. Click \"Start\"\n    2. On \"Computer\", right-click and select \"Properties\"\n    3. In Properties, select \"Advanced System Settings\"\n    4. Click the \"Environment Variables\" button\n    5. Locate the \"PATH\" var - at the end, add the path to your newly installed `file.exe` (typically `C:\\Program Files (x86)\\GnuWin32\\bin`)\n    6. Restart any CMD shells you have open & see if it works\n\nOR\n\n**Environment**\n\n    1. Open `config/environments/development.rb`\n    2. Add the following line: `Paperclip.options[:command_path] = 'C:\\Program Files (x86)\\GnuWin32\\bin'`\n    3. Restart your Rails server\n\nEither of these methods will give your Rails setup access to the `file.exe` functionality, thus providing the ability to check the contents of a file (fixing the spoofing problem)\n\n---\n\nInstallation\n------------\n\nPaperclip is distributed as a gem, which is how it should be used in your app.\n\nInclude the gem in your Gemfile:\n\n```ruby\ngem \"paperclip\", \"~> 6.0.0\"\n```\n\nOr, if you want to get the latest, you can get master from the main paperclip repository:\n\n```ruby\ngem \"paperclip\", git: \"git://github.com/thoughtbot/paperclip.git\"\n```\n\nIf you're trying to use features that don't seem to be in the latest released gem, but are\nmentioned in this README, then you probably need to specify the master branch if you want to\nuse them. This README is probably ahead of the latest released version if you're reading it\non GitHub.\n\nFor Non-Rails usage:\n\n```ruby\nclass ModuleName < ActiveRecord::Base\n  include Paperclip::Glue\n  ...\nend\n```\n\n---\n\nQuick Start\n-----------\n\n### Models\n\n```ruby\nclass User < ActiveRecord::Base\n  has_attached_file :avatar, styles: { medium: \"300x300>\", thumb: \"100x100>\" }, default_url: \"/images/:style/missing.png\"\n  validates_attachment_content_type :avatar, content_type: /\\Aimage\\/.*\\z/\nend\n```\n\n### Migrations\n\n\nAssuming you have a `users` table, add an `avatar` column to the `users` table:\n```ruby\nclass AddAvatarColumnsToUsers < ActiveRecord::Migration\n  def up\n    add_attachment :users, :avatar\n  end\n\n  def down\n    remove_attachment :users, :avatar\n  end\nend\n```\n\n(Or you can use the Rails migration generator: `rails generate paperclip user avatar`)\n\n### Edit and New Views\nMake sure you have corresponding methods in your controller:\n```erb\n<%= form_for @user, url: users_path, html: { multipart: true } do |form| %>\n  <%= form.file_field :avatar %>\n  <%= form.submit %>\n<% end %>\n```\n\n### Edit and New Views with [Simple Form](https://github.com/plataformatec/simple_form)\n\n```erb\n<%= simple_form_for @user, url: users_path do |form| %>\n  <%= form.input :avatar, as: :file %>\n  <%= form.submit %>\n<% end %>\n```\n\n### Controller\n\n```ruby\ndef create\n  @user = User.create(user_params)\nend\n\nprivate\n\n# Use strong_parameters for attribute whitelisting\n# Be sure to update your create() and update() controller methods.\n\ndef user_params\n  params.require(:user).permit(:avatar)\nend\n```\n\n### View Helpers\nAdd these to the view where you want your images displayed:\n```erb\n<%= image_tag @user.avatar.url %>\n<%= image_tag @user.avatar.url(:medium) %>\n<%= image_tag @user.avatar.url(:thumb) %>\n```\n\n### Checking a File Exists\n\nThere are two methods for checking if a file exists:\n\n- `file?` and `present?` checks if the `_file_name` field is populated\n- `exists?` checks if the file exists (will perform a TCP connection if stored in the cloud)\n\nKeep this in mind if you are checking if files are present in a loop. The first\nversion is significantly more performant, but has different semantics.\n\n### Deleting an Attachment\n\nSet the attribute to `nil` and save.\n\n```ruby\n@user.avatar = nil\n@user.save\n```\n---\n\nUsage\n-----\n\nThe basics of Paperclip are quite simple: Declare that your model has an\nattachment with the `has_attached_file` method, and give it a name.\n\nPaperclip will wrap up to four attributes (all prefixed with that attachment's name,\nso you can have multiple attachments per model if you wish) and give them a\nfriendly front end. These attributes are:\n\n* `<attachment>_file_name`\n* `<attachment>_file_size`\n* `<attachment>_content_type`\n* `<attachment>_updated_at`\n\nBy default, only `<attachment>_file_name` is required for Paperclip to operate.\nYou'll need to add `<attachment>_content_type` in case you want to use content type\nvalidation.\n\nMore information about the options passed to `has_attached_file` is available in the\ndocumentation of [`Paperclip::ClassMethods`](http://www.rubydoc.info/gems/paperclip/Paperclip/ClassMethods).\n\nValidations\n-----------\n\nFor validations, Paperclip introduces several validators to validate your attachment:\n\n* `AttachmentContentTypeValidator`\n* `AttachmentPresenceValidator`\n* `AttachmentSizeValidator`\n\nExample Usage:\n\n```ruby\nvalidates :avatar, attachment_presence: true\nvalidates_with AttachmentPresenceValidator, attributes: :avatar\nvalidates_with AttachmentSizeValidator, attributes: :avatar, less_than: 1.megabytes\n\n```\n\nValidators can also be defined using the old helper style:\n\n* `validates_attachment_presence`\n* `validates_attachment_content_type`\n* `validates_attachment_size`\n\nExample Usage:\n\n```ruby\nvalidates_attachment_presence :avatar\n```\n\nLastly, you can also define multiple validations on a single attachment using `validates_attachment`:\n\n```ruby\nvalidates_attachment :avatar, presence: true,\n  content_type: \"image/jpeg\",\n  size: { in: 0..10.kilobytes }\n```\n\n_NOTE: Post-processing will not even **start** if the attachment is not valid\naccording to the validations. Your callbacks and processors will **only** be\ncalled with valid attachments._\n\n```ruby\nclass Message < ActiveRecord::Base\n  has_attached_file :asset, styles: { thumb: \"100x100#\" }\n\n  before_post_process :skip_for_audio\n\n  def skip_for_audio\n    ! %w(audio/ogg application/ogg).include?(asset_content_type)\n  end\nend\n```\n\nIf you have other validations that depend on assignment order, the recommended\ncourse of action is to prevent the assignment of the attachment until\nafterwards, then assign manually:\n\n```ruby\nclass Book < ActiveRecord::Base\n  has_attached_file :document, styles: { thumbnail: \"60x60#\" }\n  validates_attachment :document, content_type: \"application/pdf\"\n  validates_something_else # Other validations that conflict with Paperclip's\nend\n\nclass BooksController < ApplicationController\n  def create\n    @book = Book.new(book_params)\n    @book.document = params[:book][:document]\n    @book.save\n    respond_with @book\n  end\n\n  private\n\n  def book_params\n    params.require(:book).permit(:title, :author)\n  end\nend\n```\n\n**A note on content_type validations and security**\n\nYou should ensure that you validate files to be only those MIME types you\nexplicitly want to support.  If you don't, you could be open to\n<a href=\"https://www.owasp.org/index.php/Testing_for_Stored_Cross_site_scripting_(OWASP-DV-002)\">XSS attacks</a>\nif a user uploads a file with a malicious HTML payload.\n\nIf you're only interested in images, restrict your allowed content_types to\nimage-y ones:\n\n```ruby\nvalidates_attachment :avatar,\n  content_type: [\"image/jpeg\", \"image/gif\", \"image/png\"]\n```\n\n`Paperclip::ContentTypeDetector` will attempt to match a file's extension to an\ninferred content_type, regardless of the actual contents of the file.\n\n---\n\nInternationalization (I18n)\n---------------------------\n\nFor using or adding locale files in different languages, check the project\nhttps://github.com/thoughtbot/paperclip-i18n.\n\nSecurity Validations\n====================\n\nThanks to a report from [Egor Homakov](http://homakov.blogspot.com/) we have\ntaken steps to prevent people from spoofing Content-Types and getting data\nyou weren't expecting onto your server.\n\nNOTE: Starting at version 4.0.0, all attachments are *required* to include a\ncontent_type validation, a file_name validation, or to explicitly state that\nthey're not going to have either. *Paperclip will raise an error* if you do not\ndo this.\n\n```ruby\nclass ActiveRecord::Base\n  has_attached_file :avatar\n  # Validate content type\n  validates_attachment_content_type :avatar, content_type: /\\Aimage/\n  # Validate filename\n  validates_attachment_file_name :avatar, matches: [/png\\z/, /jpe?g\\z/]\n  # Explicitly do not validate\n  do_not_validate_attachment_file_type :avatar\nend\n```\n\nThis keeps Paperclip secure-by-default, and will prevent people trying to mess\nwith your filesystem.\n\nNOTE: Also starting at version 4.0.0, Paperclip has another validation that\ncannot be turned off. This validation will prevent content type spoofing. That\nis, uploading a PHP document (for example) as part of the EXIF tags of a\nwell-formed JPEG. This check is limited to the media type (the first part of the\nMIME type, so, 'text' in `text/plain`). This will prevent HTML documents from\nbeing uploaded as JPEGs, but will not prevent GIFs from being uploaded with a\n`.jpg` extension. This validation will only add validation errors to the form. It\nwill not cause errors to be raised.\n\nThis can sometimes cause false validation errors in applications that use custom\nfile extensions. In these cases you may wish to add your custom extension to the\nlist of content type mappings by creating `config/initializers/paperclip.rb`:\n\n```ruby\n# Allow \".foo\" as an extension for files with the MIME type \"text/plain\".\nPaperclip.options[:content_type_mappings] = {\n  foo: %w(text/plain)\n}\n```\n\n---\n\nDefaults\n--------\nGlobal defaults for all your Paperclip attachments can be defined by changing the Paperclip::Attachment.default_options Hash. This can be useful for setting your default storage settings per example so you won't have to define them in every `has_attached_file` definition.\n\nIf you're using Rails, you can define a Hash with default options in `config/application.rb` or in any of the `config/environments/*.rb` files on config.paperclip_defaults. These will get merged into `Paperclip::Attachment.default_options` as your Rails app boots. An example:\n\n```ruby\nmodule YourApp\n  class Application < Rails::Application\n    # Other code...\n\n    config.paperclip_defaults = { storage: :fog, fog_credentials: { provider: \"Local\", local_root: \"#{Rails.root}/public\"}, fog_directory: \"\", fog_host: \"localhost\"}\n  end\nend\n```\n\nAnother option is to directly modify the `Paperclip::Attachment.default_options` Hash - this method works for non-Rails applications or is an option if you prefer to place the Paperclip default settings in an initializer.\n\nAn example Rails initializer would look something like this:\n\n```ruby\nPaperclip::Attachment.default_options[:storage] = :fog\nPaperclip::Attachment.default_options[:fog_credentials] = { provider: \"Local\", local_root: \"#{Rails.root}/public\"}\nPaperclip::Attachment.default_options[:fog_directory] = \"\"\nPaperclip::Attachment.default_options[:fog_host] = \"http://localhost:3000\"\n```\n---\n\nMigrations\n----------\n\nPaperclip defines several migration methods which can be used to create the necessary columns in your\nmodel. There are two types of helper methods to aid in this, as follows:\n\n### Add Attachment Column To A Table\n\nThe `attachment` helper can be used when creating a table:\n\n```ruby\nclass CreateUsersWithAttachments < ActiveRecord::Migration\n  def up\n    create_table :users do |t|\n      t.attachment :avatar\n    end\n  end\n\n  # This is assuming you are only using the users table for Paperclip attachment. Drop with care!\n  def down\n    drop_table :users\n  end\nend\n```\n\nYou can also use the `change` method, instead of the `up`/`down` combination above, as shown below:\n\n```ruby\nclass CreateUsersWithAttachments < ActiveRecord::Migration\n  def change\n    create_table :users do |t|\n      t.attachment :avatar\n    end\n  end\nend\n```\n\n### Schema Definition\n\nAlternatively, the `add_attachment` and `remove_attachment` methods can be used to add new Paperclip columns to an existing table:\n\n```ruby\nclass AddAttachmentColumnsToUsers < ActiveRecord::Migration\n  def up\n    add_attachment :users, :avatar\n  end\n\n  def down\n    remove_attachment :users, :avatar\n  end\nend\n```\n\nOr you can do this with the `change` method:\n\n```ruby\nclass AddAttachmentColumnsToUsers < ActiveRecord::Migration\n  def change\n    add_attachment :users, :avatar\n  end\nend\n```\n\n### Vintage Syntax\n\nVintage syntax (such as `t.has_attached_file` and `drop_attached_file`) is still supported in\nPaperclip 3.x, but you're advised to update those migration files to use this new syntax.\n\n---\n\nStorage\n-------\n\nPaperclip ships with 3 storage adapters:\n\n* File Storage\n* S3 Storage (via `aws-sdk-s3`)\n* Fog Storage\n\nIf you would like to use Paperclip with another storage, you can install these\ngems along side with Paperclip:\n\n* [paperclip-azure](https://github.com/supportify/paperclip-azure)\n* [paperclip-azure-storage](https://github.com/gmontard/paperclip-azure-storage)\n* [paperclip-dropbox](https://github.com/janko-m/paperclip-dropbox)\n\n### Understanding Storage\n\nThe files that are assigned as attachments are, by default, placed in the\ndirectory specified by the `:path` option to `has_attached_file`. By default, this\nlocation is `:rails_root/public/system/:class/:attachment/:id_partition/:style/:filename`.\nThis location was chosen because, on standard Capistrano deployments, the\n`public/system` directory can be symlinked to the app's shared directory, meaning it\nsurvives between deployments. For example, using that `:path`, you may have a\nfile at\n\n    /data/myapp/releases/20081229172410/public/system/users/avatar/000/000/013/small/my_pic.png\n\n_**NOTE**: This is a change from previous versions of Paperclip, but is overall a\nsafer choice for the default file store._\n\nYou may also choose to store your files using Amazon's S3 service. To do so, include\nthe `aws-sdk-s3` gem in your Gemfile:\n\n```ruby\ngem 'aws-sdk-s3'\n```\n\nAnd then you can specify using S3 from `has_attached_file`.\nYou can find more information about configuring and using S3 storage in\n[the `Paperclip::Storage::S3` documentation](http://www.rubydoc.info/gems/paperclip/Paperclip/Storage/S3).\n\nFiles on the local filesystem (and in the Rails app's public directory) will be\navailable to the internet at large. If you require access control, it's\npossible to place your files in a different location. You will need to change\nboth the `:path` and `:url` options in order to make sure the files are unavailable\nto the public. Both `:path` and `:url` allow the same set of interpolated\nvariables.\n\n---\n\nIO Adapters\n-----------\n\nWhen a file is uploaded or attached, it can be in one of a few different input\nforms, from Rails' UploadedFile object to a StringIO to a Tempfile or even a\nsimple String that is a URL that points to an image.\n\nPaperclip will accept, by default, many of these sources. It also is capable of\nhandling even more with a little configuration. The IO Adapters that handle\nimages from non-local sources are not enabled by default. They can be enabled by\nadding a line similar to the following into `config/initializers/paperclip.rb`:\n\n```ruby\nPaperclip::DataUriAdapter.register\n```\n\nIt's best to only enable a remote-loading adapter if you need it. Otherwise\nthere's a chance that someone can gain insight into your internal network\nstructure using it as a vector.\n\nThe following adapters are *not* loaded by default:\n\n* `Paperclip::UriAdapter` - which accepts a `URI` instance.\n* `Paperclip::HttpUrlProxyAdapter` - which accepts a `http` string.\n* `Paperclip::DataUriAdapter` - which accepts a Base64-encoded `data:` string.\n\n---\n\nPost Processing\n---------------\n\nPaperclip supports an extensible selection of post-processors. When you define\na set of styles for an attachment, by default it is expected that those\n\"styles\" are actually \"thumbnails.\" These are processed by\n`Paperclip::Thumbnail`.  For backward compatibility reasons you can pass either\na single geometry string, or an array containing a geometry and a format that\nthe file will be converted to, like so:\n\n```ruby\nhas_attached_file :avatar, styles: { thumb: [\"32x32#\", :png] }\n```\n\nThis will convert the \"thumb\" style to a 32x32 square in PNG format, regardless\nof what was uploaded. If the format is not specified, it is kept the same (e.g.\nJPGs will remain JPGs). `Paperclip::Thumbnail` uses ImageMagick to process\nimages; [ImageMagick's geometry documentation](http://www.imagemagick.org/script/command-line-processing.php#geometry)\nhas more information on the accepted style formats.\n\nFor more fine-grained control of the conversion process, `source_file_options` and `convert_options` can be used to pass flags and settings directly to ImageMagick's powerful Convert tool, [documented here](https://www.imagemagick.org/script/convert.php). For example:\n\n```ruby\nhas_attached_file :image, styles: { regular: ['800x800>', :png]}, \n    source_file_options: { regular: \"-density 96 -depth 8 -quality 85\" },\n    convert_options: { regular: \"-posterize 3\"}\n```\n\nImageMagick supports a number of environment variables for controlling its resource limits. For example, you can enforce memory or execution time limits by setting the following variables in your application's process environment:\n\n* `MAGICK_MEMORY_LIMIT=128MiB`\n* `MAGICK_MAP_LIMIT=64MiB`\n* `MAGICK_TIME_LIMIT=30`\n\nFor a full list of variables and description, see [ImageMagick's resources documentation](http://www.imagemagick.org/script/resources.php).\n\n---\n\nCustom Attachment Processors\n-------\n\nYou can write your own custom attachment processors to carry out tasks like\nadding watermarks, compressing images, or encrypting files. Custom processors\nmust be defined within the `Paperclip` module, inherit from\n`Paperclip::Processor` (see [`lib/paperclip/processor.rb`](https://github.com/thoughtbot/paperclip/blob/master/lib/paperclip/processor.rb)),\nand implement a `make` method that returns a `File`. All files in your Rails\napp's `lib/paperclip` and `lib/paperclip_processors` directories will be\nautomatically loaded by Paperclip. Processors are specified using the\n`:processors` option to `has_attached_file`:\n\n```ruby\nhas_attached_file :scan, styles: { text: { quality: :better } },\n                         processors: [:ocr]\n```\n\nThis would load the hypothetical class `Paperclip::Ocr`, and pass it the\noptions hash `{ quality: :better }`, along with the uploaded file.\n\nMultiple processors can be specified, and they will be invoked in the order\nthey are defined in the `:processors` array. Each successive processor is given\nthe result from the previous processor. All processors receive the same\nparameters, which are defined in the `:styles` hash.  For example, assuming we\nhad this definition:\n\n```ruby\nhas_attached_file :scan, styles: { text: { quality: :better } },\n                         processors: [:rotator, :ocr]\n```\n\nBoth the `:rotator` processor and the `:ocr` processor would receive the\noptions `{ quality: :better }`. If a processor receives an option it doesn't\nrecognise, it's expected to ignore it.\n\n_NOTE: Because processors operate by turning the original attachment into the\nstyles, no processors will be run if there are no styles defined._\n\nIf you're interested in caching your thumbnail's width, height and size in the\ndatabase, take a look at the [paperclip-meta](https://github.com/teeparham/paperclip-meta)\ngem.\n\nAlso, if you're interested in generating the thumbnail on-the-fly, you might want\nto look into the [attachment_on_the_fly](https://github.com/drpentode/Attachment-on-the-Fly)\ngem.\n\nPaperclip's thumbnail generator (see [`lib/paperclip/thumbnail.rb`](lib/paperclip/thumbnail.rb))\nis implemented as a processor, and may be a good reference for writing your own\nprocessors.\n\n---\n\nEvents\n------\n\nBefore and after the Post Processing step, Paperclip calls back to the model\nwith a few callbacks, allowing the model to change or cancel the processing\nstep. The callbacks are `before_post_process` and `after_post_process` (which\nare called before and after the processing of each attachment), and the\nattachment-specific `before_<attachment>_post_process` and\n`after_<attachment>_post_process`. The callbacks are intended to be as close to\nnormal ActiveRecord callbacks as possible, so if you return false (specifically\n\\- returning nil is not the same) in a `before_filter`, the post processing step\nwill halt. Returning false in an `after_filter` will not halt anything, but you\ncan access the model and the attachment if necessary.\n\n_NOTE: Post processing will not even **start** if the attachment is not valid\naccording to the validations. Your callbacks and processors will **only** be\ncalled with valid attachments._\n\n```ruby\nclass Message < ActiveRecord::Base\n  has_attached_file :asset, styles: { thumb: \"100x100#\" }\n\n  before_post_process :skip_for_audio\n\n  def skip_for_audio\n    ! %w(audio/ogg application/ogg).include?(asset_content_type)\n  end\nend\n```\n\n---\n\nURI Obfuscation\n---------------\n\nPaperclip has an interpolation called `:hash` for obfuscating filenames of\npublicly-available files.\n\nExample Usage:\n\n```ruby\nhas_attached_file :avatar, {\n    url: \"/system/:hash.:extension\",\n    hash_secret: \"longSecretString\"\n}\n```\n\n\nThe `:hash` interpolation will be replaced with a unique hash made up of whatever\nis specified in `:hash_data`. The default value for `:hash_data` is `\":class/:attachment/:id/:style/:updated_at\"`.\n\n`:hash_secret` is required - an exception will be raised if `:hash` is used without `:hash_secret` present.\n\nFor more on this feature, read [the author's own explanation](https://github.com/thoughtbot/paperclip/pull/416)\n\nChecksum / Fingerprint\n-------\n\nA checksum of the original file assigned will be placed in the model if it\nhas an attribute named fingerprint.  Following the user model migration example\nabove, the migration would look like the following:\n\n```ruby\nclass AddAvatarFingerprintColumnToUser < ActiveRecord::Migration\n  def up\n    add_column :users, :avatar_fingerprint, :string\n  end\n\n  def down\n    remove_column :users, :avatar_fingerprint\n  end\nend\n```\n\nThe algorithm can be specified using a configuration option; it defaults to MD5\nfor backwards compatibility with Paperclip 5 and earlier.\n\n```ruby\nhas_attached_file :some_attachment, adapter_options: { hash_digest: Digest::SHA256 }\n```\n\nRun `CLASS=User ATTACHMENT=avatar rake paperclip:refresh:fingerprints` after\nchanging the digest on existing attachments to update the fingerprints in the\ndatabase.\n\nFile Preservation for Soft-Delete\n-------\n\nAn option is available to preserve attachments in order to play nicely with soft-deleted models. (acts_as_paranoid, paranoia, etc.)\n\n```ruby\nhas_attached_file :some_attachment, {\n    preserve_files: true,\n}\n```\n\nThis will prevent ```some_attachment``` from being wiped out when the model gets destroyed, so it will still exist when the object is restored later.\n\n---\n\nDynamic Configuration\n---------------------\n\nCallable objects (lambdas, Procs) can be used in a number of places for dynamic\nconfiguration throughout Paperclip.  This strategy exists in a number of\ncomponents of the library but is most significant in the possibilities for\nallowing custom styles and processors to be applied for specific model\ninstances, rather than applying defined styles and processors across all\ninstances.\n\n### Dynamic Styles:\n\nImagine a user model that had different styles based on the role of the user.\nPerhaps some users are bosses (e.g. a User model instance responds to `#boss?`)\nand merit a bigger avatar thumbnail than regular users. The configuration to\ndetermine what style parameters are to be used based on the user role might\nlook as follows where a boss will receive a `300x300` thumbnail otherwise a\n`100x100` thumbnail will be created.\n\n```ruby\nclass User < ActiveRecord::Base\n  has_attached_file :avatar, styles: lambda { |attachment| { thumb: (attachment.instance.boss? ? \"300x300>\" : \"100x100>\") } }\nend\n```\n\n### Dynamic Processors:\n\nAnother contrived example is a user model that is aware of which file processors\nshould be applied to it (beyond the implied `thumbnail` processor invoked when\n`:styles` are defined). Perhaps we have a watermark processor available and it is\nonly used on the avatars of certain models.  The configuration for this might be\nwhere the instance is queried for which processors should be applied to it.\nPresumably some users might return `[:thumbnail, :watermark]` for its\nprocessors, where a defined `watermark` processor is invoked after the\n`thumbnail` processor already defined by Paperclip.\n\n```ruby\nclass User < ActiveRecord::Base\n  has_attached_file :avatar, processors: lambda { |instance| instance.processors }\n  attr_accessor :processors\nend\n```\n\n---\n\nLogging\n----------\n\nBy default, Paperclip outputs logging according to your logger level. If you want to disable logging (e.g. during testing) add this into your environment's configuration:\n```ruby\nYour::Application.configure do\n...\n  Paperclip.options[:log] = false\n...\nend\n```\n\nMore information in the [rdocs](http://www.rubydoc.info/github/thoughtbot/paperclip/Paperclip.options)\n\n---\n\nDeployment\n----------\n\nTo make Capistrano symlink the `public/system` directory so that attachments\nsurvive new deployments, set the `linked_dirs` option in your `config/deploy.rb`\nfile:\n\n```ruby\nset :linked_dirs, fetch(:linked_dirs, []).push('public/system')\n```\n\n### Attachment Styles\n\nPaperclip is aware of new attachment styles you have added in previous deploys. The only thing you should do after each deployment is to call\n`rake paperclip:refresh:missing_styles`.  It will store current attachment styles in `RAILS_ROOT/public/system/paperclip_attachments.yml`\nby default. You can change it by:\n\n```ruby\nPaperclip.registered_attachments_styles_path = '/tmp/config/paperclip_attachments.yml'\n```\n\nHere is an example for Capistrano:\n\n```ruby\nnamespace :paperclip do\n  desc \"build missing paperclip styles\"\n  task :build_missing_styles do\n    on roles(:app) do\n      within release_path do\n        with rails_env: fetch(:rails_env) do\n          execute :rake, \"paperclip:refresh:missing_styles\"\n        end\n      end\n    end\n  end\nend\n\nafter(\"deploy:compile_assets\", \"paperclip:build_missing_styles\")\n```\n\nNow you don't have to remember to refresh thumbnails in production every time you add a new style.\nUnfortunately, it does not work with dynamic styles - it just ignores them.\n\nIf you already have a working app and don't want `rake paperclip:refresh:missing_styles` to refresh old pictures, you need to tell\nPaperclip about existing styles. Simply create a `paperclip_attachments.yml` file by hand. For example:\n\n```ruby\nclass User < ActiveRecord::Base\n  has_attached_file :avatar, styles: { thumb: 'x100', croppable: '600x600>', big: '1000x1000>' }\nend\n\nclass Book < ActiveRecord::Base\n  has_attached_file :cover, styles: { small: 'x100', large: '1000x1000>' }\n  has_attached_file :sample, styles: { thumb: 'x100' }\nend\n```\n\nThen in `RAILS_ROOT/public/system/paperclip_attachments.yml`:\n\n```yml\n---\n:User:\n  :avatar:\n  - :thumb\n  - :croppable\n  - :big\n:Book:\n  :cover:\n  - :small\n  - :large\n  :sample:\n  - :thumb\n```\n\n---\n\nTesting\n-------\n\nPaperclip provides rspec-compatible matchers for testing attachments. See the\ndocumentation on [Paperclip::Shoulda::Matchers](http://www.rubydoc.info/gems/paperclip/Paperclip/Shoulda/Matchers)\nfor more information.\n\n**Parallel Tests**\n\nBecause of the default `path` for Paperclip storage, if you try to run tests in\nparallel, you may find that files get overwritten because the same path is being\ncalculated for them in each test process. While this fix works for\nparallel_tests, a similar concept should be used for any other mechanism for\nrunning tests concurrently.\n\n```ruby\nif ENV['PARALLEL_TEST_GROUPS']\n  Paperclip::Attachment.default_options[:path] = \":rails_root/public/system/:rails_env/#{ENV['TEST_ENV_NUMBER'].to_i}/:class/:attachment/:id_partition/:filename\"\nelse\n  Paperclip::Attachment.default_options[:path] = \":rails_root/public/system/:rails_env/:class/:attachment/:id_partition/:filename\"\nend\n```\n\nThe important part here being the inclusion of `ENV['TEST_ENV_NUMBER']`, or a\nsimilar mechanism for whichever parallel testing library you use.\n\n**Integration Tests**\n\nUsing integration tests with FactoryBot may save multiple copies of\nyour test files within the app. To avoid this, specify a custom path in\nthe `config/environments/test.rb` like so:\n\n```ruby\nPaperclip::Attachment.default_options[:path] = \"#{Rails.root}/spec/test_files/:class/:id_partition/:style.:extension\"\n```\n\nThen, make sure to delete that directory after the test suite runs by adding\nthis to `spec_helper.rb`.\n\n```ruby\nconfig.after(:suite) do\n  FileUtils.rm_rf(Dir[\"#{Rails.root}/spec/test_files/\"])\nend\n```\n\n**Example of test configuration with Factory Bot**\n\n\n```ruby\nFactoryBot.define do\n  factory :user do\n    avatar { File.new(\"#{Rails.root}/spec/support/fixtures/image.jpg\") }\n  end\nend\n```\n---\n\nContributing\n------------\n\nIf you'd like to contribute a feature or bugfix: Thanks! To make sure your\nfix/feature has a high chance of being included, please read the following\nguidelines:\n\n1. Post a [pull request](https://github.com/thoughtbot/paperclip/compare/).\n2. Make sure there are tests! We will not accept any patch that is not tested.\n   It's a rare time when explicit tests aren't needed. If you have questions\n   about writing tests for paperclip, please open a\n   [GitHub issue](https://github.com/thoughtbot/paperclip/issues/new).\n\nPlease see [`CONTRIBUTING.md`](./CONTRIBUTING.md) for more details on contributing and running test.\n\nThank you to all [the contributors](https://github.com/thoughtbot/paperclip/graphs/contributors)!\n\nLicense\n-------\n\nPaperclip is Copyright © 2008-2017 thoughtbot, inc. It is free software, and may be\nredistributed under the terms specified in the MIT-LICENSE file.\n\nAbout thoughtbot\n----------------\n\n![thoughtbot](http://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg)\n\nPaperclip is maintained and funded by thoughtbot.\nThe names and logos for thoughtbot are trademarks of thoughtbot, inc.\n\nWe love open source software!\nSee [our other projects][community] or\n[hire us][hire] to design, develop, and grow your product.\n\n[community]: https://thoughtbot.com/community?utm_source=github\n[hire]: https://thoughtbot.com?utm_source=github\n"
  },
  {
    "path": "RELEASING.md",
    "content": "Releasing paperclip\n\n1. Update `lib/paperclip/version.rb` file accordingly.\n2. Update `NEWS` to reflect the changes since last release.\n3. Commit changes. There shouldn’t be code changes, and thus CI doesn’t need to\n   run, you can then add “[ci skip]” to the commit message.\n4. Tag the release: `git tag -m 'vVERSION' vVERSION`\n5. Push changes: `git push --tags`\n6. Build and publish the gem:\n\n   ```bash\n   gem build paperclip.gemspec\n   gem push paperclip-VERSION.gem\n   ```\n\n7. Announce the new release, making sure to say “thank you” to the contributors\n   who helped shape this version.\n"
  },
  {
    "path": "Rakefile",
    "content": "require 'bundler/gem_tasks'\nrequire 'appraisal'\nrequire 'rspec/core/rake_task'\nrequire 'cucumber/rake/task'\n\ndesc 'Default: run unit tests.'\ntask :default => [:clean, :all]\n\ndesc 'Test the paperclip plugin under all supported Rails versions.'\ntask :all do |t|\n  if ENV['BUNDLE_GEMFILE']\n    exec('rake spec && cucumber')\n  else\n    exec(\"rm -f gemfiles/*.lock\")\n    Rake::Task[\"appraisal:gemfiles\"].execute\n    Rake::Task[\"appraisal:install\"].execute\n    exec('rake appraisal')\n  end\nend\n\ndesc 'Test the paperclip plugin.'\nRSpec::Core::RakeTask.new(:spec)\n\ndesc 'Run integration test'\nCucumber::Rake::Task.new do |t|\n  t.cucumber_opts = %w{--format progress}\nend\n\ndesc 'Start an IRB session with all necessary files required.'\ntask :shell do |t|\n  chdir File.dirname(__FILE__)\n  exec 'irb -I lib/ -I lib/paperclip -r rubygems -r active_record -r tempfile -r init'\nend\n\ndesc 'Clean up files.'\ntask :clean do |t|\n  FileUtils.rm_rf \"doc\"\n  FileUtils.rm_rf \"tmp\"\n  FileUtils.rm_rf \"pkg\"\n  FileUtils.rm_rf \"public\"\n  FileUtils.rm \"test/debug.log\" rescue nil\n  FileUtils.rm \"test/paperclip.db\" rescue nil\n  Dir.glob(\"paperclip-*.gem\").each{|f| FileUtils.rm f }\nend\n"
  },
  {
    "path": "UPGRADING",
    "content": "##################################################\n#  NOTE FOR UPGRADING FROM 4.3.0 OR EARLIER      #\n##################################################\n\nPaperclip is now compatible with aws-sdk-s3.\n\nIf you are using S3 storage, aws-sdk-s3 requires you to make a few small\nchanges:\n\n* You must set the `s3_region`\n* If you are explicitly setting permissions anywhere, such as in an initializer,\n  note that the format of the permissions changed from using an underscore to\n  using a hyphen. For example, `:public_read` needs to be changed to\n  `public-read`.\n\nFor a walkthrough of upgrading from 4 to *5* (not 6) and aws-sdk >= 2.0 you can watch\nhttp://rubythursday.com/episodes/ruby-snack-27-upgrade-paperclip-and-aws-sdk-in-prep-for-rails-5\n"
  },
  {
    "path": "features/basic_integration.feature",
    "content": "Feature: Rails integration\n\n  Background:\n    Given I generate a new rails application\n    And I run a rails generator to generate a \"User\" scaffold with \"name:string\"\n    And I run a paperclip generator to add a paperclip \"attachment\" to the \"User\" model\n    And I run a migration\n    And I update my new user view to include the file upload field\n    And I update my user view to include the attachment\n    And I allow the attachment to be submitted\n\n  Scenario: Configure defaults for all attachments through Railtie\n    Given I add this snippet to config/application.rb:\n      \"\"\"\n      config.paperclip_defaults = {\n        :url => \"/paperclip/custom/:attachment/:style/:filename\",\n        :validate_media_type => false\n      }\n      \"\"\"\n    And I attach :attachment\n    And I start the rails application\n    When I go to the new user page\n    And I fill in \"Name\" with \"something\"\n    And I attach the file \"spec/support/fixtures/animated.unknown\" to \"Attachment\"\n    And I press \"Submit\"\n    Then I should see \"Name: something\"\n    And I should see an image with a path of \"/paperclip/custom/attachments/original/animated.unknown\"\n    And the file at \"/paperclip/custom/attachments/original/animated.unknown\" should be the same as \"spec/support/fixtures/animated.unknown\"\n\n  Scenario: Add custom processors\n    Given I add a \"test\" processor in \"lib/paperclip\"\n    And I add a \"cool\" processor in \"lib/paperclip_processors\"\n    And I attach :attachment with:\n      \"\"\"\n      styles: { original: {} }, processors: [:test, :cool]\n      \"\"\"\n    And I start the rails application\n    When I go to the new user page\n    And I fill in \"Name\" with \"something\"\n    And I attach the file \"spec/support/fixtures/5k.png\" to \"Attachment\"\n    And I press \"Submit\"\n    Then I should see \"Name: something\"\n    And I should see an image with a path of \"/paperclip/custom/attachments/original/5k.png\"\n\n  Scenario: Filesystem integration test\n    Given I attach :attachment with:\n      \"\"\"\n        :url => \"/system/:attachment/:style/:filename\"\n      \"\"\"\n    And I start the rails application\n    When I go to the new user page\n    And I fill in \"Name\" with \"something\"\n    And I attach the file \"spec/support/fixtures/5k.png\" to \"Attachment\"\n    And I press \"Submit\"\n    Then I should see \"Name: something\"\n    And I should see an image with a path of \"/system/attachments/original/5k.png\"\n    And the file at \"/system/attachments/original/5k.png\" should be the same as \"spec/support/fixtures/5k.png\"\n\n  Scenario: S3 Integration test\n    Given I attach :attachment with:\n      \"\"\"\n        :storage => :s3,\n        :path => \"/:attachment/:style/:filename\",\n        :s3_credentials => Rails.root.join(\"config/s3.yml\"),\n        :styles => { :square => \"100x100#\" }\n      \"\"\"\n    And I write to \"config/s3.yml\" with:\n      \"\"\"\n      bucket: paperclip\n      access_key_id: access_key\n      secret_access_key: secret_key\n      s3_region: us-west-2\n      \"\"\"\n    And I start the rails application\n    When I go to the new user page\n    And I fill in \"Name\" with \"something\"\n    And I attach the file \"spec/support/fixtures/5k.png\" to \"Attachment\" on S3\n    And I press \"Submit\"\n    Then I should see \"Name: something\"\n    And I should see an image with a path of \"//s3.amazonaws.com/paperclip/attachments/original/5k.png\"\n    And the file at \"//s3.amazonaws.com/paperclip/attachments/original/5k.png\" should be uploaded to S3\n"
  },
  {
    "path": "features/migration.feature",
    "content": "Feature: Migration\n\n  Background:\n    Given I generate a new rails application\n    And I write to \"app/models/user.rb\" with:\n      \"\"\"\n      class User < ActiveRecord::Base; end\n      \"\"\"\n\n  Scenario: Vintage syntax\n    When I write to \"db/migrate/01_add_attachment_to_users.rb\" with:\n      \"\"\"\n      class AddAttachmentToUsers < ActiveRecord::Migration\n        def self.up\n          create_table :users do |t|\n            t.has_attached_file :avatar\n          end\n        end\n\n        def self.down\n          drop_attached_file :users, :avatar\n        end\n      end\n      \"\"\"\n    And I run a migration\n    Then I should have attachment columns for \"avatar\"\n\n    When I rollback a migration\n    Then I should not have attachment columns for \"avatar\"\n\n  Scenario: New syntax with create_table\n    When I write to \"db/migrate/01_add_attachment_to_users.rb\" with:\n      \"\"\"\n      class AddAttachmentToUsers < ActiveRecord::Migration\n        def self.up\n          create_table :users do |t|\n            t.attachment :avatar\n          end\n        end\n      end\n      \"\"\"\n    And I run a migration\n    Then I should have attachment columns for \"avatar\"\n\n  Scenario: New syntax outside of create_table\n    When I write to \"db/migrate/01_create_users.rb\" with:\n      \"\"\"\n      class CreateUsers < ActiveRecord::Migration\n        def self.up\n          create_table :users\n        end\n      end\n      \"\"\"\n    And I write to \"db/migrate/02_add_attachment_to_users.rb\" with:\n      \"\"\"\n      class AddAttachmentToUsers < ActiveRecord::Migration\n        def self.up\n          add_attachment :users, :avatar\n        end\n\n        def self.down\n          remove_attachment :users, :avatar\n        end\n      end\n      \"\"\"\n    And I run a migration\n    Then I should have attachment columns for \"avatar\"\n\n    When I rollback a migration\n    Then I should not have attachment columns for \"avatar\"\n"
  },
  {
    "path": "features/rake_tasks.feature",
    "content": "Feature: Rake tasks\n\n  Background:\n    Given I generate a new rails application\n    And I run a rails generator to generate a \"User\" scaffold with \"name:string\"\n    And I run a paperclip generator to add a paperclip \"attachment\" to the \"User\" model\n    And I run a migration\n    And I attach :attachment with:\n      \"\"\"\n        :path => \":rails_root/public/system/:attachment/:style/:filename\"\n      \"\"\"\n\n  Scenario: Paperclip refresh thumbnails task\n    When I modify my attachment definition to:\n      \"\"\"\n      has_attached_file :attachment, :path => \":rails_root/public/system/:attachment/:style/:filename\",\n                                     :styles => { :medium => \"200x200#\" }\n      \"\"\"\n    And I upload the fixture \"5k.png\"\n    Then the attachment \"medium/5k.png\" should have a dimension of 200x200\n    When I modify my attachment definition to:\n      \"\"\"\n      has_attached_file :attachment, :path => \":rails_root/public/system/:attachment/:style/:filename\",\n                                     :styles => { :medium => \"100x100#\" }\n      \"\"\"\n    When I successfully run `bundle exec rake paperclip:refresh:thumbnails CLASS=User --trace`\n    Then the attachment \"original/5k.png\" should exist\n    And the attachment \"medium/5k.png\" should have a dimension of 100x100\n\n  Scenario: Paperclip refresh metadata task\n    When I upload the fixture \"5k.png\"\n    And I swap the attachment \"original/5k.png\" with the fixture \"12k.png\"\n    And I successfully run `bundle exec rake paperclip:refresh:metadata CLASS=User --trace`\n    Then the attachment should have the same content type as the fixture \"12k.png\"\n    And the attachment should have the same file size as the fixture \"12k.png\"\n\n  Scenario: Paperclip refresh missing styles task\n    When I upload the fixture \"5k.png\"\n    Then the attachment file \"original/5k.png\" should exist\n    And the attachment file \"medium/5k.png\" should not exist\n    When I modify my attachment definition to:\n      \"\"\"\n      has_attached_file :attachment, :path => \":rails_root/public/system/:attachment/:style/:filename\",\n                                     :styles => { :medium => \"200x200#\" }\n      \"\"\"\n    When I successfully run `bundle exec rake paperclip:refresh:missing_styles --trace`\n    Then the attachment file \"original/5k.png\" should exist\n    And the attachment file \"medium/5k.png\" should exist\n\n  Scenario: Paperclip clean task\n    When I upload the fixture \"5k.png\"\n    And I upload the fixture \"12k.png\"\n    Then the attachment file \"original/5k.png\" should exist\n    And the attachment file \"original/12k.png\" should exist\n    When I modify my attachment definition to:\n      \"\"\"\n      has_attached_file :attachment, :path => \":rails_root/public/system/:attachment/:style/:filename\"\n      validates_attachment_size :attachment, :less_than => 10.kilobytes\n      \"\"\"\n    And I successfully run `bundle exec rake paperclip:clean CLASS=User --trace`\n    Then the attachment file \"original/5k.png\" should exist\n    But the attachment file \"original/12k.png\" should not exist\n"
  },
  {
    "path": "features/step_definitions/attachment_steps.rb",
    "content": "module AttachmentHelpers\n  def fixture_path(filename)\n    File.expand_path(\"#{PROJECT_ROOT}/spec/support/fixtures/#{filename}\")\n  end\n\n  def attachment_path(filename)\n    File.expand_path(\"public/system/attachments/#{filename}\")\n  end\nend\nWorld(AttachmentHelpers)\n\nWhen /^I modify my attachment definition to:$/ do |definition|\n  content = cd(\".\") { File.read(\"app/models/user.rb\") }\n  name = content[/has_attached_file :\\w+/][/:\\w+/]\n  content.gsub!(/has_attached_file.+end/m, <<-FILE)\n      #{definition}\n      do_not_validate_attachment_file_type #{name}\n    end\n  FILE\n\n  write_file \"app/models/user.rb\", content\n  cd(\".\") { FileUtils.rm_rf \".rbx\" }\nend\n\nWhen /^I upload the fixture \"([^\"]*)\"$/ do |filename|\n  run_simple %(bundle exec rails runner \"User.create!(:attachment => File.open('#{fixture_path(filename)}'))\")\nend\n\nThen /^the attachment \"([^\"]*)\" should have a dimension of (\\d+x\\d+)$/ do |filename, dimension|\n  cd(\".\") do\n    geometry = `identify -format \"%wx%h\" \"#{attachment_path(filename)}\"`.strip\n    expect(geometry).to eq(dimension)\n  end\nend\n\nThen /^the attachment \"([^\"]*)\" should exist$/ do |filename|\n  cd(\".\") do\n    expect(File.exist?(attachment_path(filename))).to be true\n  end\nend\n\nWhen /^I swap the attachment \"([^\"]*)\" with the fixture \"([^\"]*)\"$/ do |attachment_filename, fixture_filename|\n  cd(\".\") do\n    require 'fileutils'\n    FileUtils.rm_f attachment_path(attachment_filename)\n    FileUtils.cp fixture_path(fixture_filename), attachment_path(attachment_filename)\n  end\nend\n\nThen /^the attachment should have the same content type as the fixture \"([^\"]*)\"$/ do |filename|\n  cd(\".\") do\n    begin\n      # Use mime/types/columnar if available, for reduced memory usage\n      require \"mime/types/columnar\"\n    rescue LoadError\n      require \"mime/types\"\n    end\n\n    attachment_content_type = `bundle exec rails runner \"puts User.last.attachment_content_type\"`.strip\n    expected = MIME::Types.type_for(filename).first.content_type\n    expect(attachment_content_type).to eq(expected)\n  end\nend\n\nThen /^the attachment should have the same file name as the fixture \"([^\"]*)\"$/ do |filename|\n  cd(\".\") do\n    attachment_file_name = `bundle exec rails runner \"puts User.last.attachment_file_name\"`.strip\n    expect(attachment_file_name).to eq(File.name(fixture_path(filename)).to_s)\n  end\nend\n\nThen /^the attachment should have the same file size as the fixture \"([^\"]*)\"$/ do |filename|\n  cd(\".\") do\n    attachment_file_size = `bundle exec rails runner \"puts User.last.attachment_file_size\"`.strip\n    expect(attachment_file_size).to eq(File.size(fixture_path(filename)).to_s)\n  end\nend\n\nThen /^the attachment file \"([^\"]*)\" should (not )?exist$/ do |filename, not_exist|\n  cd(\".\") do\n    expect(attachment_path(filename)).not_to be_an_existing_file\n  end\nend\n\nThen /^I should have attachment columns for \"([^\"]*)\"$/ do |attachment_name|\n  cd(\".\") do\n    columns = eval(`bundle exec rails runner \"puts User.columns.map{ |column| [column.name, column.sql_type] }.inspect\"`.strip)\n    expect_columns = [\n      [\"#{attachment_name}_file_name\", \"varchar\"],\n      [\"#{attachment_name}_content_type\", \"varchar\"],\n      [\"#{attachment_name}_file_size\", \"bigint\"],\n      [\"#{attachment_name}_updated_at\", \"datetime\"]\n    ]\n    expect(columns).to include(*expect_columns)\n  end\nend\n\nThen /^I should not have attachment columns for \"([^\"]*)\"$/ do |attachment_name|\n  cd(\".\") do\n    columns = eval(`bundle exec rails runner \"puts User.columns.map{ |column| [column.name, column.sql_type] }.inspect\"`.strip)\n    expect_columns = [\n      [\"#{attachment_name}_file_name\", \"varchar\"],\n      [\"#{attachment_name}_content_type\", \"varchar\"],\n      [\"#{attachment_name}_file_size\", \"bigint\"],\n      [\"#{attachment_name}_updated_at\", \"datetime\"]\n    ]\n\n    expect(columns).not_to include(*expect_columns)\n  end\nend\n"
  },
  {
    "path": "features/step_definitions/html_steps.rb",
    "content": "Then %r{I should see an image with a path of \"([^\"]*)\"} do |path|\n  expect(page).to have_css(\"img[src^='#{path}']\")\nend\n\nThen %r{^the file at \"([^\"]*)\" is the same as \"([^\"]*)\"$} do |web_file, path|\n  expected = IO.read(path)\n  actual = if web_file.match %r{^https?://}\n    Net::HTTP.get(URI.parse(web_file))\n  else\n    visit(web_file)\n    page.body\n  end\n  actual.force_encoding(\"UTF-8\") if actual.respond_to?(:force_encoding)\n  expect(actual).to eq(expected)\nend\n"
  },
  {
    "path": "features/step_definitions/rails_steps.rb",
    "content": "Given /^I generate a new rails application$/ do\n  steps %{\n    When I successfully run `rails new #{APP_NAME} --skip-bundle`\n    And I cd to \"#{APP_NAME}\"\n  }\n\n  FileUtils.chdir(\"tmp/aruba/testapp/\")\n\n  steps %{\n    And I turn off class caching\n    And I write to \"Gemfile\" with:\n      \"\"\"\n      source \"http://rubygems.org\"\n      gem \"rails\", \"#{framework_version}\"\n      gem \"sqlite3\", :platform => [:ruby, :rbx]\n      gem \"activerecord-jdbcsqlite3-adapter\", :platform => :jruby\n      gem \"jruby-openssl\", :platform => :jruby\n      gem \"capybara\"\n      gem \"gherkin\"\n      gem \"aws-sdk-s3\"\n      gem \"racc\", :platform => :rbx\n      gem \"rubysl\", :platform => :rbx\n      \"\"\"\n    And I remove turbolinks\n    And I comment out lines that contain \"action_mailer\" in \"config/environments/*.rb\"\n    And I empty the application.js file\n    And I configure the application to use \"paperclip\" from this project\n  }\n\n  FileUtils.chdir(\"../../..\")\nend\n\nGiven \"I allow the attachment to be submitted\" do\n  cd(\".\") do\n    transform_file(\"app/controllers/users_controller.rb\") do |content|\n      content.gsub(\"params.require(:user).permit(:name)\",\n                   \"params.require(:user).permit!\")\n    end\n  end\nend\n\nGiven \"I remove turbolinks\" do\n  cd(\".\") do\n    transform_file(\"app/assets/javascripts/application.js\") do |content|\n      content.gsub(\"//= require turbolinks\", \"\")\n    end\n    transform_file(\"app/views/layouts/application.html.erb\") do |content|\n      content.gsub(', \"data-turbolinks-track\" => true', \"\")\n    end\n  end\nend\n\nGiven /^I comment out lines that contain \"([^\"]+)\" in \"([^\"]+)\"$/ do |contains, glob|\n  cd(\".\") do\n    Dir.glob(glob).each do |file|\n      transform_file(file) do |content|\n        content.gsub(/^(.*?#{contains}.*?)$/) { |line| \"# #{line}\" }\n      end\n    end\n  end\nend\n\nGiven /^I attach :attachment$/ do\n  attach_attachment(\"attachment\")\nend\n\nGiven /^I attach :attachment with:$/ do |definition|\n  attach_attachment(\"attachment\", definition)\nend\n\ndef attach_attachment(name, definition = nil)\n  snippet = \"has_attached_file :#{name}\"\n  if definition\n    snippet += \", \\n\"\n    snippet += definition\n  end\n  snippet += \"\\ndo_not_validate_attachment_file_type :#{name}\\n\"\n  cd(\".\") do\n    transform_file(\"app/models/user.rb\") do |content|\n      content.sub(/end\\Z/, \"#{snippet}\\nend\")\n    end\n  end\nend\n\nGiven \"I empty the application.js file\" do\n  cd(\".\") do\n    transform_file(\"app/assets/javascripts/application.js\") do |content|\n      \"\"\n    end\n  end\nend\n\nGiven /^I run a rails generator to generate a \"([^\"]*)\" scaffold with \"([^\"]*)\"$/ do |model_name, attributes|\n  step %[I successfully run `rails generate scaffold #{model_name} #{attributes}`]\nend\n\nGiven /^I run a paperclip generator to add a paperclip \"([^\"]*)\" to the \"([^\"]*)\" model$/ do |attachment_name, model_name|\n  step %[I successfully run `rails generate paperclip #{model_name} #{attachment_name}`]\nend\n\nGiven /^I run a migration$/ do\n  step %[I successfully run `rake db:migrate --trace`]\nend\n\nWhen /^I rollback a migration$/ do\n  step %[I successfully run `rake db:rollback STEPS=1 --trace`]\nend\n\nGiven /^I update my new user view to include the file upload field$/ do\n  steps %{\n    Given I overwrite \"app/views/users/new.html.erb\" with:\n      \"\"\"\n      <%= form_for @user, :html => { :multipart => true } do |f| %>\n        <%= f.label :name %>\n        <%= f.text_field :name %>\n        <%= f.label :attachment %>\n        <%= f.file_field :attachment %>\n        <%= submit_tag \"Submit\" %>\n      <% end %>\n      \"\"\"\n  }\nend\n\nGiven /^I update my user view to include the attachment$/ do\n  steps %{\n    Given I overwrite \"app/views/users/show.html.erb\" with:\n      \"\"\"\n      <p>Name: <%= @user.name %></p>\n      <p>Attachment: <%= image_tag @user.attachment.url %></p>\n      \"\"\"\n  }\nend\n\nGiven /^I add this snippet to the User model:$/ do |snippet|\n  file_name = \"app/models/user.rb\"\n  cd(\".\") do\n    content = File.read(file_name)\n    File.open(file_name, 'w') { |f| f << content.sub(/end\\Z/, \"#{snippet}\\nend\") }\n  end\nend\n\nGiven /^I add this snippet to config\\/application.rb:$/ do |snippet|\n  file_name = \"config/application.rb\"\n  cd(\".\") do\n    content = File.read(file_name)\n    File.open(file_name, 'w') {|f| f << content.sub(/class Application < Rails::Application.*$/, \"class Application < Rails::Application\\n#{snippet}\\n\")}\n  end\nend\n\nGiven /^I start the rails application$/ do\n  cd(\".\") do\n    require \"rails\"\n    require \"./config/environment\"\n    require \"capybara\"\n    Capybara.app = Rails.application\n  end\nend\n\nGiven /^I reload my application$/ do\n  Rails::Application.reload!\nend\n\nWhen /^I turn off class caching$/ do\n  cd(\".\") do\n    file = \"config/environments/test.rb\"\n    config = IO.read(file)\n    config.gsub!(%r{^\\s*config.cache_classes.*$},\n                 \"config.cache_classes = false\")\n    File.open(file, \"w\"){|f| f.write(config) }\n  end\nend\n\nThen /^the file at \"([^\"]*)\" should be the same as \"([^\"]*)\"$/ do |web_file, path|\n  expected = IO.read(path)\n  actual = read_from_web(web_file)\n  expect(actual).to eq(expected)\nend\n\nWhen /^I configure the application to use \"([^\\\"]+)\" from this project$/ do |name|\n  append_to_gemfile \"gem '#{name}', :path => '#{PROJECT_ROOT}'\"\n  steps %{And I successfully run `bundle install --local`}\nend\n\nWhen /^I configure the application to use \"([^\\\"]+)\"$/ do |gem_name|\n  append_to_gemfile \"gem '#{gem_name}'\"\nend\n\nWhen /^I append gems from Appraisal Gemfile$/ do\n  File.read(ENV['BUNDLE_GEMFILE']).split(/\\n/).each do |line|\n    if line =~ /^gem \"(?!rails|appraisal)/\n      append_to_gemfile line.strip\n    end\n  end\nend\n\nWhen /^I comment out the gem \"([^\"]*)\" from the Gemfile$/ do |gemname|\n  comment_out_gem_in_gemfile gemname\nend\n\nGiven(/^I add a \"(.*?)\" processor in \"(.*?)\"$/) do |processor, directory|\n  filename = \"#{directory}/#{processor}.rb\"\n  cd(\".\") do\n    FileUtils.mkdir_p directory\n    File.open(filename, \"w\") do |f|\n      f.write(<<-CLASS)\n        module Paperclip\n          class #{processor.capitalize} < Processor\n            def make\n              basename = File.basename(file.path, File.extname(file.path))\n              dst_format = options[:format] ? \".\\#{options[:format]}\" : ''\n\n              dst = Tempfile.new([basename, dst_format])\n              dst.binmode\n\n              convert(':src :dst',\n                src: File.expand_path(file.path),\n                dst: File.expand_path(dst.path)\n              )\n\n              dst\n            end\n          end\n        end\n      CLASS\n    end\n  end\nend\n\ndef transform_file(filename)\n  if File.exist?(filename)\n    content = File.read(filename)\n    File.open(filename, \"w\") do |f|\n      content = yield(content)\n      f.write(content)\n    end\n  end\nend\n"
  },
  {
    "path": "features/step_definitions/s3_steps.rb",
    "content": "When /^I attach the file \"([^\"]*)\" to \"([^\"]*)\" on S3$/ do |file_path, field|\n  definition = Paperclip::AttachmentRegistry.definitions_for(User)[field.downcase.to_sym]\n  path = \"https://paperclip.s3.us-west-2.amazonaws.com#{definition[:path]}\"\n  path.gsub!(':filename', File.basename(file_path))\n  path.gsub!(/:([^\\/\\.]+)/) do |match|\n    \"([^\\/\\.]+)\"\n  end\n  FakeWeb.register_uri(:put, Regexp.new(path), :body => \"<xml></xml>\")\n  step \"I attach the file \\\"#{file_path}\\\" to \\\"#{field}\\\"\"\nend\n\nThen /^the file at \"([^\"]*)\" should be uploaded to S3$/ do |url|\n  FakeWeb.registered_uri?(:put, url)\nend\n"
  },
  {
    "path": "features/step_definitions/web_steps.rb",
    "content": "# TL;DR: YOU SHOULD DELETE THIS FILE\n#\n# This file was generated by Cucumber-Rails and is only here to get you a head start\n# These step definitions are thin wrappers around the Capybara/Webrat API that lets you\n# visit pages, interact with widgets and make assertions about page content.\n#\n# If you use these step definitions as basis for your features you will quickly end up\n# with features that are:\n#\n# * Hard to maintain\n# * Verbose to read\n#\n# A much better approach is to write your own higher level step definitions, following\n# the advice in the following blog posts:\n#\n# * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html\n# * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/\n# * http://elabs.se/blog/15-you-re-cuking-it-wrong\n#\n\n\nrequire 'uri'\nrequire 'cgi'\nrequire File.expand_path(File.join(File.dirname(__FILE__), \"..\", \"support\", \"paths\"))\nrequire File.expand_path(File.join(File.dirname(__FILE__), \"..\", \"support\", \"selectors\"))\n\nmodule WithinHelpers\n  def with_scope(locator)\n    locator ? within(*selector_for(locator)) { yield } : yield\n  end\nend\nWorld(WithinHelpers)\n\n# Single-line step scoper\nWhen /^(.*) within (.*[^:])$/ do |step, parent|\n  with_scope(parent) { When step }\nend\n\n# Multi-line step scoper\nWhen /^(.*) within (.*[^:]):$/ do |step, parent, table_or_string|\n  with_scope(parent) { When \"#{step}:\", table_or_string }\nend\n\nGiven /^(?:|I )am on (.+)$/ do |page_name|\n  visit path_to(page_name)\nend\n\nWhen /^(?:|I )go to (.+)$/ do |page_name|\n  visit path_to(page_name)\nend\n\nWhen /^(?:|I )press \"([^\"]*)\"$/ do |button|\n  click_button(button)\nend\n\nWhen /^(?:|I )follow \"([^\"]*)\"$/ do |link|\n  click_link(link)\nend\n\nWhen /^(?:|I )fill in \"([^\"]*)\" with \"([^\"]*)\"$/ do |field, value|\n  fill_in(field, :with => value)\nend\n\nWhen /^(?:|I )fill in \"([^\"]*)\" for \"([^\"]*)\"$/ do |value, field|\n  fill_in(field, :with => value)\nend\n\n# Use this to fill in an entire form with data from a table. Example:\n#\n#   When I fill in the following:\n#     | Account Number | 5002       |\n#     | Expiry date    | 2009-11-01 |\n#     | Note           | Nice guy   |\n#     | Wants Email?   |            |\n#\n# TODO: Add support for checkbox, select og option\n# based on naming conventions.\n#\nWhen /^(?:|I )fill in the following:$/ do |fields|\n  fields.rows_hash.each do |name, value|\n    When %{I fill in \"#{name}\" with \"#{value}\"}\n  end\nend\n\nWhen /^(?:|I )select \"([^\"]*)\" from \"([^\"]*)\"$/ do |value, field|\n  select(value, :from => field)\nend\n\nWhen /^(?:|I )check \"([^\"]*)\"$/ do |field|\n  check(field)\nend\n\nWhen /^(?:|I )uncheck \"([^\"]*)\"$/ do |field|\n  uncheck(field)\nend\n\nWhen /^(?:|I )choose \"([^\"]*)\"$/ do |field|\n  choose(field)\nend\n\nWhen /^(?:|I )attach the file \"([^\"]*)\" to \"([^\"]*)\"$/ do |path, field|\n  attach_file(field, File.expand_path(path))\nend\n\nThen /^(?:|I )should see \"([^\"]*)\"$/ do |text|\n  expect(page).to have_content(text)\nend\n"
  },
  {
    "path": "features/support/env.rb",
    "content": "require 'aruba/cucumber'\nrequire 'capybara/cucumber'\nrequire 'rspec/matchers'\n\n$CUCUMBER=1\n\nWorld(RSpec::Matchers)\n\nBefore do\n  aruba.config.command_launcher = ENV.fetch(\"DEBUG\", nil) ? :debug : :spawn\n  @aruba_timeout_seconds = 120\nend\n"
  },
  {
    "path": "features/support/fakeweb.rb",
    "content": "require 'fake_web'\n\nFakeWeb.allow_net_connect = false\n\nmodule FakeWeb\n  class StubSocket\n    def read_timeout=(_ignored)\n    end\n\n    def continue_timeout=(_ignored)\n    end\n  end\nend\n"
  },
  {
    "path": "features/support/file_helpers.rb",
    "content": "module FileHelpers\n  def append_to(path, contents)\n    cd(\".\") do\n      File.open(path, \"a\") do |file|\n        file.puts\n        file.puts contents\n      end\n    end\n  end\n\n  def append_to_gemfile(contents)\n    append_to('Gemfile', contents)\n  end\n\n  def comment_out_gem_in_gemfile(gemname)\n    cd(\".\") do\n      gemfile = File.read(\"Gemfile\")\n      gemfile.sub!(/^(\\s*)(gem\\s*['\"]#{gemname})/, \"\\\\1# \\\\2\")\n      File.open(\"Gemfile\", 'w'){ |file| file.write(gemfile) }\n    end\n  end\n\n  def read_from_web(url)\n    file = if url.match %r{^https?://}\n             Net::HTTP.get(URI.parse(url))\n           else\n             visit(url)\n             page.source\n           end\n    file.force_encoding(\"UTF-8\") if file.respond_to?(:force_encoding)\n  end\nend\n\nWorld(FileHelpers)\n"
  },
  {
    "path": "features/support/fixtures/boot_config.txt",
    "content": "class Rails::Boot\n  def run\n    load_initializer\n\n    Rails::Initializer.class_eval do\n      def load_gems\n        @bundler_loaded ||= Bundler.require :default, Rails.env\n      end\n    end\n\n    Rails::Initializer.run(:set_load_path)\n  end\nend\n\nRails.boot!\n"
  },
  {
    "path": "features/support/fixtures/gemfile.txt",
    "content": "source \"http://rubygems.org\"\n\ngem \"rails\", \"RAILS_VERSION\"\ngem \"rdoc\"\ngem \"sqlite3\", \"1.3.8\"\n"
  },
  {
    "path": "features/support/fixtures/preinitializer.txt",
    "content": "begin\n  require \"rubygems\"\n  require \"bundler\"\nrescue LoadError\n  raise \"Could not load the bundler gem. Install it with `gem install bundler`.\"\nend\n\nif Gem::Version.new(Bundler::VERSION) <= Gem::Version.new(\"0.9.24\")\n  raise RuntimeError, \"Your bundler version is too old for Rails 2.3.\" +\n   \"Run `gem install bundler` to upgrade.\"\nend\n\nbegin\n  # Set up load paths for all bundled gems\n  ENV[\"BUNDLE_GEMFILE\"] = File.expand_path(\"../../Gemfile\", __FILE__)\n  Bundler.setup\nrescue Bundler::GemNotFound\n  raise RuntimeError, \"Bundler couldn't find some gems.\" +\n    \"Did you run `bundle install`?\"\nend\n"
  },
  {
    "path": "features/support/paths.rb",
    "content": "module NavigationHelpers\n  # Maps a name to a path. Used by the\n  #\n  #   When /^I go to (.+)$/ do |page_name|\n  #\n  # step definition in web_steps.rb\n  #\n  def path_to(page_name)\n    case page_name\n\n    when /the home\\s?page/\n      '/'\n    when /the new user page/\n      '/users/new'\n    else\n      begin\n        page_name =~ /the (.*) page/\n        path_components = $1.split(/\\s+/)\n        self.send(path_components.push('path').join('_').to_sym)\n      rescue Object\n        raise \"Can't find mapping from \\\"#{page_name}\\\" to a path.\\n\" +\n          \"Now, go and add a mapping in #{__FILE__}\"\n      end\n    end\n  end\nend\n\nWorld(NavigationHelpers)\n"
  },
  {
    "path": "features/support/rails.rb",
    "content": "PROJECT_ROOT     = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze\nAPP_NAME         = 'testapp'.freeze\nBUNDLE_ENV_VARS = %w(RUBYOPT BUNDLE_PATH BUNDLE_BIN_PATH BUNDLE_GEMFILE)\nORIGINAL_BUNDLE_VARS = Hash[ENV.select{ |key,value| BUNDLE_ENV_VARS.include?(key) }]\n\nENV['RAILS_ENV'] = 'test'\n\nBefore do\n  gemfile = ENV['BUNDLE_GEMFILE'].to_s\n  ENV['BUNDLE_GEMFILE'] = File.join(Dir.pwd, gemfile) unless gemfile.start_with?(Dir.pwd)\n  @framework_version = nil\nend\n\nAfter do\n  ORIGINAL_BUNDLE_VARS.each_pair do |key, value|\n    ENV[key] = value\n  end\nend\n\nWhen /^I reset Bundler environment variable$/ do\n  BUNDLE_ENV_VARS.each do |key|\n    ENV[key] = nil\n  end\nend\n\nmodule RailsCommandHelpers\n  def framework_version?(version_string)\n    framework_version =~ /^#{version_string}/\n  end\n\n  def framework_version\n    @framework_version ||= `rails -v`[/^Rails (.+)$/, 1]\n  end\n\n  def framework_major_version\n    framework_version.split(\".\").first.to_i\n  end\nend\nWorld(RailsCommandHelpers)\n"
  },
  {
    "path": "features/support/selectors.rb",
    "content": "module HtmlSelectorsHelpers\n  # Maps a name to a selector. Used primarily by the\n  #\n  #   When /^(.+) within (.+)$/ do |step, scope|\n  #\n  # step definitions in web_steps.rb\n  #\n  def selector_for(locator)\n    case locator\n    when \"the page\"\n      \"html > body\"\n    else\n      raise \"Can't find mapping from \\\"#{locator}\\\" to a selector.\\n\" +\n        \"Now, go and add a mapping in #{__FILE__}\"\n    end\n  end\nend\n\nWorld(HtmlSelectorsHelpers)\n"
  },
  {
    "path": "gemfiles/4.2.gemfile",
    "content": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"sqlite3\", \"~> 1.3.8\", :platforms => :ruby\ngem \"pry\"\ngem \"rails\", \"~> 4.2.0\"\n\ngroup :development, :test do\n  gem \"activerecord-import\"\n  gem \"mime-types\"\n  gem \"builder\"\n  gem \"rubocop\", :require => false\n  gem \"rspec\"\nend\n\ngemspec :path => \"../\"\n"
  },
  {
    "path": "gemfiles/5.0.gemfile",
    "content": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"sqlite3\", \"~> 1.3.8\", :platforms => :ruby\ngem \"pry\"\ngem \"rails\", \"~> 5.0.0\"\n\ngroup :development, :test do\n  gem \"activerecord-import\"\n  gem \"mime-types\"\n  gem \"builder\"\n  gem \"rubocop\", :require => false\n  gem \"rspec\"\nend\n\ngemspec :path => \"../\"\n"
  },
  {
    "path": "lib/generators/paperclip/USAGE",
    "content": "Description:\n    Explain the generator\n\nExample:\n    rails generate paperclip Thing\n\n    This will create:\n        what/will/it/create\n"
  },
  {
    "path": "lib/generators/paperclip/paperclip_generator.rb",
    "content": "require 'rails/generators/active_record'\n\nclass PaperclipGenerator < ActiveRecord::Generators::Base\n  desc \"Create a migration to add paperclip-specific fields to your model. \" +\n       \"The NAME argument is the name of your model, and the following \" +\n       \"arguments are the name of the attachments\"\n\n  argument :attachment_names, :required => true, :type => :array, :desc => \"The names of the attachment(s) to add.\",\n           :banner => \"attachment_one attachment_two attachment_three ...\"\n\n  def self.source_root\n    @source_root ||= File.expand_path('../templates', __FILE__)\n  end\n\n  def generate_migration\n    migration_template(\"paperclip_migration.rb.erb\",\n                       \"db/migrate/#{migration_file_name}\",\n                       migration_version: migration_version)\n  end\n\n  def migration_name\n    \"add_attachment_#{attachment_names.join(\"_\")}_to_#{name.underscore.pluralize}\"\n  end\n\n  def migration_file_name\n    \"#{migration_name}.rb\"\n  end\n\n  def migration_class_name\n    migration_name.camelize\n  end\n\n  def migration_version\n    if Rails.version.start_with? \"5\"\n      \"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/generators/paperclip/templates/paperclip_migration.rb.erb",
    "content": "class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>\n  def self.up\n    change_table :<%= table_name %> do |t|\n<% attachment_names.each do |attachment| -%>\n      t.attachment :<%= attachment %>\n<% end -%>\n    end\n  end\n\n  def self.down\n<% attachment_names.each do |attachment| -%>\n    remove_attachment :<%= table_name %>, :<%= attachment %>\n<% end -%>\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/attachment.rb",
    "content": "require 'uri'\nrequire 'paperclip/url_generator'\nrequire 'active_support/deprecation'\nrequire 'active_support/core_ext/string/inflections'\n\nmodule Paperclip\n  # The Attachment class manages the files for a given attachment. It saves\n  # when the model saves, deletes when the model is destroyed, and processes\n  # the file upon assignment.\n  class Attachment\n    def self.default_options\n      @default_options ||= {\n        :convert_options       => {},\n        :default_style         => :original,\n        :default_url           => \"/:attachment/:style/missing.png\",\n        :escape_url            => true,\n        :restricted_characters => /[&$+,\\/:;=?@<>\\[\\]\\{\\}\\|\\\\\\^~%# ]/,\n        :filename_cleaner      => nil,\n        :hash_data             => \":class/:attachment/:id/:style/:updated_at\",\n        :hash_digest           => \"SHA1\",\n        :interpolator          => Paperclip::Interpolations,\n        :only_process          => [],\n        :path                  => \":rails_root/public:url\",\n        :preserve_files        => false,\n        :processors            => [:thumbnail],\n        :source_file_options   => {},\n        :storage               => :filesystem,\n        :styles                => {},\n        :url                   => \"/system/:class/:attachment/:id_partition/:style/:filename\",\n        :url_generator         => Paperclip::UrlGenerator,\n        :use_default_time_zone => true,\n        :use_timestamp         => true,\n        :whiny                 => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],\n        :validate_media_type   => true,\n        :adapter_options       => { hash_digest: Digest::MD5 },\n        :check_validity_before_processing => true\n      }\n    end\n\n    attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny,\n                :options, :interpolator, :source_file_options\n    attr_accessor :post_processing\n\n    # Creates an Attachment object. +name+ is the name of the attachment,\n    # +instance+ is the model object instance it's attached to, and\n    # +options+ is the same as the hash passed to +has_attached_file+.\n    #\n    # Options include:\n    #\n    # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+\n    # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+\n    # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details\n    # +only_process+ - style args to be run through the post-processor. This defaults to the empty list (which is\n    #                  a special case that indicates all styles should be processed)\n    # +default_url+ - a URL for the missing image\n    # +default_style+ - the style to use when an argument is not specified e.g. #url, #path\n    # +storage+ - the storage mechanism. Defaults to :filesystem\n    # +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true\n    # +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails\n    # +use_default_time_zone+ - related to +use_timestamp+. Defaults to true\n    # +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation\n    # +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+\n    # +hash_secret+ - a secret passed to the +hash_digest+\n    # +convert_options+ - flags passed to the +convert+ command for processing\n    # +source_file_options+ - flags passed to the +convert+ command that controls how the file is read\n    # +processors+ - classes that transform the attachment. Defaults to [:thumbnail]\n    # +preserve_files+ - whether to keep files on the filesystem when deleting or clearing the attachment. Defaults to false\n    # +filename_cleaner+ - An object that responds to #call(filename) that will strip unacceptable charcters from filename\n    # +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations\n    # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator\n    # +escape_url+ - Perform URI escaping to URLs. Defaults to true\n    def initialize(name, instance, options = {})\n      @name              = name.to_sym\n      @name_string       = name.to_s\n      @instance          = instance\n\n      options = self.class.default_options.deep_merge(options)\n\n      @options               = options\n      @post_processing       = true\n      @queued_for_delete     = []\n      @queued_for_write      = {}\n      @errors                = {}\n      @dirty                 = false\n      @interpolator          = options[:interpolator]\n      @url_generator         = options[:url_generator].new(self)\n      @source_file_options   = options[:source_file_options]\n      @whiny                 = options[:whiny]\n\n      initialize_storage\n    end\n\n    # What gets called when you call instance.attachment = File. It clears\n    # errors, assigns attributes, and processes the file. It also queues up the\n    # previous file for deletion, to be flushed away on #save of its host.  In\n    # addition to form uploads, you can also assign another Paperclip\n    # attachment:\n    #   new_user.avatar = old_user.avatar\n    def assign(uploaded_file)\n      @file = Paperclip.io_adapters.for(uploaded_file,\n                                        @options[:adapter_options])\n      ensure_required_accessors!\n      ensure_required_validations!\n\n      if @file.assignment?\n        clear(*only_process)\n\n        if @file.nil?\n          nil\n        else\n          assign_attributes\n          post_process_file\n          reset_file_if_original_reprocessed\n        end\n      else\n        nil\n      end\n    end\n\n    # Returns the public URL of the attachment with a given style. This does\n    # not necessarily need to point to a file that your Web server can access\n    # and can instead point to an action in your app, for example for fine grained\n    # security; this has a serious performance tradeoff.\n    #\n    # Options:\n    #\n    # +timestamp+ - Add a timestamp to the end of the URL. Default: true.\n    # +escape+    - Perform URI escaping to the URL. Default: true.\n    #\n    # Global controls (set on has_attached_file):\n    #\n    # +interpolator+  - The object that fills in a URL pattern's variables.\n    # +default_url+   - The image to show when the attachment has no image.\n    # +url+           - The URL for a saved image.\n    # +url_generator+ - The object that generates a URL. Default: Paperclip::UrlGenerator.\n    #\n    # As mentioned just above, the object that generates this URL can be passed\n    # in, for finer control. This object must respond to two methods:\n    #\n    # +#new(Paperclip::Attachment, options_hash)+\n    # +#for(style_name, options_hash)+\n\n    def url(style_name = default_style, options = {})\n      if options == true || options == false # Backwards compatibility.\n        @url_generator.for(style_name, default_options.merge(:timestamp => options))\n      else\n        @url_generator.for(style_name, default_options.merge(options))\n      end\n    end\n\n    def default_options\n      {\n        :timestamp => @options[:use_timestamp],\n        :escape => @options[:escape_url]\n      }\n    end\n\n    # Alias to +url+ that allows using the expiring_url method provided by the cloud\n    # storage implementations, but keep using filesystem storage for development and\n    # testing.\n    def expiring_url(time = 3600, style_name = default_style)\n      url(style_name)\n    end\n\n    # Returns the path of the attachment as defined by the :path option. If the\n    # file is stored in the filesystem the path refers to the path of the file\n    # on disk. If the file is stored in S3, the path is the \"key\" part of the\n    # URL, and the :bucket option refers to the S3 bucket.\n    def path(style_name = default_style)\n      path = original_filename.nil? ? nil : interpolate(path_option, style_name)\n      path.respond_to?(:unescape) ? path.unescape : path\n    end\n\n    # :nodoc:\n    def staged_path(style_name = default_style)\n      if staged?\n        @queued_for_write[style_name].path\n      end\n    end\n\n    # :nodoc:\n    def staged?\n      ! @queued_for_write.empty?\n    end\n\n    # Alias to +url+\n    def to_s style_name = default_style\n      url(style_name)\n    end\n\n    def as_json(options = nil)\n      to_s((options && options[:style]) || default_style)\n    end\n\n    def default_style\n      @options[:default_style]\n    end\n\n    def styles\n      if @options[:styles].respond_to?(:call) || @normalized_styles.nil?\n        styles = @options[:styles]\n        styles = styles.call(self) if styles.respond_to?(:call)\n\n        @normalized_styles = styles.dup\n        styles.each_pair do |name, options|\n          @normalized_styles[name.to_sym] = Paperclip::Style.new(name.to_sym, options.dup, self)\n        end\n      end\n      @normalized_styles\n    end\n\n    def only_process\n      only_process = @options[:only_process].dup\n      only_process = only_process.call(self) if only_process.respond_to?(:call)\n      only_process.map(&:to_sym)\n    end\n\n    def processors\n      processing_option = @options[:processors]\n\n      if processing_option.respond_to?(:call)\n        processing_option.call(instance)\n      else\n        processing_option\n      end\n    end\n\n    # Returns an array containing the errors on this attachment.\n    def errors\n      @errors\n    end\n\n    # Returns true if there are changes that need to be saved.\n    def dirty?\n      @dirty\n    end\n\n    # Saves the file, if there are no errors. If there are, it flushes them to\n    # the instance's errors and returns false, cancelling the save.\n    def save\n      flush_deletes unless @options[:keep_old_files]\n      process = only_process\n      if process.any? && !process.include?(:original)\n        @queued_for_write.except!(:original)\n      end\n      flush_writes\n      @dirty = false\n      true\n    end\n\n    # Clears out the attachment. Has the same effect as previously assigning\n    # nil to the attachment. Does NOT save. If you wish to clear AND save,\n    # use #destroy.\n    def clear(*styles_to_clear)\n      if styles_to_clear.any?\n        queue_some_for_delete(*styles_to_clear)\n      else\n        queue_all_for_delete\n        @queued_for_write  = {}\n        @errors            = {}\n      end\n    end\n\n    # Destroys the attachment. Has the same effect as previously assigning\n    # nil to the attachment *and saving*. This is permanent. If you wish to\n    # wipe out the existing attachment but not save, use #clear.\n    def destroy\n      clear\n      save\n    end\n\n    # Returns the uploaded file if present.\n    def uploaded_file\n      instance_read(:uploaded_file)\n    end\n\n    # Returns the name of the file as originally assigned, and lives in the\n    # <attachment>_file_name attribute of the model.\n    def original_filename\n      instance_read(:file_name)\n    end\n\n    # Returns the size of the file as originally assigned, and lives in the\n    # <attachment>_file_size attribute of the model.\n    def size\n      instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)\n    end\n\n    # Returns the fingerprint of the file, if one's defined. The fingerprint is\n    # stored in the <attachment>_fingerprint attribute of the model.\n    def fingerprint\n      instance_read(:fingerprint)\n    end\n\n    # Returns the content_type of the file as originally assigned, and lives\n    # in the <attachment>_content_type attribute of the model.\n    def content_type\n      instance_read(:content_type)\n    end\n\n    # Returns the creation time of the file as originally assigned, and\n    # lives in the <attachment>_created_at attribute of the model.\n    def created_at\n      if able_to_store_created_at?\n        time = instance_read(:created_at)\n        time && time.to_f.to_i\n      end\n    end\n\n    # Returns the last modified time of the file as originally assigned, and\n    # lives in the <attachment>_updated_at attribute of the model.\n    def updated_at\n      time = instance_read(:updated_at)\n      time && time.to_f.to_i\n    end\n\n    # The time zone to use for timestamp interpolation.  Using the default\n    # time zone ensures that results are consistent across all threads.\n    def time_zone\n      @options[:use_default_time_zone] ? Time.zone_default : Time.zone\n    end\n\n    # Returns a unique hash suitable for obfuscating the URL of an otherwise\n    # publicly viewable attachment.\n    def hash_key(style_name = default_style)\n      raise ArgumentError, \"Unable to generate hash without :hash_secret\" unless @options[:hash_secret]\n      require 'openssl' unless defined?(OpenSSL)\n      data = interpolate(@options[:hash_data], style_name)\n      OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data)\n    end\n\n    # This method really shouldn't be called that often. Its expected use is\n    # in the paperclip:refresh rake task and that's it. It will regenerate all\n    # thumbnails forcefully, by reobtaining the original file and going through\n    # the post-process again.\n    # NOTE: Calling reprocess WILL NOT delete existing files. This is due to\n    # inconsistencies in timing of S3 commands. It's possible that calling\n    # #reprocess! will lose data if the files are not kept.\n    def reprocess!(*style_args)\n      saved_flags = @options.slice(\n        :only_process,\n        :preserve_files,\n        :check_validity_before_processing\n      )\n      @options[:only_process] = style_args\n      @options[:preserve_files] = true\n      @options[:check_validity_before_processing] = false\n\n      begin\n        assign(self)\n        save\n        instance.save\n      rescue Errno::EACCES => e\n        warn \"#{e} - skipping file.\"\n        false\n      ensure\n        @options.merge!(saved_flags)\n      end\n    end\n\n    # Returns true if a file has been assigned.\n    def file?\n      original_filename.present?\n    end\n\n    alias :present? :file?\n\n    def blank?\n      not present?\n    end\n\n    # Determines whether the instance responds to this attribute. Used to prevent\n    # calculations on fields we won't even store.\n    def instance_respond_to?(attr)\n      instance.respond_to?(:\"#{name}_#{attr}\")\n    end\n\n    # Writes the attachment-specific attribute on the instance. For example,\n    # instance_write(:file_name, \"me.jpg\") will write \"me.jpg\" to the instance's\n    # \"avatar_file_name\" field (assuming the attachment is called avatar).\n    def instance_write(attr, value)\n      setter = :\"#{@name_string}_#{attr}=\"\n      if instance.respond_to?(setter)\n        instance.send(setter, value)\n      end\n    end\n\n    # Reads the attachment-specific attribute on the instance. See instance_write\n    # for more details.\n    def instance_read(attr)\n      getter = :\"#{@name_string}_#{attr}\"\n      if instance.respond_to?(getter)\n        instance.send(getter)\n      end\n    end\n\n    private\n\n    def path_option\n      @options[:path].respond_to?(:call) ? @options[:path].call(self) : @options[:path]\n    end\n\n    def active_validator_classes\n      @instance.class.validators.map(&:class)\n    end\n\n    def missing_required_validator?\n      (active_validator_classes.flat_map(&:ancestors) & Paperclip::REQUIRED_VALIDATORS).empty?\n    end\n\n    def ensure_required_validations!\n      if missing_required_validator?\n        raise Paperclip::Errors::MissingRequiredValidatorError\n      end\n    end\n\n    def ensure_required_accessors! #:nodoc:\n      %w(file_name).each do |field|\n        unless @instance.respond_to?(\"#{@name_string}_#{field}\") && @instance.respond_to?(\"#{@name_string}_#{field}=\")\n          raise Paperclip::Error.new(\"#{@instance.class} model missing required attr_accessor for '#{@name_string}_#{field}'\")\n        end\n      end\n    end\n\n    def log message #:nodoc:\n      Paperclip.log(message)\n    end\n\n    def initialize_storage #:nodoc:\n      storage_class_name = @options[:storage].to_s.downcase.camelize\n      begin\n        storage_module = Paperclip::Storage.const_get(storage_class_name)\n      rescue NameError\n        raise Errors::StorageMethodNotFound, \"Cannot load storage module '#{storage_class_name}'\"\n      end\n      self.extend(storage_module)\n    end\n\n    def assign_attributes\n      @queued_for_write[:original] = @file\n      assign_file_information\n      assign_fingerprint { @file.fingerprint }\n      assign_timestamps\n    end\n\n    def assign_file_information\n      instance_write(:file_name, cleanup_filename(@file.original_filename))\n      instance_write(:content_type, @file.content_type.to_s.strip)\n      instance_write(:file_size, @file.size)\n    end\n\n    def assign_fingerprint\n      if instance_respond_to?(:fingerprint)\n        instance_write(:fingerprint, yield)\n      end\n    end\n\n    def assign_timestamps\n      if has_enabled_but_unset_created_at?\n        instance_write(:created_at, Time.now)\n      end\n\n      instance_write(:updated_at, Time.now)\n    end\n\n    def post_process_file\n      dirty!\n\n      if post_processing\n        post_process(*only_process)\n      end\n    end\n\n    def dirty!\n      @dirty = true\n    end\n\n    def reset_file_if_original_reprocessed\n      instance_write(:file_size, @queued_for_write[:original].size)\n      assign_fingerprint { @queued_for_write[:original].fingerprint }\n      reset_updater\n    end\n\n    def reset_updater\n      if instance.respond_to?(updater)\n        instance.send(updater)\n      end\n    end\n\n    def updater\n      :\"#{name}_file_name_will_change!\"\n    end\n\n    def extra_options_for(style) #:nodoc:\n      process_options(:convert_options, style)\n    end\n\n    def extra_source_file_options_for(style) #:nodoc:\n      process_options(:source_file_options, style)\n    end\n\n    def process_options(options_type, style) #:nodoc:\n      all_options   = @options[options_type][:all]\n      all_options   = all_options.call(instance)   if all_options.respond_to?(:call)\n      style_options = @options[options_type][style]\n      style_options = style_options.call(instance) if style_options.respond_to?(:call)\n\n      [ style_options, all_options ].compact.join(\" \")\n    end\n\n    def post_process(*style_args) #:nodoc:\n      return if @queued_for_write[:original].nil?\n\n      instance.run_paperclip_callbacks(:post_process) do\n        instance.run_paperclip_callbacks(:\"#{name}_post_process\") do\n          if !@options[:check_validity_before_processing] || !instance.errors.any?\n            post_process_styles(*style_args)\n          end\n        end\n      end\n    end\n\n    def post_process_styles(*style_args) #:nodoc:\n      post_process_style(:original, styles[:original]) if styles.include?(:original) && process_style?(:original, style_args)\n      styles.reject{ |name, style| name == :original }.each do |name, style|\n        post_process_style(name, style) if process_style?(name, style_args)\n      end\n    end\n\n    def post_process_style(name, style) #:nodoc:\n      begin\n        raise RuntimeError.new(\"Style #{name} has no processors defined.\") if style.processors.blank?\n        intermediate_files = []\n        original = @queued_for_write[:original]\n\n        @queued_for_write[name] = style.processors.\n          reduce(original) do |file, processor|\n          file = Paperclip.processor(processor).make(file, style.processor_options, self)\n          intermediate_files << file unless file == @queued_for_write[:original]\n          # if we're processing the original, close + unlink the source tempfile\n          if name == :original\n            @queued_for_write[:original].close(true)\n          end\n          file\n        end\n\n        unadapted_file = @queued_for_write[name]\n        @queued_for_write[name] = Paperclip.io_adapters.\n          for(@queued_for_write[name], @options[:adapter_options])\n        unadapted_file.close if unadapted_file.respond_to?(:close)\n        @queued_for_write[name]\n      rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e\n        log(\"An error was received while processing: #{e.inspect}\")\n        (@errors[:processing] ||= []) << e.message if @options[:whiny]\n      ensure\n        unlink_files(intermediate_files)\n      end\n    end\n\n    def process_style?(style_name, style_args) #:nodoc:\n      style_args.empty? || style_args.include?(style_name)\n    end\n\n    def interpolate(pattern, style_name = default_style) #:nodoc:\n      interpolator.interpolate(pattern, self, style_name)\n    end\n\n    def queue_some_for_delete(*styles)\n      @queued_for_delete += styles.uniq.map do |style|\n        path(style) if exists?(style)\n      end.compact\n    end\n\n    def queue_all_for_delete #:nodoc:\n      return if !file?\n      unless @options[:preserve_files]\n        @queued_for_delete += [:original, *styles.keys].uniq.map do |style|\n          path(style) if exists?(style)\n        end.compact\n      end\n      instance_write(:file_name, nil)\n      instance_write(:content_type, nil)\n      instance_write(:file_size, nil)\n      instance_write(:fingerprint, nil)\n      instance_write(:created_at, nil) if has_enabled_but_unset_created_at?\n      instance_write(:updated_at, nil)\n    end\n\n    def flush_errors #:nodoc:\n      @errors.each do |error, message|\n        [message].flatten.each {|m| instance.errors.add(name, m) }\n      end\n    end\n\n    # called by storage after the writes are flushed and before @queued_for_write is cleared\n    def after_flush_writes\n      unlink_files(@queued_for_write.values)\n    end\n\n    def unlink_files(files)\n      Array(files).each do |file|\n        file.close unless file.closed?\n\n        begin\n          file.unlink if file.respond_to?(:unlink)\n        rescue Errno::ENOENT\n        end\n      end\n    end\n\n    # You can either specifiy :restricted_characters or you can define your own\n    # :filename_cleaner object. This object needs to respond to #call and takes\n    # the filename that will be cleaned. It should return the cleaned filename.\n    def filename_cleaner\n      @options[:filename_cleaner] || FilenameCleaner.new(@options[:restricted_characters])\n    end\n\n    def cleanup_filename(filename)\n      filename_cleaner.call(filename)\n    end\n\n    # Check if attachment database table has a created_at field\n    def able_to_store_created_at?\n      @instance.respond_to?(\"#{name}_created_at\".to_sym)\n    end\n\n    # Check if attachment database table has a created_at field which is not yet set\n    def has_enabled_but_unset_created_at?\n      able_to_store_created_at? && !instance_read(:created_at)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/attachment_registry.rb",
    "content": "require 'singleton'\n\nmodule Paperclip\n  class AttachmentRegistry\n    include Singleton\n\n    def self.register(klass, attachment_name, attachment_options)\n      instance.register(klass, attachment_name, attachment_options)\n    end\n\n    def self.clear\n      instance.clear\n    end\n\n    def self.names_for(klass)\n      instance.names_for(klass)\n    end\n\n    def self.each_definition(&block)\n      instance.each_definition(&block)\n    end\n\n    def self.definitions_for(klass)\n      instance.definitions_for(klass)\n    end\n\n    def initialize\n      clear\n    end\n\n    def register(klass, attachment_name, attachment_options)\n      @attachments ||= {}\n      @attachments[klass] ||= {}\n      @attachments[klass][attachment_name] = attachment_options\n    end\n\n    def clear\n      @attachments = Hash.new { |h,k| h[k] = {} }\n    end\n\n    def names_for(klass)\n      @attachments[klass].keys\n    end\n\n    def each_definition\n      @attachments.each do |klass, attachments|\n        attachments.each do |name, options|\n          yield klass, name, options\n        end\n      end\n    end\n\n    def definitions_for(klass)\n      parent_classes = klass.ancestors.reverse\n      parent_classes.each_with_object({}) do |ancestor, inherited_definitions|\n        inherited_definitions.deep_merge! @attachments[ancestor]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/callbacks.rb",
    "content": "module Paperclip\n  module Callbacks\n    def self.included(base)\n      base.extend(Defining)\n      base.send(:include, Running)\n    end\n\n    module Defining\n      def define_paperclip_callbacks(*callbacks)\n        define_callbacks(*[callbacks, { terminator: hasta_la_vista_baby }].flatten)\n        callbacks.each do |callback|\n          eval <<-end_callbacks\n            def before_#{callback}(*args, &blk)\n              set_callback(:#{callback}, :before, *args, &blk)\n            end\n            def after_#{callback}(*args, &blk)\n              set_callback(:#{callback}, :after, *args, &blk)\n            end\n          end_callbacks\n        end\n      end\n\n      private\n\n      def hasta_la_vista_baby\n        lambda do |_, result|\n          if result.respond_to?(:call)\n            result.call == false\n          else\n            result == false\n          end\n        end\n      end\n    end\n\n    module Running\n      def run_paperclip_callbacks(callback, &block)\n        run_callbacks(callback, &block)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/content_type_detector.rb",
    "content": "module Paperclip\n  class ContentTypeDetector\n    # The content-type detection strategy is as follows:\n    #\n    # 1. Blank/Empty files: If there's no filepath or the file is empty,\n    #    provide a sensible default (application/octet-stream or inode/x-empty)\n    #\n    # 2. Calculated match: Return the first result that is found by both the\n    #    `file` command and MIME::Types.\n    #\n    # 3. Standard types: Return the first standard (without an x- prefix) entry\n    #    in MIME::Types\n    #\n    # 4. Experimental types: If there were no standard types in MIME::Types\n    #    list, try to return the first experimental one\n    #\n    # 5. Raw `file` command: Just use the output of the `file` command raw, or\n    #    a sensible default. This is cached from Step 2.\n\n    EMPTY_TYPE = \"inode/x-empty\"\n    SENSIBLE_DEFAULT = \"application/octet-stream\"\n\n    def initialize(filepath)\n      @filepath = filepath\n    end\n\n    # Returns a String describing the file's content type\n    def detect\n      if blank_name?\n        SENSIBLE_DEFAULT\n      elsif empty_file?\n        EMPTY_TYPE\n      elsif calculated_type_matches.any?\n        calculated_type_matches.first\n      else\n        type_from_file_contents || SENSIBLE_DEFAULT\n      end.to_s\n    end\n\n    private\n\n    def blank_name?\n      @filepath.nil? || @filepath.empty?\n    end\n\n    def empty_file?\n      File.exist?(@filepath) && File.size(@filepath) == 0\n    end\n\n    alias :empty? :empty_file?\n\n    def calculated_type_matches\n      possible_types.select do |content_type|\n        content_type == type_from_file_contents\n      end\n    end\n\n    def possible_types\n      MIME::Types.type_for(@filepath).collect(&:content_type)\n    end\n\n    def type_from_file_contents\n      type_from_mime_magic || type_from_file_command\n    rescue Errno::ENOENT => e\n      Paperclip.log(\"Error while determining content type: #{e}\")\n      SENSIBLE_DEFAULT\n    end\n\n    def type_from_mime_magic\n      @type_from_mime_magic ||= File.open(@filepath) do |file|\n        MimeMagic.by_magic(file).try(:type)\n      end\n    end\n\n    def type_from_file_command\n      @type_from_file_command ||=\n        FileCommandContentTypeDetector.new(@filepath).detect\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/errors.rb",
    "content": "module Paperclip\n  # A base error class for Paperclip. Most of the error that will be thrown\n  # from Paperclip will inherits from this class.\n  class Error < StandardError\n  end\n\n  module Errors\n    # Will be thrown when a storage method is not found.\n    class StorageMethodNotFound < Paperclip::Error\n    end\n\n    # Will be thrown when a command or executable is not found.\n    class CommandNotFoundError < Paperclip::Error\n    end\n\n    # Attachments require a content_type or file_name validator,\n    # or to have explicitly opted out of them.\n    class MissingRequiredValidatorError < Paperclip::Error\n    end\n\n    # Will be thrown when ImageMagic cannot determine the uploaded file's\n    # metadata, usually this would mean the file is not an image. If you are\n    # consistently receiving this error on PDFs make sure that you have\n    # installed Ghostscript.\n    class NotIdentifiedByImageMagickError < Paperclip::Error\n    end\n\n    # Will be thrown if the interpolation is creating an infinite loop. If you\n    # are creating an interpolator which might cause an infinite loop, you\n    # should be throwing this error upon the infinite loop as well.\n    class InfiniteInterpolationError < Paperclip::Error\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/file_command_content_type_detector.rb",
    "content": "module Paperclip\n  class FileCommandContentTypeDetector\n    SENSIBLE_DEFAULT = \"application/octet-stream\"\n\n    def initialize(filename)\n      @filename = filename\n    end\n\n    def detect\n      type_from_file_command\n    end\n\n    private\n\n    def type_from_file_command\n      # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.\n      type = begin\n               Paperclip.run(\"file\", \"-b --mime :file\", file: @filename)\n             rescue Terrapin::CommandLineError => e\n               Paperclip.log(\"Error while determining content type: #{e}\")\n               SENSIBLE_DEFAULT\n             end\n\n      if type.nil? || type.match(/\\(.*?\\)/)\n        type = SENSIBLE_DEFAULT\n      end\n      type.split(/[:;\\s]+/)[0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/filename_cleaner.rb",
    "content": "module Paperclip\n  class FilenameCleaner\n    def initialize(invalid_character_regex)\n      @invalid_character_regex = invalid_character_regex\n    end\n\n    def call(filename)\n      if @invalid_character_regex\n        filename.gsub(@invalid_character_regex, \"_\")\n      else\n        filename\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/geometry.rb",
    "content": "module Paperclip\n\n  # Defines the geometry of an image.\n  class Geometry\n    attr_accessor :height, :width, :modifier\n\n    EXIF_ROTATED_ORIENTATION_VALUES = [5, 6, 7, 8]\n\n    # Gives a Geometry representing the given height and width\n    def initialize(width = nil, height = nil, modifier = nil)\n      if width.is_a?(Hash)\n        options = width\n        @height = options[:height].to_f\n        @width = options[:width].to_f\n        @modifier = options[:modifier]\n        @orientation = options[:orientation].to_i\n      else\n        @height = height.to_f\n        @width  = width.to_f\n        @modifier = modifier\n      end\n    end\n\n    # Extracts the Geometry from a file (or path to a file)\n    def self.from_file(file)\n      GeometryDetector.new(file).make\n    end\n\n    # Extracts the Geometry from a \"WxH,O\" string\n    # Where W is the width, H is the height,\n    # and O is the EXIF orientation\n    def self.parse(string)\n      GeometryParser.new(string).make\n    end\n\n    # Swaps the height and width if necessary\n    def auto_orient\n      if EXIF_ROTATED_ORIENTATION_VALUES.include?(@orientation)\n        @height, @width = @width, @height\n        @orientation -= 4\n      end\n    end\n\n    # True if the dimensions represent a square\n    def square?\n      height == width\n    end\n\n    # True if the dimensions represent a horizontal rectangle\n    def horizontal?\n      height < width\n    end\n\n    # True if the dimensions represent a vertical rectangle\n    def vertical?\n      height > width\n    end\n\n    # The aspect ratio of the dimensions.\n    def aspect\n      width / height\n    end\n\n    # Returns the larger of the two dimensions\n    def larger\n      [height, width].max\n    end\n\n    # Returns the smaller of the two dimensions\n    def smaller\n      [height, width].min\n    end\n\n    # Returns the width and height in a format suitable to be passed to Geometry.parse\n    def to_s\n      s = \"\"\n      s << width.to_i.to_s if width > 0\n      s << \"x#{height.to_i}\" if height > 0\n      s << modifier.to_s\n      s\n    end\n\n    # Same as to_s\n    def inspect\n      to_s\n    end\n\n    # Returns the scaling and cropping geometries (in string-based ImageMagick format)\n    # neccessary to transform this Geometry into the Geometry given. If crop is true,\n    # then it is assumed the destination Geometry will be the exact final resolution.\n    # In this case, the source Geometry is scaled so that an image containing the\n    # destination Geometry would be completely filled by the source image, and any\n    # overhanging image would be cropped. Useful for square thumbnail images. The cropping\n    # is weighted at the center of the Geometry.\n    def transformation_to dst, crop = false\n      if crop\n        ratio = Geometry.new( dst.width / self.width, dst.height / self.height )\n        scale_geometry, scale = scaling(dst, ratio)\n        crop_geometry         = cropping(dst, ratio, scale)\n      else\n        scale_geometry        = dst.to_s\n      end\n\n      [ scale_geometry, crop_geometry ]\n    end\n\n    # resize to a new geometry\n    # @param geometry [String] the Paperclip geometry definition to resize to\n    # @example\n    #   Paperclip::Geometry.new(150, 150).resize_to('50x50!')\n    #   #=> Paperclip::Geometry(50, 50)\n    def resize_to(geometry)\n      new_geometry = Paperclip::Geometry.parse geometry\n      case new_geometry.modifier\n      when '!', '#'\n        new_geometry\n      when '>'\n        if new_geometry.width >= self.width && new_geometry.height >= self.height\n          self\n        else\n          scale_to new_geometry\n        end\n      when '<'\n        if new_geometry.width <= self.width || new_geometry.height <= self.height\n          self\n        else\n          scale_to new_geometry\n        end\n      else\n        scale_to new_geometry\n      end\n    end\n\n    private\n\n    def scaling dst, ratio\n      if ratio.horizontal? || ratio.square?\n        [ \"%dx\" % dst.width, ratio.width ]\n      else\n        [ \"x%d\" % dst.height, ratio.height ]\n      end\n    end\n\n    def cropping dst, ratio, scale\n      if ratio.horizontal? || ratio.square?\n        \"%dx%d+%d+%d\" % [ dst.width, dst.height, 0, (self.height * scale - dst.height) / 2 ]\n      else\n        \"%dx%d+%d+%d\" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ]\n      end\n    end\n\n    # scale to the requested geometry and preserve the aspect ratio\n    def scale_to(new_geometry)\n      scale = [new_geometry.width.to_f / self.width.to_f , new_geometry.height.to_f / self.height.to_f].min\n      Paperclip::Geometry.new((self.width * scale).round, (self.height * scale).round)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/geometry_detector_factory.rb",
    "content": "module Paperclip\n  class GeometryDetector\n    def initialize(file)\n      @file = file\n      raise_if_blank_file\n    end\n\n    def make\n      geometry = GeometryParser.new(geometry_string.strip).make\n      geometry || raise(Errors::NotIdentifiedByImageMagickError.new)\n    end\n\n    private\n\n    def geometry_string\n      begin\n        orientation = Paperclip.options[:use_exif_orientation] ?\n          \"%[exif:orientation]\" : \"1\"\n        Paperclip.run(\n          Paperclip.options[:is_windows] ? \"magick identify\" : \"identify\",\n          \"-format '%wx%h,#{orientation}' :file\", {\n            :file => \"#{path}[0]\"\n          }, {\n            :swallow_stderr => true\n          }\n        )\n      rescue Terrapin::ExitStatusError\n        \"\"\n      rescue Terrapin::CommandNotFoundError => e\n        raise_because_imagemagick_missing\n      end\n    end\n\n    def path\n      @file.respond_to?(:path) ? @file.path : @file\n    end\n\n    def raise_if_blank_file\n      if path.blank?\n        raise Errors::NotIdentifiedByImageMagickError.new(\"Cannot find the geometry of a file with a blank name\")\n      end\n    end\n\n    def raise_because_imagemagick_missing\n      raise Errors::CommandNotFoundError.new(\"Could not run the `identify` command. Please install ImageMagick.\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/geometry_parser_factory.rb",
    "content": "module Paperclip\n  class GeometryParser\n    FORMAT = /\\b(\\d*)x?(\\d*)\\b(?:,(\\d?))?(\\@\\>|\\>\\@|[\\>\\<\\#\\@\\%^!])?/i\n    def initialize(string)\n      @string = string\n    end\n\n    def make\n      if match\n        Geometry.new(\n          :height => @height,\n          :width => @width,\n          :modifier => @modifier,\n          :orientation => @orientation\n        )\n      end\n    end\n\n    private\n\n    def match\n      if actual_match = @string && @string.match(FORMAT)\n        @width = actual_match[1]\n        @height = actual_match[2]\n        @orientation = actual_match[3]\n        @modifier = actual_match[4]\n      end\n      actual_match\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/glue.rb",
    "content": "require 'paperclip/callbacks'\nrequire 'paperclip/validators'\nrequire 'paperclip/schema'\n\nmodule Paperclip\n  module Glue\n    def self.included(base)\n      base.extend ClassMethods\n      base.send :include, Callbacks\n      base.send :include, Validators\n      base.send :include, Schema if defined? ActiveRecord::Base\n\n      locale_path = Dir.glob(File.dirname(__FILE__) + \"/locales/*.{rb,yml}\")\n      I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/has_attached_file.rb",
    "content": "module Paperclip\n  class HasAttachedFile\n    def self.define_on(klass, name, options)\n      new(klass, name, options).define\n    end\n\n    def initialize(klass, name, options)\n      @klass = klass\n      @name = name\n      @options = options\n    end\n\n    def define\n      define_flush_errors\n      define_getters\n      define_setter\n      define_query\n      register_new_attachment\n      add_active_record_callbacks\n      add_paperclip_callbacks\n      add_required_validations\n    end\n\n    private\n\n    def define_flush_errors\n      @klass.send(:validates_each, @name) do |record, attr, value|\n        attachment = record.send(@name)\n        attachment.send(:flush_errors)\n      end\n    end\n\n    def define_getters\n      define_instance_getter\n      define_class_getter\n    end\n\n    def define_instance_getter\n      name = @name\n      options = @options\n\n      @klass.send :define_method, @name do |*args|\n        ivar = \"@attachment_#{name}\"\n        attachment = instance_variable_get(ivar)\n\n        if attachment.nil?\n          attachment = Attachment.new(name, self, options)\n          instance_variable_set(ivar, attachment)\n        end\n\n        if args.length > 0\n          attachment.to_s(args.first)\n        else\n          attachment\n        end\n      end\n    end\n\n    def define_class_getter\n      @klass.extend(ClassMethods)\n    end\n\n    def define_setter\n      name = @name\n      @klass.send :define_method, \"#{@name}=\" do |file|\n        send(name).assign(file)\n      end\n    end\n\n    def define_query\n      name = @name\n      @klass.send :define_method, \"#{@name}?\" do\n        send(name).file?\n      end\n    end\n\n    def register_new_attachment\n      Paperclip::AttachmentRegistry.register(@klass, @name, @options)\n    end\n\n    def add_required_validations\n      options = Paperclip::Attachment.default_options.deep_merge(@options)\n      if options[:validate_media_type] != false\n        name = @name\n        @klass.validates_media_type_spoof_detection name,\n          :if => ->(instance){ instance.send(name).dirty? }\n      end\n    end\n\n    def add_active_record_callbacks\n      name = @name\n      @klass.send(:after_save) { send(name).send(:save) }\n      @klass.send(:before_destroy) { send(name).send(:queue_all_for_delete) }\n      if @klass.respond_to?(:after_commit)\n        @klass.send(:after_commit, on: :destroy) do\n          send(name).send(:flush_deletes)\n        end\n      else\n        @klass.send(:after_destroy) { send(name).send(:flush_deletes) }\n      end\n    end\n\n    def add_paperclip_callbacks\n      @klass.send(\n        :define_paperclip_callbacks,\n        :post_process, :\"#{@name}_post_process\")\n    end\n\n    module ClassMethods\n      def attachment_definitions\n        Paperclip::AttachmentRegistry.definitions_for(self)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/helpers.rb",
    "content": "module Paperclip\n  module Helpers\n    def configure\n      yield(self) if block_given?\n    end\n\n    def interpolates key, &block\n      Paperclip::Interpolations[key] = block\n    end\n\n    # The run method takes the name of a binary to run, the arguments\n    # to that binary, the values to interpolate and some local options.\n    #\n    #  :cmd -> The name of a binary to run.\n    #\n    #  :arguments -> The command line arguments to that binary.\n    #\n    #  :interpolation_values -> Values to be interpolated into the arguments.\n    #\n    #  :local_options -> The options to be used by Cocain::CommandLine.\n    #                    These could be: runner\n    #                                    logger\n    #                                    swallow_stderr\n    #                                    expected_outcodes\n    #                                    environment\n    #                                    runner_options\n    #\n    def run(cmd, arguments = \"\", interpolation_values = {}, local_options = {})\n      command_path = options[:command_path]\n      terrapin_path_array = Terrapin::CommandLine.path.try(:split, Terrapin::OS.path_separator)\n      Terrapin::CommandLine.path = [terrapin_path_array, command_path].flatten.compact.uniq\n      if logging? && (options[:log_command] || local_options[:log_command])\n        local_options = local_options.merge(:logger => logger)\n      end\n      Terrapin::CommandLine.new(cmd, arguments, local_options).run(interpolation_values)\n    end\n\n    # Find all instances of the given Active Record model +klass+ with attachment +name+.\n    # This method is used by the refresh rake tasks.\n    def each_instance_with_attachment(klass, name)\n      class_for(klass).unscoped.where(\"#{name}_file_name IS NOT NULL\").find_each do |instance|\n        yield(instance)\n      end\n    end\n\n    def class_for(class_name)\n      class_name.split('::').inject(Object) do |klass, partial_class_name|\n        if klass.const_defined?(partial_class_name)\n          klass.const_get(partial_class_name, false)\n        else\n          klass.const_missing(partial_class_name)\n        end\n      end\n    end\n\n    def reset_duplicate_clash_check!\n      @names_url = nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/interpolations/plural_cache.rb",
    "content": "module Paperclip\n  module Interpolations\n    class PluralCache\n      def initialize\n        @symbol_cache = {}.compare_by_identity\n        @klass_cache = {}.compare_by_identity\n      end\n\n      def pluralize_symbol(symbol)\n        @symbol_cache[symbol] ||= symbol.to_s.downcase.pluralize\n      end\n\n      def underscore_and_pluralize_class(klass)\n        @klass_cache[klass] ||= klass.name.underscore.pluralize\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/interpolations.rb",
    "content": "module Paperclip\n  # This module contains all the methods that are available for interpolation\n  # in paths and urls. To add your own (or override an existing one), you\n  # can either open this module and define it, or call the\n  # Paperclip.interpolates method.\n  module Interpolations\n    extend self\n    ID_PARTITION_LIMIT = 1_000_000_000\n\n    # Hash assignment of interpolations. Included only for compatibility,\n    # and is not intended for normal use.\n    def self.[]= name, block\n      define_method(name, &block)\n      @interpolators_cache = nil\n    end\n\n    # Hash access of interpolations. Included only for compatibility,\n    # and is not intended for normal use.\n    def self.[] name\n      method(name)\n    end\n\n    # Returns a sorted list of all interpolations.\n    def self.all\n      self.instance_methods(false).sort!\n    end\n\n    # Perform the actual interpolation. Takes the pattern to interpolate\n    # and the arguments to pass, which are the attachment and style name.\n    # You can pass a method name on your record as a symbol, which should turn\n    # an interpolation pattern for Paperclip to use.\n    def self.interpolate pattern, *args\n      pattern = args.first.instance.send(pattern) if pattern.kind_of? Symbol\n      result = pattern.dup\n      interpolators_cache.each do |method, token|\n        result.gsub!(token) { send(method, *args) } if result.include?(token)\n      end\n      result\n    end\n\n    def self.interpolators_cache\n      @interpolators_cache ||= all.reverse!.map! { |method| [method, \":#{method}\"] }\n    end\n\n    def self.plural_cache\n      @plural_cache ||= PluralCache.new\n    end\n\n    # Returns the filename, the same way as \":basename.:extension\" would.\n    def filename attachment, style_name\n      [ basename(attachment, style_name), extension(attachment, style_name) ].delete_if(&:empty?).join(\".\".freeze)\n    end\n\n    # Returns the interpolated URL. Will raise an error if the url itself\n    # contains \":url\" to prevent infinite recursion. This interpolation\n    # is used in the default :path to ease default specifications.\n    RIGHT_HERE = \"#{__FILE__.gsub(%r{\\A\\./}, \"\")}:#{__LINE__ + 3}\"\n    def url attachment, style_name\n      raise Errors::InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) }\n      attachment.url(style_name, :timestamp => false, :escape => false)\n    end\n\n    # Returns the timestamp as defined by the <attachment>_updated_at field\n    # in the server default time zone unless :use_global_time_zone is set\n    # to false.  Note that a Rails.config.time_zone change will still\n    # invalidate any path or URL that uses :timestamp.  For a\n    # time_zone-agnostic timestamp, use #updated_at.\n    def timestamp attachment, style_name\n      attachment.instance_read(:updated_at).in_time_zone(attachment.time_zone).to_s\n    end\n\n    # Returns an integer timestamp that is time zone-neutral, so that paths\n    # remain valid even if a server's time zone changes.\n    def updated_at attachment, style_name\n      attachment.updated_at\n    end\n\n    # Returns the Rails.root constant.\n    def rails_root attachment, style_name\n      Rails.root\n    end\n\n    # Returns the Rails.env constant.\n    def rails_env attachment, style_name\n      Rails.env\n    end\n\n    # Returns the underscored, pluralized version of the class name.\n    # e.g. \"users\" for the User class.\n    # NOTE: The arguments need to be optional, because some tools fetch\n    # all class names. Calling #class will return the expected class.\n    def class attachment = nil, style_name = nil\n      return super() if attachment.nil? && style_name.nil?\n      plural_cache.underscore_and_pluralize_class(attachment.instance.class)\n    end\n\n    # Returns the basename of the file. e.g. \"file\" for \"file.jpg\"\n    def basename attachment, style_name\n      File.basename(attachment.original_filename, \".*\".freeze)\n    end\n\n    # Returns the extension of the file. e.g. \"jpg\" for \"file.jpg\"\n    # If the style has a format defined, it will return the format instead\n    # of the actual extension.\n    def extension attachment, style_name\n      ((style = attachment.styles[style_name.to_s.to_sym]) && style[:format]) ||\n        File.extname(attachment.original_filename).sub(/\\A\\.+/, \"\".freeze)\n    end\n\n    # Returns the dot+extension of the file. e.g. \".jpg\" for \"file.jpg\"\n    # If the style has a format defined, it will return the format instead\n    # of the actual extension. If the extension is empty, no dot is added.\n    def dotextension attachment, style_name\n      ext = extension(attachment, style_name)\n      ext.empty? ? ext : \".#{ext}\"\n    end\n\n    # Returns an extension based on the content type. e.g. \"jpeg\" for\n    # \"image/jpeg\". If the style has a specified format, it will override the\n    # content-type detection.\n    #\n    # Each mime type generally has multiple extensions associated with it, so\n    # if the extension from the original filename is one of these extensions,\n    # that extension is used, otherwise, the first in the list is used.\n    def content_type_extension attachment, style_name\n      mime_type = MIME::Types[attachment.content_type]\n      extensions_for_mime_type = unless mime_type.empty?\n        mime_type.first.extensions\n      else\n        []\n      end\n\n      original_extension = extension(attachment, style_name)\n      style = attachment.styles[style_name.to_s.to_sym]\n      if style && style[:format]\n        style[:format].to_s\n      elsif extensions_for_mime_type.include? original_extension\n        original_extension\n      elsif !extensions_for_mime_type.empty?\n        extensions_for_mime_type.first\n      else\n        # It's possible, though unlikely, that the mime type is not in the\n        # database, so just use the part after the '/' in the mime type as the\n        # extension.\n        %r{/([^/]*)\\z}.match(attachment.content_type)[1]\n      end\n    end\n\n    # Returns the id of the instance.\n    def id attachment, style_name\n      attachment.instance.id\n    end\n\n    # Returns the #to_param of the instance.\n    def param attachment, style_name\n      attachment.instance.to_param\n    end\n\n    # Returns the fingerprint of the instance.\n    def fingerprint attachment, style_name\n      attachment.fingerprint\n    end\n\n    # Returns a the attachment hash.  See Paperclip::Attachment#hash_key for\n    # more details.\n    def hash attachment=nil, style_name=nil\n      if attachment && style_name\n        attachment.hash_key(style_name)\n      else\n        super()\n      end\n    end\n\n    # Returns the id of the instance in a split path form. e.g. returns\n    # 000/001/234 for an id of 1234.\n    def id_partition attachment, style_name\n      case id = attachment.instance.id\n      when Integer\n        if id < ID_PARTITION_LIMIT\n          (\"%09d\".freeze % id).scan(/\\d{3}/).join(\"/\".freeze)\n        else\n          (\"%012d\".freeze % id).scan(/\\d{3}/).join(\"/\".freeze)\n        end\n      when String\n        id.scan(/.{3}/).first(3).join(\"/\".freeze)\n      else\n        nil\n      end\n    end\n\n    # Returns the pluralized form of the attachment name. e.g.\n    # \"avatars\" for an attachment of :avatar\n    def attachment attachment, style_name\n      plural_cache.pluralize_symbol(attachment.name)\n    end\n\n    # Returns the style, or the default style if nil is supplied.\n    def style attachment, style_name\n      style_name || attachment.default_style\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/io_adapters/abstract_adapter.rb",
    "content": "require 'active_support/core_ext/module/delegation'\n\nmodule Paperclip\n  class AbstractAdapter\n    OS_RESTRICTED_CHARACTERS = %r{[/:]}\n\n    attr_reader :content_type, :original_filename, :size, :tempfile\n    delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink, :to => :@tempfile\n    alias :length :size\n\n    def initialize(target, options = {})\n      @target = target\n      @options = options\n    end\n\n    def fingerprint\n      @fingerprint ||= begin\n        digest = @options.fetch(:hash_digest).new\n        File.open(path, \"rb\") do |f|\n          buf = \"\"\n          digest.update(buf) while f.read(16384, buf)\n        end\n        digest.hexdigest\n      end\n    end\n\n    def read(length = nil, buffer = nil)\n      @tempfile.read(length, buffer)\n    end\n\n    def inspect\n      \"#{self.class}: #{self.original_filename}\"\n    end\n\n    def original_filename=(new_filename)\n      return unless new_filename\n      @original_filename = new_filename.gsub(OS_RESTRICTED_CHARACTERS, \"_\")\n    end\n\n    def nil?\n      false\n    end\n\n    def assignment?\n      true\n    end\n\n    private\n\n    def destination\n      @destination ||= TempfileFactory.new.generate(@original_filename.to_s)\n    end\n\n    def copy_to_tempfile(src)\n      link_or_copy_file(src.path, destination.path)\n      destination\n    end\n\n    def link_or_copy_file(src, dest)\n      begin\n        Paperclip.log(\"Trying to link #{src} to #{dest}\")\n        FileUtils.ln(src, dest, force: true) # overwrite existing\n      rescue Errno::EXDEV, Errno::EPERM, Errno::ENOENT, Errno::EEXIST => e\n        Paperclip.log(\n          \"Link failed with #{e.message}; copying link #{src} to #{dest}\"\n        )\n        FileUtils.cp(src, dest)\n      end\n\n      @destination.close\n      @destination.open.binmode\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/io_adapters/attachment_adapter.rb",
    "content": "module Paperclip\n  class AttachmentAdapter < AbstractAdapter\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        Paperclip::Attachment === target || Paperclip::Style === target\n      end\n    end\n\n    def initialize(target, options = {})\n      super\n      @target, @style = case target\n      when Paperclip::Attachment\n        [target, :original]\n      when Paperclip::Style\n        [target.attachment, target.name]\n      end\n\n      cache_current_values\n    end\n\n    private\n\n    def cache_current_values\n      self.original_filename = @target.original_filename\n      @content_type = @target.content_type\n      @tempfile = copy_to_tempfile(@target)\n      @size = @tempfile.size || @target.size\n    end\n\n    def copy_to_tempfile(source)\n      if source.staged?\n        link_or_copy_file(source.staged_path(@style), destination.path)\n      else\n        begin\n          source.copy_to_local_file(@style, destination.path)\n        rescue Errno::EACCES\n          # clean up lingering tempfile if we cannot access source file\n          destination.close(true)\n          raise\n        end\n      end\n      destination\n    end\n  end\nend\n\nPaperclip::AttachmentAdapter.register\n"
  },
  {
    "path": "lib/paperclip/io_adapters/data_uri_adapter.rb",
    "content": "module Paperclip\n  class DataUriAdapter < StringioAdapter\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        String === target && target =~ REGEXP\n      end\n    end\n\n    REGEXP = /\\Adata:([-\\w]+\\/[-\\w\\+\\.]+)?;base64,(.*)/m\n\n    def initialize(target_uri, options = {})\n      super(extract_target(target_uri), options)\n    end\n\n    private\n\n    def extract_target(uri)\n      data_uri_parts = uri.match(REGEXP) || []\n      StringIO.new(Base64.decode64(data_uri_parts[2] || \"\"))\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/io_adapters/empty_string_adapter.rb",
    "content": "module Paperclip\n  class EmptyStringAdapter < AbstractAdapter\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        target.is_a?(String) && target.empty?\n      end\n    end\n\n    def nil?\n      false\n    end\n\n    def assignment?\n      false\n    end\n  end\nend\n\nPaperclip::EmptyStringAdapter.register\n"
  },
  {
    "path": "lib/paperclip/io_adapters/file_adapter.rb",
    "content": "module Paperclip\n  class FileAdapter < AbstractAdapter\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        File === target || ::Tempfile === target\n      end\n    end\n\n    def initialize(target, options = {})\n      super\n      cache_current_values\n    end\n\n    private\n\n    def cache_current_values\n      if @target.respond_to?(:original_filename)\n        self.original_filename = @target.original_filename\n      end\n      self.original_filename ||= File.basename(@target.path)\n      @tempfile = copy_to_tempfile(@target)\n      @content_type = ContentTypeDetector.new(@target.path).detect\n      @size = File.size(@target)\n    end\n  end\nend\n\nPaperclip::FileAdapter.register\n"
  },
  {
    "path": "lib/paperclip/io_adapters/http_url_proxy_adapter.rb",
    "content": "module Paperclip\n  class HttpUrlProxyAdapter < UriAdapter\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        String === target && target =~ REGEXP\n      end\n    end\n\n    REGEXP = /\\Ahttps?:\\/\\//\n\n    def initialize(target, options = {})\n      escaped = URI.escape(target)\n      super(URI(target == URI.unescape(target) ? escaped : target), options)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/io_adapters/identity_adapter.rb",
    "content": "module Paperclip\n  class IdentityAdapter < AbstractAdapter\n    def self.register\n      Paperclip.io_adapters.register Paperclip::IdentityAdapter.new do |target|\n        Paperclip.io_adapters.registered?(target)\n      end\n    end\n\n    def initialize\n    end\n\n    def new(target, _)\n      target\n    end\n  end\nend\n\nPaperclip::IdentityAdapter.register\n"
  },
  {
    "path": "lib/paperclip/io_adapters/nil_adapter.rb",
    "content": "module Paperclip\n  class NilAdapter < AbstractAdapter\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        target.nil? || ((Paperclip::Attachment === target) && !target.present?)\n      end\n    end\n\n    def initialize(_target, _options = {}); end\n\n    def original_filename\n      \"\"\n    end\n\n    def content_type\n      \"\"\n    end\n\n    def size\n      0\n    end\n\n    def nil?\n      true\n    end\n\n    def read(*_args)\n      nil\n    end\n\n    def eof?\n      true\n    end\n  end\nend\n\nPaperclip::NilAdapter.register\n"
  },
  {
    "path": "lib/paperclip/io_adapters/registry.rb",
    "content": "module Paperclip\n  class AdapterRegistry\n    class NoHandlerError < Paperclip::Error; end\n\n    attr_reader :registered_handlers\n\n    def initialize\n      @registered_handlers = []\n    end\n\n    def register(handler_class, &block)\n      @registered_handlers << [block, handler_class]\n    end\n\n    def unregister(handler_class)\n      @registered_handlers.reject! { |_, klass| klass == handler_class }\n    end\n\n    def handler_for(target)\n      @registered_handlers.each do |tester, handler|\n        return handler if tester.call(target)\n      end\n      raise NoHandlerError.new(\"No handler found for #{target.inspect}\")\n    end\n\n    def registered?(target)\n      @registered_handlers.any? do |tester, handler|\n        handler === target\n      end\n    end\n\n    def for(target, options = {})\n      handler_for(target).new(target, options)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/io_adapters/stringio_adapter.rb",
    "content": "module Paperclip\n  class StringioAdapter < AbstractAdapter\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        StringIO === target\n      end\n    end\n\n    def initialize(target, options = {})\n      super\n      cache_current_values\n    end\n\n    attr_writer :content_type\n\n    private\n\n    def cache_current_values\n      self.original_filename = @target.original_filename if @target.respond_to?(:original_filename)\n      self.original_filename ||= \"data\"\n      @tempfile = copy_to_tempfile(@target)\n      @content_type = ContentTypeDetector.new(@tempfile.path).detect\n      @size = @target.size\n    end\n\n    def copy_to_tempfile(source)\n      while data = source.read(16*1024)\n        destination.write(data)\n      end\n      destination.rewind\n      destination\n    end\n  end\nend\n\nPaperclip::StringioAdapter.register\n"
  },
  {
    "path": "lib/paperclip/io_adapters/uploaded_file_adapter.rb",
    "content": "module Paperclip\n  class UploadedFileAdapter < AbstractAdapter\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        target.class.name.include?(\"UploadedFile\")\n      end\n    end\n\n    def initialize(target, options = {})\n      super\n      cache_current_values\n\n      if @target.respond_to?(:tempfile)\n        @tempfile = copy_to_tempfile(@target.tempfile)\n      else\n        @tempfile = copy_to_tempfile(@target)\n      end\n    end\n\n    class << self\n      attr_accessor :content_type_detector\n    end\n\n    private\n\n    def cache_current_values\n      self.original_filename = @target.original_filename\n      @content_type = determine_content_type\n      @size = File.size(@target.path)\n    end\n\n    def content_type_detector\n      self.class.content_type_detector || Paperclip::ContentTypeDetector\n    end\n\n    def determine_content_type\n      content_type = @target.content_type.to_s.strip\n      if content_type_detector\n        content_type = content_type_detector.new(@target.path).detect\n      end\n      content_type\n    end\n  end\nend\n\nPaperclip::UploadedFileAdapter.register\n"
  },
  {
    "path": "lib/paperclip/io_adapters/uri_adapter.rb",
    "content": "require \"open-uri\"\n\nmodule Paperclip\n  class UriAdapter < AbstractAdapter\n    attr_writer :content_type\n\n    def self.register\n      Paperclip.io_adapters.register self do |target|\n        target.is_a?(URI)\n      end\n    end\n\n    def initialize(target, options = {})\n      super\n      @content = download_content\n      cache_current_values\n      @tempfile = copy_to_tempfile(@content)\n    end\n\n    private\n\n    def cache_current_values\n      self.content_type = content_type_from_content || \"text/html\"\n\n      self.original_filename = filename_from_content_disposition ||\n                               filename_from_path || default_filename\n      @size = @content.size\n    end\n\n    def content_type_from_content\n      @content.meta[\"content-type\"].presence\n    end\n\n    def filename_from_content_disposition\n      if @content.meta.key?(\"content-disposition\") && @content.meta[\"content-disposition\"].match(/filename/i)\n        # can include both filename and filename* values according to RCF6266. filename should come first\n        _, filename = @content.meta[\"content-disposition\"].split(/filename\\*?\\s*=\\s*/i)\n\n        # filename can be enclosed in quotes or not\n        matches = filename.match(/\"(.*)\"/)\n        matches ? matches[1] : filename.split(';')[0]\n      end\n    end\n\n    def filename_from_path\n      @target.path.split(\"/\").last\n    end\n\n    def default_filename\n      \"index.html\"\n    end\n\n    def download_content\n      options = { read_timeout: Paperclip.options[:read_timeout] }.compact\n\n      open(@target, **options)\n    end\n\n    def copy_to_tempfile(src)\n      while data = src.read(16 * 1024)\n        destination.write(data)\n      end\n      src.close\n      destination.rewind\n      destination\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/locales/en.yml",
    "content": "en:\n  errors:\n    messages:\n      in_between: \"must be in between %{min} and %{max}\"\n      spoofed_media_type: \"has contents that are not what they are reported to be\"\n\n  number:\n    human:\n      storage_units:\n        format: \"%n %u\"\n        units:\n          byte:\n            one:   \"Byte\"\n            other: \"Bytes\"\n          kb: \"KB\"\n          mb: \"MB\"\n          gb: \"GB\"\n          tb: \"TB\"\n"
  },
  {
    "path": "lib/paperclip/logger.rb",
    "content": "module Paperclip\n  module Logger\n    # Log a paperclip-specific line. This will log to STDOUT\n    # by default. Set Paperclip.options[:log] to false to turn off.\n    def log(message)\n      logger.info(\"[paperclip] #{message}\") if logging?\n    end\n\n    def logger #:nodoc:\n      @logger ||= options[:logger] || ::Logger.new(STDOUT)\n    end\n\n    def logger=(logger)\n      @logger = logger\n    end\n\n    def logging? #:nodoc:\n      options[:log]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/matchers/have_attached_file_matcher.rb",
    "content": "module Paperclip\n  module Shoulda\n    module Matchers\n      # Ensures that the given instance or class has an attachment with the\n      # given name.\n      #\n      # Example:\n      #   describe User do\n      #     it { should have_attached_file(:avatar) }\n      #   end\n      def have_attached_file name\n        HaveAttachedFileMatcher.new(name)\n      end\n\n      class HaveAttachedFileMatcher\n        def initialize attachment_name\n          @attachment_name = attachment_name\n        end\n\n        def matches? subject\n          @subject = subject\n          @subject = @subject.class unless Class === @subject\n          responds? && has_column?\n        end\n\n        def failure_message\n          \"Should have an attachment named #{@attachment_name}\"\n        end\n\n        def failure_message_when_negated\n          \"Should not have an attachment named #{@attachment_name}\"\n        end\n        alias negative_failure_message failure_message_when_negated\n\n        def description\n          \"have an attachment named #{@attachment_name}\"\n        end\n\n        protected\n\n        def responds?\n          methods = @subject.instance_methods.map(&:to_s)\n          methods.include?(\"#{@attachment_name}\") &&\n            methods.include?(\"#{@attachment_name}=\") &&\n            methods.include?(\"#{@attachment_name}?\")\n        end\n\n        def has_column?\n          @subject.column_names.include?(\"#{@attachment_name}_file_name\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/matchers/validate_attachment_content_type_matcher.rb",
    "content": "module Paperclip\n  module Shoulda\n    module Matchers\n      # Ensures that the given instance or class validates the content type of\n      # the given attachment as specified.\n      #\n      # Example:\n      #   describe User do\n      #     it { should validate_attachment_content_type(:icon).\n      #                   allowing('image/png', 'image/gif').\n      #                   rejecting('text/plain', 'text/xml') }\n      #   end\n      def validate_attachment_content_type name\n        ValidateAttachmentContentTypeMatcher.new(name)\n      end\n\n      class ValidateAttachmentContentTypeMatcher\n        def initialize attachment_name\n          @attachment_name = attachment_name\n          @allowed_types = []\n          @rejected_types = []\n        end\n\n        def allowing *types\n          @allowed_types = types.flatten\n          self\n        end\n\n        def rejecting *types\n          @rejected_types = types.flatten\n          self\n        end\n\n        def matches? subject\n          @subject = subject\n          @subject = @subject.new if @subject.class == Class\n          @allowed_types && @rejected_types &&\n          allowed_types_allowed? && rejected_types_rejected?\n        end\n\n        def failure_message\n          \"#{expected_attachment}\\n\".tap do |message|\n            message << accepted_types_and_failures.to_s\n            message << \"\\n\\n\" if @allowed_types.present? && @rejected_types.present?\n            message << rejected_types_and_failures.to_s\n          end\n        end\n\n        def description\n          \"validate the content types allowed on attachment #{@attachment_name}\"\n        end\n\n        protected\n\n        def accepted_types_and_failures\n          if @allowed_types.present?\n            \"Accept content types: #{@allowed_types.join(\", \")}\\n\".tap do |message|\n              if @missing_allowed_types.present?\n                message << \"  #{@missing_allowed_types.join(\", \")} were rejected.\"\n              else\n                message << \"  All were accepted successfully.\"\n              end\n            end\n          end\n        end\n        def rejected_types_and_failures\n          if @rejected_types.present?\n            \"Reject content types: #{@rejected_types.join(\", \")}\\n\".tap do |message|\n              if @missing_rejected_types.present?\n                message << \"  #{@missing_rejected_types.join(\", \")} were accepted.\"\n              else\n                message << \"  All were rejected successfully.\"\n              end\n            end\n          end\n        end\n\n        def expected_attachment\n          \"Expected #{@attachment_name}:\\n\"\n        end\n\n        def type_allowed?(type)\n          @subject.send(\"#{@attachment_name}_content_type=\", type)\n          @subject.valid?\n          @subject.errors[:\"#{@attachment_name}_content_type\"].blank?\n        end\n\n        def allowed_types_allowed?\n          @missing_allowed_types ||= @allowed_types.reject { |type| type_allowed?(type) }\n          @missing_allowed_types.none?\n        end\n\n        def rejected_types_rejected?\n          @missing_rejected_types ||= @rejected_types.select { |type| type_allowed?(type) }\n          @missing_rejected_types.none?\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/matchers/validate_attachment_presence_matcher.rb",
    "content": "module Paperclip\n  module Shoulda\n    module Matchers\n      # Ensures that the given instance or class validates the presence of the\n      # given attachment.\n      #\n      # describe User do\n      #   it { should validate_attachment_presence(:avatar) }\n      # end\n      def validate_attachment_presence name\n        ValidateAttachmentPresenceMatcher.new(name)\n      end\n\n      class ValidateAttachmentPresenceMatcher\n        def initialize attachment_name\n          @attachment_name = attachment_name\n        end\n\n        def matches? subject\n          @subject = subject\n          @subject = subject.new if subject.class == Class\n          error_when_not_valid? && no_error_when_valid?\n        end\n\n        def failure_message\n          \"Attachment #{@attachment_name} should be required\"\n        end\n\n        def failure_message_when_negated\n          \"Attachment #{@attachment_name} should not be required\"\n        end\n        alias negative_failure_message failure_message_when_negated\n\n        def description\n          \"require presence of attachment #{@attachment_name}\"\n        end\n\n        protected\n\n        def error_when_not_valid?\n          @subject.send(@attachment_name).assign(nil)\n          @subject.valid?\n          @subject.errors[:\"#{@attachment_name}\"].present?\n        end\n\n        def no_error_when_valid?\n          @file = StringIO.new(\".\")\n          @subject.send(@attachment_name).assign(@file)\n          @subject.valid?\n          expected_message = [\n            @attachment_name.to_s.titleize,\n            I18n.t(:blank, scope: [:errors, :messages])\n          ].join(' ')\n          @subject.errors.full_messages.exclude?(expected_message)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/matchers/validate_attachment_size_matcher.rb",
    "content": "module Paperclip\n  module Shoulda\n    module Matchers\n      # Ensures that the given instance or class validates the size of the\n      # given attachment as specified.\n      #\n      # Examples:\n      #   it { should validate_attachment_size(:avatar).\n      #                 less_than(2.megabytes) }\n      #   it { should validate_attachment_size(:icon).\n      #                 greater_than(1024) }\n      #   it { should validate_attachment_size(:icon).\n      #                 in(0..100) }\n      def validate_attachment_size name\n        ValidateAttachmentSizeMatcher.new(name)\n      end\n\n      class ValidateAttachmentSizeMatcher\n        def initialize attachment_name\n          @attachment_name = attachment_name\n        end\n\n        def less_than size\n          @high = size\n          self\n        end\n\n        def greater_than size\n          @low = size\n          self\n        end\n\n        def in range\n          @low, @high = range.first, range.last\n          self\n        end\n\n        def matches? subject\n          @subject = subject\n          @subject = @subject.new if @subject.class == Class\n          lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?\n        end\n\n        def failure_message\n          \"Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes\"\n        end\n\n        def failure_message_when_negated\n          \"Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes\"\n        end\n        alias negative_failure_message failure_message_when_negated\n\n        def description\n          \"validate the size of attachment #{@attachment_name}\"\n        end\n\n        protected\n\n        def override_method object, method, &replacement\n          (class << object; self; end).class_eval do\n            define_method(method, &replacement)\n          end\n        end\n\n        def passes_validation_with_size(new_size)\n          file = StringIO.new(\".\")\n          override_method(file, :size){ new_size }\n          override_method(file, :to_tempfile){ file }\n\n          @subject.send(@attachment_name).post_processing = false\n          @subject.send(@attachment_name).assign(file)\n          @subject.valid?\n          @subject.errors[:\"#{@attachment_name}_file_size\"].blank?\n        ensure\n          @subject.send(@attachment_name).post_processing = true\n        end\n\n        def lower_than_low?\n          @low.nil? || !passes_validation_with_size(@low - 1)\n        end\n\n        def higher_than_low?\n          @low.nil? || passes_validation_with_size(@low + 1)\n        end\n\n        def lower_than_high?\n          @high.nil? || @high == Float::INFINITY || passes_validation_with_size(@high - 1)\n        end\n\n        def higher_than_high?\n          @high.nil? || @high == Float::INFINITY || !passes_validation_with_size(@high + 1)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/matchers.rb",
    "content": "require 'paperclip/matchers/have_attached_file_matcher'\nrequire 'paperclip/matchers/validate_attachment_presence_matcher'\nrequire 'paperclip/matchers/validate_attachment_content_type_matcher'\nrequire 'paperclip/matchers/validate_attachment_size_matcher'\n\nmodule Paperclip\n  module Shoulda\n    # Provides RSpec-compatible & Test::Unit-compatible matchers for testing Paperclip attachments.\n    #\n    # *RSpec*\n    #\n    # In spec_helper.rb, you'll need to require the matchers:\n    #\n    #   require \"paperclip/matchers\"\n    #\n    # And _include_ the module:\n    #\n    #   RSpec.configure do |config|\n    #     config.include Paperclip::Shoulda::Matchers\n    #   end\n    #\n    # Example:\n    #   describe User do\n    #     it { should have_attached_file(:avatar) }\n    #     it { should validate_attachment_presence(:avatar) }\n    #     it { should validate_attachment_content_type(:avatar).\n    #                   allowing('image/png', 'image/gif').\n    #                   rejecting('text/plain', 'text/xml') }\n    #     it { should validate_attachment_size(:avatar).\n    #                   less_than(2.megabytes) }\n    #   end\n    #\n    #\n    # *TestUnit*\n    #\n    # In test_helper.rb, you'll need to require the matchers as well:\n    #\n    #   require \"paperclip/matchers\"\n    #\n    # And _extend_ the module:\n    #\n    #   class ActiveSupport::TestCase\n    #     extend Paperclip::Shoulda::Matchers\n    #\n    #     #...other initializers...#\n    #   end\n    #\n    # Example:\n    #   require 'test_helper'\n    #\n    #   class UserTest < ActiveSupport::TestCase\n    #     should have_attached_file(:avatar)\n    #     should validate_attachment_presence(:avatar)\n    #     should validate_attachment_content_type(:avatar).\n    #                  allowing('image/png', 'image/gif').\n    #                  rejecting('text/plain', 'text/xml')\n    #     should validate_attachment_size(:avatar).\n    #                  less_than(2.megabytes)\n    #   end\n    #\n    module Matchers\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/media_type_spoof_detector.rb",
    "content": "module Paperclip\n  class MediaTypeSpoofDetector\n    def self.using(file, name, content_type)\n      new(file, name, content_type)\n    end\n\n    def initialize(file, name, content_type)\n      @file = file\n      @name = name\n      @content_type = content_type || \"\"\n    end\n\n    def spoofed?\n      if has_name? && media_type_mismatch? && mapping_override_mismatch?\n        Paperclip.log(\"Content Type Spoof: Filename #{File.basename(@name)} (#{supplied_content_type} from Headers, #{content_types_from_name.map(&:to_s)} from Extension), content type discovered from file command: #{calculated_content_type}. See documentation to allow this combination.\")\n        true\n      else\n        false\n      end\n    end\n\n    private\n\n    def has_name?\n      @name.present?\n    end\n\n    def has_extension?\n      File.extname(@name).present?\n    end\n\n    def media_type_mismatch?\n      extension_type_mismatch? || calculated_type_mismatch?\n    end\n\n    def extension_type_mismatch?\n      supplied_media_type.present? &&\n        has_extension? &&\n        !media_types_from_name.include?(supplied_media_type)\n    end\n\n    def calculated_type_mismatch?\n      supplied_media_type.present? &&\n        !calculated_content_type.include?(supplied_media_type)\n    end\n\n    def mapping_override_mismatch?\n      !Array(mapped_content_type).include?(calculated_content_type)\n    end\n\n\n    def supplied_content_type\n      @content_type\n    end\n\n    def supplied_media_type\n      @content_type.split(\"/\").first\n    end\n\n    def content_types_from_name\n      @content_types_from_name ||= MIME::Types.type_for(@name)\n    end\n\n    def media_types_from_name\n      @media_types_from_name ||= content_types_from_name.collect(&:media_type)\n    end\n\n    def calculated_content_type\n      @calculated_content_type ||= type_from_file_command.chomp\n    end\n\n    def calculated_media_type\n      @calculated_media_type ||= calculated_content_type.split(\"/\").first\n    end\n\n    def type_from_file_command\n      begin\n        Paperclip.run(\"file\", \"-b --mime :file\", file: @file.path).\n          split(/[:;\\s]+/).first\n      rescue Terrapin::CommandLineError\n        \"\"\n      end\n    end\n\n    def mapped_content_type\n      Paperclip.options[:content_type_mappings][filename_extension]\n    end\n\n    def filename_extension\n      File.extname(@name.to_s.downcase).sub(/^\\./, '').to_sym\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/missing_attachment_styles.rb",
    "content": "require 'paperclip/attachment_registry'\nrequire 'set'\n\nmodule Paperclip\n  class << self\n    attr_writer :registered_attachments_styles_path\n    def registered_attachments_styles_path\n      @registered_attachments_styles_path ||= Rails.root.join('public/system/paperclip_attachments.yml').to_s\n    end\n  end\n\n  # Get list of styles saved on previous deploy (running rake paperclip:refresh:missing_styles)\n  def self.get_registered_attachments_styles\n    YAML.load_file(Paperclip.registered_attachments_styles_path)\n  rescue Errno::ENOENT\n    nil\n  end\n  private_class_method :get_registered_attachments_styles\n\n  def self.save_current_attachments_styles!\n    File.open(Paperclip.registered_attachments_styles_path, 'w') do |f|\n      YAML.dump(current_attachments_styles, f)\n    end\n  end\n\n  # Returns hash with styles for all classes using Paperclip.\n  # Unfortunately current version does not work with lambda styles:(\n  #   {\n  #     :User => {:avatar => [:small, :big]},\n  #     :Book => {\n  #       :cover => [:thumb, :croppable]},\n  #       :sample => [:thumb, :big]},\n  #     }\n  #   }\n  def self.current_attachments_styles\n    Hash.new.tap do |current_styles|\n      Paperclip::AttachmentRegistry.each_definition do |klass, attachment_name, attachment_attributes|\n        # TODO: is it even possible to take into account Procs?\n        next if attachment_attributes[:styles].kind_of?(Proc)\n        attachment_attributes[:styles].try(:keys).try(:each) do |style_name|\n          klass_sym = klass.to_s.to_sym\n          current_styles[klass_sym] ||= Hash.new\n          current_styles[klass_sym][attachment_name.to_sym] ||= Array.new\n          current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym\n          current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!\n        end\n      end\n    end\n  end\n  private_class_method :current_attachments_styles\n\n  # Returns hash with styles missing from recent run of rake paperclip:refresh:missing_styles\n  #   {\n  #     :User => {:avatar => [:big]},\n  #     :Book => {\n  #       :cover => [:croppable]},\n  #     }\n  #   }\n  def self.missing_attachments_styles\n    current_styles = current_attachments_styles\n    registered_styles = get_registered_attachments_styles\n\n    Hash.new.tap do |missing_styles|\n      current_styles.each do |klass, attachment_definitions|\n        attachment_definitions.each do |attachment_name, styles|\n          registered = registered_styles[klass][attachment_name] || [] rescue []\n          missed = styles - registered\n          if missed.present?\n            klass_sym = klass.to_s.to_sym\n            missing_styles[klass_sym] ||= Hash.new\n            missing_styles[klass_sym][attachment_name.to_sym] ||= Array.new\n            missing_styles[klass_sym][attachment_name.to_sym].concat(missed.to_a)\n            missing_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/processor.rb",
    "content": "module Paperclip\n  # Paperclip processors allow you to modify attached files when they are\n  # attached in any way you are able. Paperclip itself uses command-line\n  # programs for its included Thumbnail processor, but custom processors\n  # are not required to follow suit.\n  #\n  # Processors are required to be defined inside the Paperclip module and\n  # are also required to be a subclass of Paperclip::Processor. There is\n  # only one method you *must* implement to properly be a subclass:\n  # #make, but #initialize may also be of use. #initialize accepts 3\n  # arguments: the file that will be operated on (which is an instance of\n  # File), a hash of options that were defined in has_attached_file's\n  # style hash, and the Paperclip::Attachment itself. These are set as\n  # instance variables that can be used within `#make`.\n  #\n  # #make must return an instance of File (Tempfile is acceptable) which\n  # contains the results of the processing.\n  #\n  # See Paperclip.run for more information about using command-line\n  # utilities from within Processors.\n  class Processor\n    attr_accessor :file, :options, :attachment\n\n    def initialize file, options = {}, attachment = nil\n      @file = file\n      @options = options\n      @attachment = attachment\n    end\n\n    def make\n    end\n\n    def self.make file, options = {}, attachment = nil\n      new(file, options, attachment).make\n    end\n\n    # The convert method runs the convert binary with the provided arguments.\n    # See Paperclip.run for the available options.\n    def convert(arguments = \"\", local_options = {})\n      Paperclip.run(\n        Paperclip.options[:is_windows] ? \"magick convert\" : \"convert\",\n        arguments,\n        local_options,\n      )\n    end\n\n    # The identify method runs the identify binary with the provided arguments.\n    # See Paperclip.run for the available options.\n    def identify(arguments = \"\", local_options = {})\n      Paperclip.run(\n        Paperclip.options[:is_windows] ? \"magick identify\" : \"identify\",\n        arguments,\n        local_options,\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/processor_helpers.rb",
    "content": "module Paperclip\n  module ProcessorHelpers\n    class NoSuchProcessor < StandardError; end\n\n    def processor(name) #:nodoc:\n      @known_processors ||= {}\n      if @known_processors[name.to_s]\n        @known_processors[name.to_s]\n      else\n        name = name.to_s.camelize\n        load_processor(name) unless Paperclip.const_defined?(name)\n        processor = Paperclip.const_get(name)\n        @known_processors[name.to_s] = processor\n      end\n    end\n\n    def load_processor(name)\n      if defined?(Rails.root) && Rails.root\n        filename = \"#{name.to_s.underscore}.rb\"\n        directories = %w(lib/paperclip lib/paperclip_processors)\n\n        required = directories.map do |directory|\n          pathname = File.expand_path(Rails.root.join(directory, filename))\n          file_exists = File.exist?(pathname)\n          require pathname if file_exists\n          file_exists\n        end\n\n        raise LoadError, \"Could not find the '#{name}' processor in any of these paths: #{directories.join(', ')}\" unless required.any?\n      end\n    end\n\n    def clear_processors!\n      @known_processors.try(:clear)\n    end\n\n    # You can add your own processor via the Paperclip configuration. Normally\n    # Paperclip will load all processors from the\n    # Rails.root/lib/paperclip_processors directory, but here you can add any\n    # existing class using this mechanism.\n    #\n    #   Paperclip.configure do |c|\n    #     c.register_processor :watermarker, WatermarkingProcessor.new\n    #   end\n    def register_processor(name, processor)\n      @known_processors ||= {}\n      @known_processors[name.to_s] = processor\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/rails_environment.rb",
    "content": "module Paperclip\n  class RailsEnvironment\n    def self.get\n      new.get\n    end\n\n    def get\n      if rails_exists? && rails_environment_exists?\n        Rails.env\n      else\n        nil\n      end\n    end\n\n    private\n\n    def rails_exists?\n      Object.const_defined?(:Rails)\n    end\n\n    def rails_environment_exists?\n      Rails.respond_to?(:env)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/railtie.rb",
    "content": "require 'paperclip'\nrequire 'paperclip/schema'\n\nmodule Paperclip\n  require 'rails'\n\n  class Railtie < Rails::Railtie\n    initializer 'paperclip.insert_into_active_record' do |app|\n      ActiveSupport.on_load :active_record do\n        Paperclip::Railtie.insert\n      end\n\n      if app.config.respond_to?(:paperclip_defaults)\n        Paperclip::Attachment.default_options.merge!(app.config.paperclip_defaults)\n      end\n    end\n\n    rake_tasks { load \"tasks/paperclip.rake\" }\n  end\n\n  class Railtie\n    def self.insert\n      Paperclip.options[:logger] = Rails.logger\n\n      if defined?(ActiveRecord)\n        Paperclip.options[:logger] = ActiveRecord::Base.logger\n        ActiveRecord::Base.send(:include, Paperclip::Glue)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/schema.rb",
    "content": "require 'active_support/deprecation'\n\nmodule Paperclip\n  # Provides helper methods that can be used in migrations.\n  module Schema\n    COLUMNS = {:file_name    => :string,\n               :content_type => :string,\n               :file_size    => :bigint,\n               :updated_at   => :datetime}\n\n    def self.included(base)\n      ActiveRecord::ConnectionAdapters::Table.send :include, TableDefinition\n      ActiveRecord::ConnectionAdapters::TableDefinition.send :include, TableDefinition\n      ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Statements\n      ActiveRecord::Migration::CommandRecorder.send :include, CommandRecorder\n    end\n\n    module Statements\n      def add_attachment(table_name, *attachment_names)\n        raise ArgumentError, \"Please specify attachment name in your add_attachment call in your migration.\" if attachment_names.empty?\n\n        options = attachment_names.extract_options!\n\n        attachment_names.each do |attachment_name|\n          COLUMNS.each_pair do |column_name, column_type|\n            column_options = options.merge(options[column_name.to_sym] || {})\n            add_column(table_name, \"#{attachment_name}_#{column_name}\", column_type, column_options)\n          end\n        end\n      end\n\n      def remove_attachment(table_name, *attachment_names)\n        raise ArgumentError, \"Please specify attachment name in your remove_attachment call in your migration.\" if attachment_names.empty?\n\n        attachment_names.each do |attachment_name|\n          COLUMNS.keys.each do |column_name|\n            remove_column(table_name, \"#{attachment_name}_#{column_name}\")\n          end\n        end\n      end\n\n      def drop_attached_file(*args)\n        ActiveSupport::Deprecation.warn \"Method `drop_attached_file` in the migration has been deprecated and will be replaced by `remove_attachment`.\"\n        remove_attachment(*args)\n      end\n    end\n\n    module TableDefinition\n      def attachment(*attachment_names)\n        options = attachment_names.extract_options!\n        attachment_names.each do |attachment_name|\n          COLUMNS.each_pair do |column_name, column_type|\n            column_options = options.merge(options[column_name.to_sym] || {})\n            column(\"#{attachment_name}_#{column_name}\", column_type, column_options)\n          end\n        end\n      end\n\n      def has_attached_file(*attachment_names)\n        ActiveSupport::Deprecation.warn \"Method `t.has_attached_file` in the migration has been deprecated and will be replaced by `t.attachment`.\"\n        attachment(*attachment_names)\n      end\n    end\n\n    module CommandRecorder\n      def add_attachment(*args)\n        record(:add_attachment, args)\n      end\n\n      private\n\n      def invert_add_attachment(args)\n        [:remove_attachment, args]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/storage/filesystem.rb",
    "content": "module Paperclip\n  module Storage\n    # The default place to store attachments is in the filesystem. Files on the local\n    # filesystem can be very easily served by Apache without requiring a hit to your app.\n    # They also can be processed more easily after they've been saved, as they're just\n    # normal files. There are two Filesystem-specific options for has_attached_file:\n    # * +path+: The location of the repository of attachments on disk. This can (and, in\n    #   almost all cases, should) be coordinated with the value of the +url+ option to\n    #   allow files to be saved into a place where Apache can serve them without\n    #   hitting your app. Defaults to\n    #   \":rails_root/public/:attachment/:id/:style/:basename.:extension\"\n    #   By default this places the files in the app's public directory which can be served\n    #   directly. If you are using capistrano for deployment, a good idea would be to\n    #   make a symlink to the capistrano-created system directory from inside your app's\n    #   public directory.\n    #   See Paperclip::Attachment#interpolate for more information on variable interpolaton.\n    #     :path => \"/var/app/attachments/:class/:id/:style/:basename.:extension\"\n    # * +override_file_permissions+: This allows you to override the file permissions for files\n    #   saved by paperclip. If you set this to an explicit octal value (0755, 0644, etc) then\n    #   that value will be used to set the permissions for an uploaded file. The default is 0666.\n    #   If you set :override_file_permissions to false, the chmod will be skipped. This allows\n    #   you to use paperclip on filesystems that don't understand unix file permissions, and has the \n    #   added benefit of using the storage directories default umask on those that do.\n    module Filesystem\n      def self.extended base\n      end\n\n      def exists?(style_name = default_style)\n        if original_filename\n          File.exist?(path(style_name))\n        else\n          false\n        end\n      end\n\n      def flush_writes #:nodoc:\n        @queued_for_write.each do |style_name, file|\n          FileUtils.mkdir_p(File.dirname(path(style_name)))\n          begin\n            move_file(file.path, path(style_name))\n          rescue SystemCallError\n            File.open(path(style_name), \"wb\") do |new_file|\n              while chunk = file.read(16 * 1024)\n                new_file.write(chunk)\n              end\n            end\n          end\n          unless @options[:override_file_permissions] == false\n            resolved_chmod = (@options[:override_file_permissions] & ~0111) || (0666 & ~File.umask)\n            FileUtils.chmod( resolved_chmod, path(style_name) )\n          end\n          file.rewind\n        end\n\n        after_flush_writes # allows attachment to clean up temp files\n\n        @queued_for_write = {}\n      end\n\n      def flush_deletes #:nodoc:\n        @queued_for_delete.each do |path|\n          begin\n            log(\"deleting #{path}\")\n            FileUtils.rm(path) if File.exist?(path)\n          rescue Errno::ENOENT => e\n            # ignore file-not-found, let everything else pass\n          end\n          begin\n            while(true)\n              path = File.dirname(path)\n              FileUtils.rmdir(path)\n              break if File.exist?(path) # Ruby 1.9.2 does not raise if the removal failed.\n            end\n          rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR, Errno::EACCES\n            # Stop trying to remove parent directories\n          rescue SystemCallError => e\n            log(\"There was an unexpected error while deleting directories: #{e.class}\")\n            # Ignore it\n          end\n        end\n        @queued_for_delete = []\n      end\n\n      def copy_to_local_file(style, local_dest_path)\n        FileUtils.cp(path(style), local_dest_path)\n      end\n\n      private\n\n      def move_file(src, dest)\n        # Support hardlinked files\n        if File.identical?(src, dest)\n          File.unlink(src)\n        else\n          FileUtils.mv(src, dest)\n        end\n      end\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/storage/fog.rb",
    "content": "module Paperclip\n  module Storage\n    # fog is a modern and versatile cloud computing library for Ruby.\n    # Among others, it supports Amazon S3 to store your files. In\n    # contrast to the outdated AWS-S3 gem it is actively maintained and\n    # supports multiple locations.\n    # Amazon's S3 file hosting service is a scalable, easy place to\n    # store files for distribution. You can find out more about it at\n    # http://aws.amazon.com/s3 There are a few fog-specific options for\n    # has_attached_file, which will be explained using S3 as an example:\n    # * +fog_credentials+: Takes a Hash with your credentials. For S3,\n    #   you can use the following format:\n    #     aws_access_key_id: '<your aws_access_key_id>'\n    #     aws_secret_access_key: '<your aws_secret_access_key>'\n    #     provider: 'AWS'\n    #     region: 'eu-west-1'\n    #     scheme: 'https'\n    # * +fog_directory+: This is the name of the S3 bucket that will\n    #   store your files.  Remember that the bucket must be unique across\n    #   all of Amazon S3. If the bucket does not exist, Paperclip will\n    #   attempt to create it.\n    # * +fog_file+: This can be hash or lambda returning hash. The\n    #   value is used as base properties for new uploaded file.\n    # * +path+: This is the key under the bucket in which the file will\n    #   be stored. The URL will be constructed from the bucket and the\n    #   path. This is what you will want to interpolate. Keys should be\n    #   unique, like filenames, and despite the fact that S3 (strictly\n    #   speaking) does not support directories, you can still use a / to\n    #   separate parts of your file name.\n    # * +fog_public+: (optional, defaults to true) Should the uploaded\n    #   files be public or not? (true/false)\n    # * +fog_host+: (optional) The fully-qualified domain name (FQDN)\n    #   that is the alias to the S3 domain of your bucket, e.g.\n    #   'http://images.example.com'. This can also be used in\n    #   conjunction with Cloudfront (http://aws.amazon.com/cloudfront)\n    # * +fog_options+: (optional) A hash of options that are passed\n    #   to fog when the file is created. For example, you could set\n    #   the multipart-chunk size to 100MB with a hash:\n    #     { :multipart_chunk_size => 104857600 }\n\n    module Fog\n      def self.extended base\n        begin\n          require 'fog'\n        rescue LoadError => e\n          e.message << \" (You may need to install the fog gem)\"\n          raise e\n        end unless defined?(Fog)\n\n        base.instance_eval do\n          unless @options[:url].to_s.match(/\\A:fog.*url\\z/)\n            @options[:path]  = @options[:path].gsub(/:url/, @options[:url]).gsub(/\\A:rails_root\\/public\\/system\\//, '')\n            @options[:url]   = ':fog_public_url'\n          end\n          Paperclip.interpolates(:fog_public_url) do |attachment, style|\n            attachment.public_url(style)\n          end unless Paperclip::Interpolations.respond_to? :fog_public_url\n        end\n      end\n\n      AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX = /\\A(?:[a-z]|\\d(?!\\d{0,2}(?:\\.\\d{1,3}){3}\\z))(?:[a-z0-9]|\\.(?![\\.\\-])|\\-(?![\\.])){1,61}[a-z0-9]\\z/\n\n      def exists?(style = default_style)\n        if original_filename\n          !!directory.files.head(path(style))\n        else\n          false\n        end\n      end\n\n      def fog_credentials\n        @fog_credentials ||= parse_credentials(@options[:fog_credentials])\n      end\n\n      def fog_file\n        @fog_file ||= begin\n          value = @options[:fog_file]\n          if !value\n            {}\n          elsif value.respond_to?(:call)\n            value.call(self)\n          else\n            value\n          end\n        end\n      end\n\n      def fog_public(style = default_style)\n        if @options.key?(:fog_public)\n          value = @options[:fog_public]\n          if value.respond_to?(:key?) && value.key?(style)\n            value[style]\n          elsif value.respond_to?(:call)\n            value.call(self)\n          else\n            value\n          end\n        else\n          true\n        end\n      end\n\n      def flush_writes\n        for style, file in @queued_for_write do\n          log(\"saving #{path(style)}\")\n          retried = false\n          begin\n            attributes = fog_file.merge(\n              :body         => file,\n              :key          => path(style),\n              :public       => fog_public(style),\n              :content_type => file.content_type\n            )\n            attributes.merge!(@options[:fog_options]) if @options[:fog_options]\n            directory.files.create(attributes)\n          rescue Excon::Errors::NotFound\n            raise if retried\n            retried = true\n            directory.save\n            file.rewind\n            retry\n          ensure\n            file.rewind\n          end\n        end\n\n        after_flush_writes # allows attachment to clean up temp files\n\n        @queued_for_write = {}\n      end\n\n      def flush_deletes\n        for path in @queued_for_delete do\n          log(\"deleting #{path}\")\n          directory.files.new(:key => path).destroy\n        end\n        @queued_for_delete = []\n      end\n\n      def public_url(style = default_style)\n        if @options[:fog_host]\n          \"#{dynamic_fog_host_for_style(style)}/#{path(style)}\"\n        else\n          if fog_credentials[:provider] == 'AWS'\n            \"#{scheme}://#{host_name_for_directory}/#{path(style)}\"\n          else\n            directory.files.new(:key => path(style)).public_url\n          end\n        end\n      end\n\n      def expiring_url(time = (Time.now + 3600), style_name = default_style)\n        time = convert_time(time)\n        http_url_method = \"get_#{scheme}_url\"\n        if path(style_name) && directory.files.respond_to?(http_url_method)\n          expiring_url = directory.files.public_send(http_url_method, path(style_name), time)\n\n          if @options[:fog_host]\n            expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style_name))\n          end\n        else\n          expiring_url = url(style_name)\n        end\n\n        return expiring_url\n      end\n\n      def parse_credentials(creds)\n        creds = find_credentials(creds).stringify_keys\n        (creds[RailsEnvironment.get] || creds).symbolize_keys\n      end\n\n      def copy_to_local_file(style, local_dest_path)\n        log(\"copying #{path(style)} to local file #{local_dest_path}\")\n        ::File.open(local_dest_path, 'wb') do |local_file|\n          file = directory.files.get(path(style))\n          return false unless file\n          local_file.write(file.body)\n        end\n      rescue ::Fog::Errors::Error => e\n        warn(\"#{e} - cannot copy #{path(style)} to local file #{local_dest_path}\")\n        false\n      end\n\n      private\n\n      def convert_time(time)\n        if time.is_a?(Integer)\n          time = Time.now + time\n        end\n        time\n      end\n\n      def dynamic_fog_host_for_style(style)\n        if @options[:fog_host].respond_to?(:call)\n          @options[:fog_host].call(self)\n        else\n          (@options[:fog_host] =~ /%d/) ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host]\n        end\n      end\n\n      def host_name_for_directory\n        if directory_name.to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX\n          \"#{directory_name}.s3.amazonaws.com\"\n        else\n          \"s3.amazonaws.com/#{directory_name}\"\n        end\n      end\n\n      def find_credentials(creds)\n        case creds\n        when File\n          YAML::load(ERB.new(File.read(creds.path)).result)\n        when String, Pathname\n          YAML::load(ERB.new(File.read(creds)).result)\n        when Hash\n          creds\n        else\n          if creds.respond_to?(:call)\n            creds.call(self)\n          else\n            raise ArgumentError, \"Credentials are not a path, file, hash or proc.\"\n          end\n        end\n      end\n\n      def connection\n        @connection ||= ::Fog::Storage.new(fog_credentials)\n      end\n\n      def directory\n        @directory ||= connection.directories.new(key: directory_name)\n      end\n\n      def directory_name\n        if @options[:fog_directory].respond_to?(:call)\n          @options[:fog_directory].call(self)\n        else\n          @options[:fog_directory]\n        end\n      end\n\n      def scheme\n        @scheme ||= fog_credentials[:scheme] || 'https'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/storage/s3.rb",
    "content": "module Paperclip\n  module Storage\n    # Amazon's S3 file hosting service is a scalable, easy place to store files for\n    # distribution. You can find out more about it at http://aws.amazon.com/s3\n    #\n    # To use Paperclip with S3, include the +aws-sdk-s3+ gem in your Gemfile:\n    #   gem 'aws-sdk-s3'\n    # There are a few S3-specific options for has_attached_file:\n    # * +s3_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point\n    #   to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon\n    #   gives you. You can 'environment-space' this just like you do to your\n    #   database.yml file, so different environments can use different accounts:\n    #     development:\n    #       access_key_id: 123...\n    #       secret_access_key: 123...\n    #     test:\n    #       access_key_id: abc...\n    #       secret_access_key: abc...\n    #     production:\n    #       access_key_id: 456...\n    #       secret_access_key: 456...\n    #   This is not required, however, and the file may simply look like this:\n    #     access_key_id: 456...\n    #     secret_access_key: 456...\n    #   In which case, those access keys will be used in all environments. You can also\n    #   put your bucket name in this file, instead of adding it to the code directly.\n    #   This is useful when you want the same account but a different bucket for\n    #   development versus production.\n    #   When using a Proc it provides a single parameter which is the attachment itself. A\n    #   method #instance is available on the attachment which will take you back to your\n    #   code. eg.\n    #     class User\n    #       has_attached_file :download,\n    #                         :storage => :s3,\n    #                         :s3_credentials => Proc.new{|a| a.instance.s3_credentials }\n    #\n    #       def s3_credentials\n    #         {:bucket => \"xxx\", :access_key_id => \"xxx\", :secret_access_key => \"xxx\"}\n    #       end\n    #     end\n    # * +s3_permissions+: This is a String that should be one of the \"canned\" access\n    #   policies that S3 provides (more information can be found here:\n    #   http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)\n    #   The default for Paperclip is public-read.\n    #\n    #   You can set permission on a per style bases by doing the following:\n    #     :s3_permissions => {\n    #       :original => \"private\"\n    #     }\n    #   Or globally:\n    #     :s3_permissions => \"private\"\n    #\n    # * +s3_protocol+: The protocol for the URLs generated to your S3 assets.\n    #   Can be either 'http', 'https', or an empty string to generate\n    #   protocol-relative URLs. Defaults to empty string.\n    # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as\n    #   {'Expires' => 1.year.from_now.httpdate}. If you use a Proc, headers are determined at\n    #   runtime. Paperclip will call that Proc with attachment as the only argument.\n    #   Can be defined both globally and within a style-specific hash.\n    # * +bucket+: This is the name of the S3 bucket that will store your files. Remember\n    #   that the bucket must be unique across all of Amazon S3. If the bucket does not exist\n    #   Paperclip will attempt to create it. The bucket name will not be interpolated.\n    #   You can define the bucket as a Proc if you want to determine its name at runtime.\n    #   Paperclip will call that Proc with attachment as the only argument.\n    # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the\n    #   S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the\n    #   link in the +url+ entry for more information about S3 domains and buckets.\n    # * +s3_prefixes_in_alias+: The number of prefixes that is prepended by\n    #   s3_host_alias. This will remove the prefixes from the path in\n    #   :s3_alias_url url interpolation\n    # * +url+: There are four options for the S3 url. You can choose to have the bucket's name\n    #   placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).\n    #   You can also specify a CNAME (which requires the CNAME to be specified as\n    #   :s3_alias_url. You can read more about CNAMEs and S3 at\n    #   http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html\n    #   Normally, this won't matter in the slightest and you can leave the default (which is\n    #   path-style, or :s3_path_url). But in some cases paths don't work and you need to use\n    #   the domain-style (:s3_domain_url). Anything else here will be treated like path-style.\n    #\n    #   Notes:\n    #   * The value of this option is a string, not a symbol.\n    #     <b>right:</b> <tt>\":s3_domain_url\"</tt>\n    #     <b>wrong:</b> <tt>:s3_domain_url</tt>\n    #   * If you use a CNAME for use with CloudFront, you can NOT specify https as your\n    #     :s3_protocol;\n    #     This is *not supported* by S3/CloudFront. Finally, when using the host\n    #     alias, the :bucket parameter is ignored, as the hostname is used as the bucket name\n    #     by S3. The fourth option for the S3 url is :asset_host, which uses Rails' built-in\n    #     asset_host settings.\n    #   * To get the full url from a paperclip'd object, use the\n    #     image_path helper; this is what image_tag uses to generate the url for an img tag.\n    # * +path+: This is the key under the bucket in which the file will be stored. The\n    #   URL will be constructed from the bucket and the path. This is what you will want\n    #   to interpolate. Keys should be unique, like filenames, and despite the fact that\n    #   S3 (strictly speaking) does not support directories, you can still use a / to\n    #   separate parts of your file name.\n    # * +s3_host_name+: If you are using your bucket in Tokyo region\n    #   etc, write host_name (e.g., 's3-ap-northeast-1.amazonaws.com').\n    # * +s3_region+: For aws-sdk-s3, s3_region is required.\n    # * +s3_metadata+: These key/value pairs will be stored with the\n    #   object.  This option works by prefixing each key with\n    #   \"x-amz-meta-\" before sending it as a header on the object\n    #   upload request. Can be defined both globally and within a style-specific hash.\n    # * +s3_storage_class+: If this option is set to\n    #   <tt>:REDUCED_REDUNDANCY</tt>, the object will be stored using Reduced\n    #   Redundancy Storage. RRS enables customers to reduce their\n    #   costs by storing non-critical, reproducible data at lower\n    #   levels of redundancy than Amazon S3's standard storage.\n    # * +use_accelerate_endpoint+: Use accelerate endpoint\n    #   http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html\n    #\n    #   You can set storage class on a per style bases by doing the following:\n    #     :s3_storage_class => {\n    #       :thumb => :REDUCED_REDUNDANCY\n    #     }\n    #\n    #   Or globally:\n    #     :s3_storage_class => :REDUCED_REDUNDANCY\n    #\n    #   Other storage classes, such as <tt>:STANDARD_IA</tt>, are also available—see the\n    #   documentation for the <tt>aws-sdk-s3</tt> gem for the full list.\n\n    module S3\n      def self.extended base\n        begin\n          require \"aws-sdk-s3\"\n        rescue LoadError => e\n          e.message << \" (You may need to install the aws-sdk-s3 gem)\"\n          raise e\n        end\n\n        base.instance_eval do\n          @s3_options     = @options[:s3_options]     || {}\n          @s3_permissions = set_permissions(@options[:s3_permissions])\n          @s3_protocol    = @options[:s3_protocol] || \"\".freeze\n          @s3_metadata = @options[:s3_metadata] || {}\n          @s3_headers = {}\n          merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)\n\n          @s3_storage_class = set_storage_class(@options[:s3_storage_class])\n\n          @s3_server_side_encryption = \"AES256\"\n          if @options[:s3_server_side_encryption].blank?\n            @s3_server_side_encryption = false\n          end\n          if @s3_server_side_encryption\n            @s3_server_side_encryption = @options[:s3_server_side_encryption]\n          end\n\n          unless @options[:url].to_s.match(/\\A:s3.*url\\z/) || @options[:url] == \":asset_host\".freeze\n            @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\\A:rails_root\\/public\\/system/, \"\".freeze)\n            @options[:url]  = \":s3_path_url\".freeze\n          end\n          @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)\n\n          @http_proxy = @options[:http_proxy] || nil\n\n          @use_accelerate_endpoint = @options[:use_accelerate_endpoint]\n        end\n\n        Paperclip.interpolates(:s3_alias_url) do |attachment, style|\n          protocol = attachment.s3_protocol(style, true)\n          host = attachment.s3_host_alias\n          path = attachment.path(style).\n            split(\"/\")[attachment.s3_prefixes_in_alias..-1].\n            join(\"/\").\n            sub(%r{\\A/}, \"\".freeze)\n          \"#{protocol}//#{host}/#{path}\"\n        end unless Paperclip::Interpolations.respond_to? :s3_alias_url\n        Paperclip.interpolates(:s3_path_url) do |attachment, style|\n          \"#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\\A/}, \"\".freeze)}\"\n        end unless Paperclip::Interpolations.respond_to? :s3_path_url\n        Paperclip.interpolates(:s3_domain_url) do |attachment, style|\n          \"#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\\A/}, \"\".freeze)}\"\n        end unless Paperclip::Interpolations.respond_to? :s3_domain_url\n        Paperclip.interpolates(:asset_host) do |attachment, style|\n          \"#{attachment.path(style).sub(%r{\\A/}, \"\".freeze)}\"\n        end unless Paperclip::Interpolations.respond_to? :asset_host\n      end\n\n      def expiring_url(time = 3600, style_name = default_style)\n        if path(style_name)\n          base_options = { expires_in: time }\n          s3_object(style_name).presigned_url(\n            :get,\n            base_options.merge(s3_url_options),\n          ).to_s\n        else\n          url(style_name)\n        end\n      end\n\n      def s3_credentials\n        @s3_credentials ||= parse_credentials(@options[:s3_credentials])\n      end\n\n      def s3_host_name\n        host_name = @options[:s3_host_name]\n        host_name = host_name.call(self) if host_name.is_a?(Proc)\n\n        host_name || s3_credentials[:s3_host_name] || \"s3.amazonaws.com\".freeze\n      end\n\n      def s3_region\n        region = @options[:s3_region]\n        region = region.call(self) if region.is_a?(Proc)\n\n        region || s3_credentials[:s3_region]\n      end\n\n      def s3_host_alias\n        @s3_host_alias = @options[:s3_host_alias]\n        @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.respond_to?(:call)\n        @s3_host_alias\n      end\n\n      def s3_prefixes_in_alias\n        @s3_prefixes_in_alias ||= @options[:s3_prefixes_in_alias].to_i\n      end\n\n      def s3_url_options\n        s3_url_options = @options[:s3_url_options] || {}\n        s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call)\n        s3_url_options\n      end\n\n      def bucket_name\n        @bucket = @options[:bucket] || s3_credentials[:bucket]\n        @bucket = @bucket.call(self) if @bucket.respond_to?(:call)\n        @bucket or raise ArgumentError, \"missing required :bucket option\"\n      end\n\n      def s3_interface\n        @s3_interface ||= begin\n          config = { region: s3_region }\n\n          if using_http_proxy?\n\n            proxy_opts = { :host => http_proxy_host }\n            proxy_opts[:port] = http_proxy_port if http_proxy_port\n            if http_proxy_user\n              userinfo = http_proxy_user.to_s\n              userinfo += \":#{http_proxy_password}\" if http_proxy_password\n              proxy_opts[:userinfo] = userinfo\n            end\n            config[:proxy_uri] = URI::HTTP.build(proxy_opts)\n          end\n\n          config[:use_accelerate_endpoint] = use_accelerate_endpoint?\n\n          [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt|\n            config[opt] = s3_credentials[opt] if s3_credentials[opt]\n          end\n\n          obtain_s3_instance_for(config.merge(@s3_options))\n        end\n      end\n\n      def obtain_s3_instance_for(options)\n        instances = (Thread.current[:paperclip_s3_instances] ||= {})\n        instances[options] ||= ::Aws::S3::Resource.new(options)\n      end\n\n      def s3_bucket\n        @s3_bucket ||= s3_interface.bucket(bucket_name)\n      end\n\n      def style_name_as_path(style_name)\n        path(style_name).sub(%r{\\A/},'')\n      end\n\n      def s3_object style_name = default_style\n        s3_bucket.object style_name_as_path(style_name)\n      end\n\n      def use_accelerate_endpoint?\n        !!@use_accelerate_endpoint\n      end\n\n      def using_http_proxy?\n        !!@http_proxy\n      end\n\n      def http_proxy_host\n        using_http_proxy? ? @http_proxy[:host] : nil\n      end\n\n      def http_proxy_port\n        using_http_proxy? ? @http_proxy[:port] : nil\n      end\n\n      def http_proxy_user\n        using_http_proxy? ? @http_proxy[:user] : nil\n      end\n\n      def http_proxy_password\n        using_http_proxy? ? @http_proxy[:password] : nil\n      end\n\n      def set_permissions permissions\n        permissions = { :default => permissions } unless permissions.respond_to?(:merge)\n        permissions.merge :default => (permissions[:default] || :\"public-read\")\n      end\n\n      def set_storage_class(storage_class)\n        storage_class = {:default => storage_class} unless storage_class.respond_to?(:merge)\n        storage_class\n      end\n\n      def parse_credentials creds\n        creds = creds.respond_to?(:call) ? creds.call(self) : creds\n        creds = find_credentials(creds).stringify_keys\n        (creds[RailsEnvironment.get] || creds).symbolize_keys\n      end\n\n      def exists?(style = default_style)\n        if original_filename\n          s3_object(style).exists?\n        else\n          false\n        end\n      rescue Aws::Errors::ServiceError => e\n        false\n      end\n\n      def s3_permissions(style = default_style)\n        s3_permissions = @s3_permissions[style] || @s3_permissions[:default]\n        s3_permissions = s3_permissions.call(self, style) if s3_permissions.respond_to?(:call)\n        s3_permissions\n      end\n\n      def s3_storage_class(style = default_style)\n        @s3_storage_class[style] || @s3_storage_class[:default]\n      end\n\n      def s3_protocol(style = default_style, with_colon = false)\n        protocol = @s3_protocol\n        protocol = protocol.call(style, self) if protocol.respond_to?(:call)\n\n        if with_colon && !protocol.empty?\n          \"#{protocol}:\"\n        else\n          protocol.to_s\n        end\n      end\n\n      def create_bucket\n        s3_interface.bucket(bucket_name).create\n      end\n\n      def flush_writes #:nodoc:\n        @queued_for_write.each do |style, file|\n        retries = 0\n          begin\n            log(\"saving #{path(style)}\")\n            write_options = {\n              :content_type => file.content_type,\n              :acl => s3_permissions(style)\n            }\n\n            # add storage class for this style if defined\n            storage_class = s3_storage_class(style)\n            write_options.merge!(:storage_class => storage_class) if storage_class\n\n            if @s3_server_side_encryption\n              write_options[:server_side_encryption] = @s3_server_side_encryption\n            end\n\n            style_specific_options = styles[style]\n\n            if style_specific_options\n              merge_s3_headers( style_specific_options[:s3_headers], @s3_headers, @s3_metadata) if style_specific_options[:s3_headers]\n              @s3_metadata.merge!(style_specific_options[:s3_metadata]) if style_specific_options[:s3_metadata]\n            end\n\n            write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?\n            write_options.merge!(@s3_headers)\n\n            s3_object(style).upload_file(file.path, write_options)\n          rescue ::Aws::S3::Errors::NoSuchBucket\n            create_bucket\n            retry\n          rescue ::Aws::S3::Errors::SlowDown\n            retries += 1\n            if retries <= 5\n              sleep((2 ** retries) * 0.5)\n              retry\n            else\n              raise\n            end\n          ensure\n            file.rewind\n          end\n        end\n\n        after_flush_writes # allows attachment to clean up temp files\n\n        @queued_for_write = {}\n      end\n\n      def flush_deletes #:nodoc:\n        @queued_for_delete.each do |path|\n          begin\n            log(\"deleting #{path}\")\n            s3_bucket.object(path.sub(%r{\\A/}, \"\")).delete\n          rescue Aws::Errors::ServiceError => e\n            # Ignore this.\n          end\n        end\n        @queued_for_delete = []\n      end\n\n      def copy_to_local_file(style, local_dest_path)\n        log(\"copying #{path(style)} to local file #{local_dest_path}\")\n        ::File.open(local_dest_path, 'wb') do |local_file|\n          s3_object(style).get do |chunk|\n            local_file.write(chunk)\n          end\n        end\n      rescue Aws::Errors::ServiceError => e\n        warn(\"#{e} - cannot copy #{path(style)} to local file #{local_dest_path}\")\n        false\n      end\n\n      private\n\n      def find_credentials creds\n        case creds\n        when File\n          YAML::load(ERB.new(File.read(creds.path)).result)\n        when String, Pathname\n          YAML::load(ERB.new(File.read(creds)).result)\n        when Hash\n          creds\n        when NilClass\n          {}\n        else\n          raise ArgumentError, \"Credentials given are not a path, file, proc, or hash.\"\n        end\n      end\n\n      def use_secure_protocol?(style_name)\n        s3_protocol(style_name) == \"https\"\n      end\n\n      def merge_s3_headers(http_headers, s3_headers, s3_metadata)\n        return if http_headers.nil?\n        http_headers = http_headers.call(instance) if http_headers.respond_to?(:call)\n        http_headers.inject({}) do |headers,(name,value)|\n          case name.to_s\n          when /\\Ax-amz-meta-(.*)/i\n            s3_metadata[$1.downcase] = value\n          else\n            s3_headers[name.to_s.downcase.sub(/\\Ax-amz-/,'').tr(\"-\",\"_\").to_sym] = value\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/storage.rb",
    "content": "require \"paperclip/storage/filesystem\"\nrequire \"paperclip/storage/fog\"\nrequire \"paperclip/storage/s3\"\n"
  },
  {
    "path": "lib/paperclip/style.rb",
    "content": "module Paperclip\n  # The Style class holds the definition of a thumbnail style,  applying\n  # whatever processing is required to normalize the definition and delaying\n  # the evaluation of block parameters until useful context is available.\n\n  class Style\n\n    attr_reader :name, :attachment, :format\n\n    # Creates a Style object. +name+ is the name of the attachment,\n    # +definition+ is the style definition from has_attached_file, which\n    # can be string, array or hash\n    def initialize name, definition, attachment\n      @name = name\n      @attachment = attachment\n      if definition.is_a? Hash\n        @geometry = definition.delete(:geometry)\n        @format = definition.delete(:format)\n        @processors = definition.delete(:processors)\n        @convert_options = definition.delete(:convert_options)\n        @source_file_options = definition.delete(:source_file_options)\n        @other_args = definition\n      elsif definition.is_a? String\n        @geometry = definition\n        @format = nil\n        @other_args = {}\n      else\n        @geometry, @format = [definition, nil].flatten[0..1]\n        @other_args = {}\n      end\n      @format = default_format if @format.blank?\n    end\n\n    # retrieves from the attachment the processors defined in the has_attached_file call\n    # (which method (in the attachment) will call any supplied procs)\n    # There is an important change of interface here: a style rule can set its own processors\n    # by default we behave as before, though.\n    # if a proc has been supplied, we call it here\n    def processors\n      @processors.respond_to?(:call) ? @processors.call(attachment.instance) : (@processors || attachment.processors)\n    end\n\n    # retrieves from the attachment the whiny setting\n    def whiny\n      attachment.whiny\n    end\n\n    # returns true if we're inclined to grumble\n    def whiny?\n      !!whiny\n    end\n\n    def convert_options\n      @convert_options.respond_to?(:call) ? @convert_options.call(attachment.instance) :\n        (@convert_options || attachment.send(:extra_options_for, name))\n    end\n\n    def source_file_options\n      @source_file_options.respond_to?(:call) ? @source_file_options.call(attachment.instance) :\n        (@source_file_options || attachment.send(:extra_source_file_options_for, name))\n    end\n\n    # returns the geometry string for this style\n    # if a proc has been supplied, we call it here\n    def geometry\n      @geometry.respond_to?(:call) ? @geometry.call(attachment.instance) : @geometry\n    end\n\n    # Supplies the hash of options that processors expect to receive as their second argument\n    # Arguments other than the standard geometry, format etc are just passed through from\n    # initialization and any procs are called here, just before post-processing.\n    def processor_options\n      args = {:style => name}\n      @other_args.each do |k,v|\n        args[k] = v.respond_to?(:call) ? v.call(attachment) : v\n      end\n      [:processors, :geometry, :format, :whiny, :convert_options, :source_file_options].each do |k|\n        (arg = send(k)) && args[k] = arg\n      end\n      args\n    end\n\n    # Supports getting and setting style properties with hash notation to ensure backwards-compatibility\n    # eg. @attachment.styles[:large][:geometry]@ will still work\n    def [](key)\n      if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key)\n        send(key)\n      elsif defined? @other_args[key]\n        @other_args[key]\n      end\n    end\n\n    def []=(key, value)\n      if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key)\n        send(\"#{key}=\".intern, value)\n      else\n        @other_args[key] = value\n      end\n    end\n\n    # defaults to default format (nil by default)\n    def default_format\n      base = attachment.options[:default_format]\n      base.respond_to?(:call) ? base.call(attachment, name) : base\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/tempfile.rb",
    "content": "module Paperclip\n  # Overriding some implementation of Tempfile\n  class Tempfile < ::Tempfile\n    # Due to how ImageMagick handles its image format conversion and how\n    # Tempfile handles its naming scheme, it is necessary to override how\n    # Tempfile makes # its names so as to allow for file extensions. Idea\n    # taken from the comments on this blog post:\n    # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions\n    #\n    # This is Ruby 1.9.3's implementation.\n    def make_tmpname(prefix_suffix, n)\n      if RUBY_PLATFORM =~ /java/\n        case prefix_suffix\n        when String\n          prefix, suffix = prefix_suffix, ''\n        when Array\n          prefix, suffix = *prefix_suffix\n        else\n          raise ArgumentError, \"unexpected prefix_suffix: #{prefix_suffix.inspect}\"\n        end\n\n        t = Time.now.strftime(\"%y%m%d\")\n        path = \"#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}\"\n      else\n        super\n      end\n    end\n  end\n\n  module TempfileEncoding\n    # This overrides Tempfile#binmode to make sure that the extenal encoding\n    # for binary mode is ASCII-8BIT. This behavior is what's in CRuby, but not\n    # in JRuby\n    def binmode\n      set_encoding('ASCII-8BIT')\n      super\n    end\n  end\nend\n\nif RUBY_PLATFORM =~ /java/\n  ::Tempfile.send :include, Paperclip::TempfileEncoding\nend\n"
  },
  {
    "path": "lib/paperclip/tempfile_factory.rb",
    "content": "module Paperclip\n  class TempfileFactory\n\n    def generate(name = random_name)\n      @name = name\n      file = Tempfile.new([basename, extension])\n      file.binmode\n      file\n    end\n\n    def extension\n      File.extname(@name)\n    end\n\n    def basename\n      Digest::MD5.hexdigest(File.basename(@name, extension))\n    end\n\n    def random_name\n      SecureRandom.uuid\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/thumbnail.rb",
    "content": "module Paperclip\n  # Handles thumbnailing images that are uploaded.\n  class Thumbnail < Processor\n\n    attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options,\n                  :source_file_options, :animated, :auto_orient, :frame_index\n\n    # List of formats that we need to preserve animation\n    ANIMATED_FORMATS = %w(gif)\n    MULTI_FRAME_FORMATS = %w(.mkv .avi .mp4 .mov .mpg .mpeg .gif)\n\n    # Creates a Thumbnail object set to work on the +file+ given. It\n    # will attempt to transform the image into one defined by +target_geometry+\n    # which is a \"WxH\"-style string. +format+ will be inferred from the +file+\n    # unless specified. Thumbnail creation will raise no errors unless\n    # +whiny+ is true (which it is, by default. If +convert_options+ is\n    # set, the options will be appended to the convert command upon image conversion\n    #\n    # Options include:\n    #\n    #   +geometry+ - the desired width and height of the thumbnail (required)\n    #   +file_geometry_parser+ - an object with a method named +from_file+ that takes an image file and produces its geometry and a +transformation_to+. Defaults to Paperclip::Geometry\n    #   +string_geometry_parser+ - an object with a method named +parse+ that takes a string and produces an object with +width+, +height+, and +to_s+ accessors. Defaults to Paperclip::Geometry\n    #   +source_file_options+ - flags passed to the +convert+ command that influence how the source file is read\n    #   +convert_options+ - flags passed to the +convert+ command that influence how the image is processed\n    #   +whiny+ - whether to raise an error when processing fails. Defaults to true\n    #   +format+ - the desired filename extension\n    #   +animated+ - whether to merge all the layers in the image. Defaults to true\n    #   +frame_index+ - the frame index of the source file to render as the thumbnail\n    def initialize(file, options = {}, attachment = nil)\n      super\n\n      geometry             = options[:geometry].to_s\n      @crop                = geometry[-1,1] == '#'\n      @target_geometry     = options.fetch(:string_geometry_parser, Geometry).parse(geometry)\n      @current_geometry    = options.fetch(:file_geometry_parser, Geometry).from_file(@file)\n      @source_file_options = options[:source_file_options]\n      @convert_options     = options[:convert_options]\n      @whiny               = options.fetch(:whiny, true)\n      @format              = options[:format]\n      @animated            = options.fetch(:animated, true)\n      @auto_orient         = options.fetch(:auto_orient, true)\n      if @auto_orient && @current_geometry.respond_to?(:auto_orient)\n        @current_geometry.auto_orient\n      end\n      @source_file_options = @source_file_options.split(/\\s+/) if @source_file_options.respond_to?(:split)\n      @convert_options     = @convert_options.split(/\\s+/)     if @convert_options.respond_to?(:split)\n\n      @current_format      = File.extname(@file.path)\n      @basename            = File.basename(@file.path, @current_format)\n      @frame_index         = multi_frame_format? ? options.fetch(:frame_index, 0) : 0\n    end\n\n    # Returns true if the +target_geometry+ is meant to crop.\n    def crop?\n      @crop\n    end\n\n    # Returns true if the image is meant to make use of additional convert options.\n    def convert_options?\n      !@convert_options.nil? && !@convert_options.empty?\n    end\n\n    # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile\n    # that contains the new image.\n    def make\n      src = @file\n      filename = [@basename, @format ? \".#{@format}\" : \"\"].join\n      dst = TempfileFactory.new.generate(filename)\n\n      begin\n        parameters = []\n        parameters << source_file_options\n        parameters << \":source\"\n        parameters << transformation_command\n        parameters << convert_options\n        parameters << \":dest\"\n\n        parameters = parameters.flatten.compact.join(\" \").strip.squeeze(\" \")\n\n        frame = animated? ? \"\" : \"[#{@frame_index}]\"\n        convert(\n          parameters,\n          source: \"#{File.expand_path(src.path)}#{frame}\",\n          dest: File.expand_path(dst.path),\n        )\n      rescue Terrapin::ExitStatusError => e\n        if @whiny\n          message = \"There was an error processing the thumbnail for #{@basename}:\\n\" + e.message\n          raise Paperclip::Error, message\n        end\n      rescue Terrapin::CommandNotFoundError => e\n        raise Paperclip::Errors::CommandNotFoundError.new(\"Could not run the `convert` command. Please install ImageMagick.\")\n      end\n\n      dst\n    end\n\n    # Returns the command ImageMagick's +convert+ needs to transform the image\n    # into the thumbnail.\n    def transformation_command\n      scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)\n      trans = []\n      trans << \"-coalesce\" if animated?\n      trans << \"-auto-orient\" if auto_orient\n      trans << \"-resize\" << %[\"#{scale}\"] unless scale.nil? || scale.empty?\n      trans << \"-crop\" << %[\"#{crop}\"] << \"+repage\" if crop\n      trans << '-layers \"optimize\"' if animated?\n      trans\n    end\n\n    protected\n\n    def multi_frame_format?\n      MULTI_FRAME_FORMATS.include? @current_format\n    end\n\n    def animated?\n      @animated && (ANIMATED_FORMATS.include?(@format.to_s) || @format.blank?)  && identified_as_animated?\n    end\n\n    # Return true if ImageMagick's +identify+ returns an animated format\n    def identified_as_animated?\n      if @identified_as_animated.nil?\n        @identified_as_animated = ANIMATED_FORMATS.include? identify(\"-format %m :file\", :file => \"#{@file.path}[0]\").to_s.downcase.strip\n      end\n      @identified_as_animated\n    rescue Terrapin::ExitStatusError => e\n      raise Paperclip::Error, \"There was an error running `identify` for #{@basename}\" if @whiny\n    rescue Terrapin::CommandNotFoundError => e\n      raise Paperclip::Errors::CommandNotFoundError.new(\"Could not run the `identify` command. Please install ImageMagick.\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/url_generator.rb",
    "content": "require 'uri'\nrequire 'active_support/core_ext/module/delegation'\n\nmodule Paperclip\n  class UrlGenerator\n    def initialize(attachment)\n      @attachment = attachment\n    end\n\n    def for(style_name, options)\n      interpolated = attachment_options[:interpolator].interpolate(\n        most_appropriate_url, @attachment, style_name\n      )\n\n      escaped = escape_url_as_needed(interpolated, options)\n      timestamp_as_needed(escaped, options)\n    end\n\n    private\n\n    attr_reader :attachment\n    delegate :options, to: :attachment, prefix: true\n\n    # This method is all over the place.\n    def default_url\n      if attachment_options[:default_url].respond_to?(:call)\n        attachment_options[:default_url].call(@attachment)\n      elsif attachment_options[:default_url].is_a?(Symbol)\n        @attachment.instance.send(attachment_options[:default_url])\n      else\n        attachment_options[:default_url]\n      end\n    end\n\n    def most_appropriate_url\n      if @attachment.original_filename.nil?\n        default_url\n      else\n        attachment_options[:url]\n      end\n    end\n\n    def timestamp_as_needed(url, options)\n      if options[:timestamp] && timestamp_possible?\n        delimiter_char = url.match(/\\?.+=/) ? '&' : '?'\n        \"#{url}#{delimiter_char}#{@attachment.updated_at.to_s}\"\n      else\n        url\n      end\n    end\n\n    def timestamp_possible?\n      @attachment.respond_to?(:updated_at) && @attachment.updated_at.present?\n    end\n\n    def escape_url_as_needed(url, options)\n      if options[:escape]\n        escape_url(url)\n      else\n        url\n      end\n    end\n\n    def escape_url(url)\n      if url.respond_to?(:escape)\n        url.escape\n      else\n        URI.escape(url).gsub(escape_regex){|m| \"%#{m.ord.to_s(16).upcase}\" }\n      end\n    end\n\n    def escape_regex\n      /[\\?\\(\\)\\[\\]\\+]/\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/validators/attachment_content_type_validator.rb",
    "content": "module Paperclip\n  module Validators\n    class AttachmentContentTypeValidator < ActiveModel::EachValidator\n      def initialize(options)\n        options[:allow_nil] = true unless options.has_key?(:allow_nil)\n        super\n      end\n\n      def self.helper_method_name\n        :validates_attachment_content_type\n      end\n\n      def validate_each(record, attribute, value)\n        base_attribute = attribute.to_sym\n        attribute = \"#{attribute}_content_type\".to_sym\n        value = record.send :read_attribute_for_validation, attribute\n\n        return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])\n\n        validate_whitelist(record, attribute, value)\n        validate_blacklist(record, attribute, value)\n\n        if record.errors.include? attribute\n          record.errors[attribute].each do |error|\n            record.errors.add base_attribute, error\n          end\n        end\n      end\n\n      def validate_whitelist(record, attribute, value)\n        if allowed_types.present? && allowed_types.none? { |type| type === value }\n          mark_invalid record, attribute, allowed_types\n        end\n      end\n\n      def validate_blacklist(record, attribute, value)\n        if forbidden_types.present? && forbidden_types.any? { |type| type === value }\n          mark_invalid record, attribute, forbidden_types\n        end\n      end\n\n      def mark_invalid(record, attribute, types)\n        record.errors.add attribute, :invalid, options.merge(:types => types.join(', '))\n      end\n\n      def allowed_types\n        [options[:content_type]].flatten.compact\n      end\n\n      def forbidden_types\n        [options[:not]].flatten.compact\n      end\n\n      def check_validity!\n        unless options.has_key?(:content_type) || options.has_key?(:not)\n          raise ArgumentError, \"You must pass in either :content_type or :not to the validator\"\n        end\n      end\n    end\n\n    module HelperMethods\n      # Places ActiveModel validations on the content type of the file\n      # assigned. The possible options are:\n      # * +content_type+: Allowed content types.  Can be a single content type\n      #   or an array.  Each type can be a String or a Regexp. It should be\n      #   noted that Internet Explorer uploads files with content_types that you\n      #   may not expect. For example, JPEG images are given image/pjpeg and\n      #   PNGs are image/x-png, so keep that in mind when determining how you\n      #   match.  Allows all by default.\n      # * +not+: Forbidden content types.\n      # * +message+: The message to display when the uploaded file has an invalid\n      #   content type.\n      # * +if+: A lambda or name of an instance method. Validation will only\n      #   be run is this lambda or method returns true.\n      # * +unless+: Same as +if+ but validates if lambda or method returns false.\n      # NOTE: If you do not specify an [attachment]_content_type field on your\n      # model, content_type validation will work _ONLY upon assignment_ and\n      # re-validation after the instance has been reloaded will always succeed.\n      # You'll still need to have a virtual attribute (created by +attr_accessor+)\n      # name +[attachment]_content_type+ to be able to use this validator.\n      def validates_attachment_content_type(*attr_names)\n        options = _merge_attributes(attr_names)\n        validates_with AttachmentContentTypeValidator, options.dup\n        validate_before_processing AttachmentContentTypeValidator, options.dup\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/validators/attachment_file_name_validator.rb",
    "content": "module Paperclip\n  module Validators\n    class AttachmentFileNameValidator < ActiveModel::EachValidator\n      def initialize(options)\n        options[:allow_nil] = true unless options.has_key?(:allow_nil)\n        super\n      end\n\n      def self.helper_method_name\n        :validates_attachment_file_name\n      end\n\n      def validate_each(record, attribute, value)\n        base_attribute = attribute.to_sym\n        attribute = \"#{attribute}_file_name\".to_sym\n        value = record.send :read_attribute_for_validation, attribute\n\n        return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])\n\n        validate_whitelist(record, attribute, value)\n        validate_blacklist(record, attribute, value)\n\n        if record.errors.include? attribute\n          record.errors[attribute].each do |error|\n            record.errors.add base_attribute, error\n          end\n        end\n      end\n\n      def validate_whitelist(record, attribute, value)\n        if allowed.present? && allowed.none? { |type| type === value }\n          mark_invalid record, attribute, allowed\n        end\n      end\n\n      def validate_blacklist(record, attribute, value)\n        if forbidden.present? && forbidden.any? { |type| type === value }\n          mark_invalid record, attribute, forbidden\n        end\n      end\n\n      def mark_invalid(record, attribute, patterns)\n        record.errors.add attribute, :invalid, options.merge(:names => patterns.join(', '))\n      end\n\n      def allowed\n        [options[:matches]].flatten.compact\n      end\n\n      def forbidden\n        [options[:not]].flatten.compact\n      end\n\n      def check_validity!\n        unless options.has_key?(:matches) || options.has_key?(:not)\n          raise ArgumentError, \"You must pass in either :matches or :not to the validator\"\n        end\n      end\n    end\n\n    module HelperMethods\n      # Places ActiveModel validations on the name of the file\n      # assigned. The possible options are:\n      # * +matches+: Allowed filename patterns as Regexps. Can be a single one\n      #   or an array.\n      # * +not+: Forbidden file name patterns, specified the same was as +matches+.\n      # * +message+: The message to display when the uploaded file has an invalid\n      #   name.\n      # * +if+: A lambda or name of an instance method. Validation will only\n      #   be run is this lambda or method returns true.\n      # * +unless+: Same as +if+ but validates if lambda or method returns false.\n      def validates_attachment_file_name(*attr_names)\n        options = _merge_attributes(attr_names)\n        validates_with AttachmentFileNameValidator, options.dup\n        validate_before_processing AttachmentFileNameValidator, options.dup\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/paperclip/validators/attachment_file_type_ignorance_validator.rb",
    "content": "require 'active_model/validations/presence'\n\nmodule Paperclip\n  module Validators\n    class AttachmentFileTypeIgnoranceValidator < ActiveModel::EachValidator\n      def validate_each(record, attribute, value)\n        # This doesn't do anything. It's just to mark that you don't care about\n        # the file_names or content_types of your incoming attachments.\n      end\n\n      def self.helper_method_name\n        :do_not_validate_attachment_file_type\n      end\n    end\n\n    module HelperMethods\n      # Places ActiveModel validations on the presence of a file.\n      # Options:\n      # * +if+: A lambda or name of an instance method. Validation will only\n      #   be run if this lambda or method returns true.\n      # * +unless+: Same as +if+ but validates if lambda or method returns false.\n      def do_not_validate_attachment_file_type(*attr_names)\n        options = _merge_attributes(attr_names)\n        validates_with AttachmentFileTypeIgnoranceValidator, options.dup\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/paperclip/validators/attachment_presence_validator.rb",
    "content": "require 'active_model/validations/presence'\n\nmodule Paperclip\n  module Validators\n    class AttachmentPresenceValidator < ActiveModel::EachValidator\n      def validate_each(record, attribute, value)\n        if record.send(\"#{attribute}_file_name\").blank?\n          record.errors.add(attribute, :blank, options)\n        end\n      end\n\n      def self.helper_method_name\n        :validates_attachment_presence\n      end\n    end\n\n    module HelperMethods\n      # Places ActiveModel validations on the presence of a file.\n      # Options:\n      # * +if+: A lambda or name of an instance method. Validation will only\n      #   be run if this lambda or method returns true.\n      # * +unless+: Same as +if+ but validates if lambda or method returns false.\n      def validates_attachment_presence(*attr_names)\n        options = _merge_attributes(attr_names)\n        validates_with AttachmentPresenceValidator, options.dup\n        validate_before_processing AttachmentPresenceValidator, options.dup\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/validators/attachment_size_validator.rb",
    "content": "require 'active_model/validations/numericality'\n\nmodule Paperclip\n  module Validators\n    class AttachmentSizeValidator < ActiveModel::Validations::NumericalityValidator\n      AVAILABLE_CHECKS = [:less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to]\n\n      def initialize(options)\n        extract_options(options)\n        super\n      end\n\n      def self.helper_method_name\n        :validates_attachment_size\n      end\n\n      def validate_each(record, attr_name, value)\n        base_attr_name = attr_name\n        attr_name = \"#{attr_name}_file_size\".to_sym\n        value = record.send(:read_attribute_for_validation, attr_name)\n\n        unless value.blank?\n          options.slice(*AVAILABLE_CHECKS).each do |option, option_value|\n            option_value = option_value.call(record) if option_value.is_a?(Proc)\n            option_value = extract_option_value(option, option_value)\n\n            unless value.send(CHECKS[option], option_value)\n              error_message_key = options[:in] ? :in_between : option\n              [ attr_name, base_attr_name ].each do |error_attr_name|\n                record.errors.add(error_attr_name, error_message_key, filtered_options(value).merge(\n                  :min => min_value_in_human_size(record),\n                  :max => max_value_in_human_size(record),\n                  :count => human_size(option_value)\n                ))\n              end\n            end\n          end\n        end\n      end\n\n      def check_validity!\n        unless (AVAILABLE_CHECKS + [:in]).any? { |argument| options.has_key?(argument) }\n          raise ArgumentError, \"You must pass either :less_than, :greater_than, or :in to the validator\"\n        end\n      end\n\n      private\n\n      def extract_options(options)\n        if range = options[:in]\n          if !options[:in].respond_to?(:call)\n            options[:less_than_or_equal_to] = range.max\n            options[:greater_than_or_equal_to] = range.min\n          else\n            options[:less_than_or_equal_to] = range\n            options[:greater_than_or_equal_to] = range\n          end\n        end\n      end\n\n      def extract_option_value(option, option_value)\n        if option_value.is_a?(Range)\n          if [:less_than, :less_than_or_equal_to].include?(option)\n            option_value.max\n          else\n            option_value.min\n          end\n        else\n          option_value\n        end\n      end\n\n      def human_size(size)\n        ActiveSupport::NumberHelper.number_to_human_size(size)\n      end\n\n      def min_value_in_human_size(record)\n        value = options[:greater_than_or_equal_to] || options[:greater_than]\n        value = value.call(record) if value.respond_to?(:call)\n        value = value.min if value.respond_to?(:min)\n        human_size(value)\n      end\n\n      def max_value_in_human_size(record)\n        value = options[:less_than_or_equal_to] || options[:less_than]\n        value = value.call(record) if value.respond_to?(:call)\n        value = value.max if value.respond_to?(:max)\n        human_size(value)\n      end\n    end\n\n    module HelperMethods\n      # Places ActiveModel validations on the size of the file assigned. The\n      # possible options are:\n      # * +in+: a Range of bytes (i.e. +1..1.megabyte+),\n      # * +less_than+: equivalent to :in => 0..options[:less_than]\n      # * +greater_than+: equivalent to :in => options[:greater_than]..Infinity\n      # * +message+: error message to display, use :min and :max as replacements\n      # * +if+: A lambda or name of an instance method. Validation will only\n      #   be run if this lambda or method returns true.\n      # * +unless+: Same as +if+ but validates if lambda or method returns false.\n      def validates_attachment_size(*attr_names)\n        options = _merge_attributes(attr_names)\n        validates_with AttachmentSizeValidator, options.dup\n        validate_before_processing AttachmentSizeValidator, options.dup\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/validators/media_type_spoof_detection_validator.rb",
    "content": "require 'active_model/validations/presence'\n\nmodule Paperclip\n  module Validators\n    class MediaTypeSpoofDetectionValidator < ActiveModel::EachValidator\n      def validate_each(record, attribute, value)\n        adapter = Paperclip.io_adapters.for(value)\n        if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed?\n          record.errors.add(attribute, :spoofed_media_type)\n        end\n\n        if adapter.tempfile\n          adapter.tempfile.close(true)\n        end\n      end\n    end\n\n    module HelperMethods\n      # Places ActiveModel validations on the presence of a file.\n      # Options:\n      # * +if+: A lambda or name of an instance method. Validation will only\n      #   be run if this lambda or method returns true.\n      # * +unless+: Same as +if+ but validates if lambda or method returns false.\n      def validates_media_type_spoof_detection(*attr_names)\n        options = _merge_attributes(attr_names)\n        validates_with MediaTypeSpoofDetectionValidator, options.dup\n        validate_before_processing MediaTypeSpoofDetectionValidator, options.dup\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/validators.rb",
    "content": "require 'active_model'\nrequire 'active_support/concern'\nrequire 'active_support/core_ext/array/wrap'\nrequire 'paperclip/validators/attachment_content_type_validator'\nrequire 'paperclip/validators/attachment_file_name_validator'\nrequire 'paperclip/validators/attachment_presence_validator'\nrequire 'paperclip/validators/attachment_size_validator'\nrequire 'paperclip/validators/media_type_spoof_detection_validator'\nrequire 'paperclip/validators/attachment_file_type_ignorance_validator'\n\nmodule Paperclip\n  module Validators\n    extend ActiveSupport::Concern\n\n    included do\n      extend  HelperMethods\n      include HelperMethods\n    end\n\n    ::Paperclip::REQUIRED_VALIDATORS = [AttachmentFileNameValidator, AttachmentContentTypeValidator, AttachmentFileTypeIgnoranceValidator]\n\n    module ClassMethods\n      # This method is a shortcut to validator classes that is in\n      # \"Attachment...Validator\" format. It is almost the same thing as the\n      # +validates+ method that shipped with Rails, but this is customized to\n      # be using with attachment validators. This is helpful when you're using\n      # multiple attachment validators on a single attachment.\n      #\n      # Example of using the validator:\n      #\n      #   validates_attachment :avatar, :presence => true,\n      #      :content_type => { :content_type => \"image/jpg\" },\n      #      :size => { :in => 0..10.kilobytes }\n      #\n      def validates_attachment(*attributes)\n        options = attributes.extract_options!.dup\n\n        Paperclip::Validators.constants.each do |constant|\n          if constant.to_s =~ /\\AAttachment(.+)Validator\\z/\n            validator_kind = $1.underscore.to_sym\n\n            if options.has_key?(validator_kind)\n              validator_options = options.delete(validator_kind)\n              validator_options = {} if validator_options == true\n              conditional_options = options.slice(:if, :unless)\n              Array.wrap(validator_options).each do |local_options|\n                method_name = Paperclip::Validators.const_get(constant.to_s).helper_method_name\n                send(method_name, attributes, local_options.merge(conditional_options))\n              end\n            end\n          end\n        end\n      end\n\n      def validate_before_processing(validator_class, options)\n        options = options.dup\n        attributes = options.delete(:attributes)\n        attributes.each do |attribute|\n          options[:attributes] = [attribute]\n          create_validating_before_filter(attribute, validator_class, options)\n        end\n      end\n\n      def create_validating_before_filter(attribute, validator_class, options)\n        if_clause = options.delete(:if)\n        unless_clause = options.delete(:unless)\n        send(:\"before_#{attribute}_post_process\", :if => if_clause, :unless => unless_clause) do |*args|\n          validator_class.new(options.dup).validate(self)\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/paperclip/version.rb",
    "content": "module Paperclip\n  unless defined?(Paperclip::VERSION)\n    VERSION = \"6.1.0\".freeze\n  end\nend\n"
  },
  {
    "path": "lib/paperclip.rb",
    "content": "# Paperclip allows file attachments that are stored in the filesystem. All graphical\n# transformations are done using the Graphics/ImageMagick command line utilities and\n# are stored in Tempfiles until the record is saved. Paperclip does not require a\n# separate model for storing the attachment's information, instead adding a few simple\n# columns to your table.\n#\n# Author:: Jon Yurek\n# Copyright:: Copyright (c) 2008-2011 thoughtbot, inc.\n# License:: MIT License (http://www.opensource.org/licenses/mit-license.php)\n#\n# Paperclip defines an attachment as any file, though it makes special considerations\n# for image files. You can declare that a model has an attached file with the\n# +has_attached_file+ method:\n#\n#   class User < ActiveRecord::Base\n#     has_attached_file :avatar, :styles => { :thumb => \"100x100\" }\n#   end\n#\n#   user = User.new\n#   user.avatar = params[:user][:avatar]\n#   user.avatar.url\n#   # => \"/users/avatars/4/original_me.jpg\"\n#   user.avatar.url(:thumb)\n#   # => \"/users/avatars/4/thumb_me.jpg\"\n#\n# See the +has_attached_file+ documentation for more details.\n\nrequire 'erb'\nrequire 'digest'\nrequire 'tempfile'\nrequire 'paperclip/version'\nrequire 'paperclip/geometry_parser_factory'\nrequire 'paperclip/geometry_detector_factory'\nrequire 'paperclip/geometry'\nrequire 'paperclip/processor'\nrequire 'paperclip/processor_helpers'\nrequire 'paperclip/tempfile'\nrequire 'paperclip/thumbnail'\nrequire 'paperclip/interpolations/plural_cache'\nrequire 'paperclip/interpolations'\nrequire 'paperclip/tempfile_factory'\nrequire 'paperclip/style'\nrequire 'paperclip/attachment'\nrequire 'paperclip/storage'\nrequire 'paperclip/callbacks'\nrequire 'paperclip/file_command_content_type_detector'\nrequire 'paperclip/media_type_spoof_detector'\nrequire 'paperclip/content_type_detector'\nrequire 'paperclip/glue'\nrequire 'paperclip/errors'\nrequire 'paperclip/missing_attachment_styles'\nrequire 'paperclip/validators'\nrequire 'paperclip/logger'\nrequire 'paperclip/helpers'\nrequire 'paperclip/has_attached_file'\nrequire 'paperclip/attachment_registry'\nrequire 'paperclip/filename_cleaner'\nrequire 'paperclip/rails_environment'\n\nbegin\n  # Use mime/types/columnar if available, for reduced memory usage\n  require \"mime/types/columnar\"\nrescue LoadError\n  require \"mime/types\"\nend\n\nrequire 'mimemagic'\nrequire 'mimemagic/overlay'\nrequire 'logger'\nrequire 'terrapin'\n\nrequire 'paperclip/railtie' if defined?(Rails::Railtie)\n\n# The base module that gets included in ActiveRecord::Base. See the\n# documentation for Paperclip::ClassMethods for more useful information.\nmodule Paperclip\n  extend Helpers\n  extend Logger\n  extend ProcessorHelpers\n\n  # Provides configurability to Paperclip. The options available are:\n  # * whiny: Will raise an error if Paperclip cannot process thumbnails of\n  #   an uploaded image. Defaults to true.\n  # * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors\n  #   log levels, etc. Defaults to true.\n  # * command_path: Defines the path at which to find the command line\n  #   programs if they are not visible to Rails the system's search path. Defaults to\n  #   nil, which uses the first executable found in the user's search path.\n  # * use_exif_orientation: Whether to inspect EXIF data to determine an\n  #   image's orientation. Defaults to true.\n  def self.options\n    @options ||= {\n      command_path: nil,\n      content_type_mappings: {},\n      log: true,\n      log_command: true,\n      read_timeout: nil,\n      swallow_stderr: true,\n      use_exif_orientation: true,\n      whiny: true,\n      is_windows: Gem.win_platform?\n    }\n  end\n\n  def self.io_adapters=(new_registry)\n    @io_adapters = new_registry\n  end\n\n  def self.io_adapters\n    @io_adapters ||= Paperclip::AdapterRegistry.new\n  end\n\n  module ClassMethods\n    # +has_attached_file+ gives the class it is called on an attribute that maps to a file. This\n    # is typically a file stored somewhere on the filesystem and has been uploaded by a user.\n    # The attribute returns a Paperclip::Attachment object which handles the management of\n    # that file. The intent is to make the attachment as much like a normal attribute. The\n    # thumbnails will be created when the new file is assigned, but they will *not* be saved\n    # until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is\n    # called on it, the attachment will *not* be deleted until +save+ is called. See the\n    # Paperclip::Attachment documentation for more specifics. There are a number of options\n    # you can set to change the behavior of a Paperclip attachment:\n    # * +url+: The full URL of where the attachment is publicly accessible. This can just\n    #   as easily point to a directory served directly through Apache as it can to an action\n    #   that can control permissions. You can specify the full domain and path, but usually\n    #   just an absolute path is sufficient. The leading slash *must* be included manually for\n    #   absolute paths. The default value is\n    #   \"/system/:class/:attachment/:id_partition/:style/:filename\". See\n    #   Paperclip::Attachment#interpolate for more information on variable interpolaton.\n    #     :url => \"/:class/:attachment/:id/:style_:filename\"\n    #     :url => \"http://some.other.host/stuff/:class/:id_:extension\"\n    #   Note: When using the +s3+ storage option, the +url+ option expects\n    #   particular values. See the Paperclip::Storage::S3#url documentation for\n    #   specifics.\n    # * +default_url+: The URL that will be returned if there is no attachment assigned.\n    #   This field is interpolated just as the url is. The default value is\n    #   \"/:attachment/:style/missing.png\"\n    #     has_attached_file :avatar, :default_url => \"/images/default_:style_avatar.png\"\n    #     User.new.avatar_url(:small) # => \"/images/default_small_avatar.png\"\n    # * +styles+: A hash of thumbnail styles and their geometries. You can find more about\n    #   geometry strings at the ImageMagick website\n    #   (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip\n    #   also adds the \"#\" option (e.g. \"50x50#\"), which will resize the image to fit maximally\n    #   inside the dimensions and then crop the rest off (weighted at the center). The\n    #   default value is to generate no thumbnails.\n    # * +default_style+: The thumbnail style that will be used by default URLs.\n    #   Defaults to +original+.\n    #     has_attached_file :avatar, :styles => { :normal => \"100x100#\" },\n    #                       :default_style => :normal\n    #     user.avatar.url # => \"/avatars/23/normal_me.png\"\n    # * +keep_old_files+: Keep the existing attachment files (original + resized) from\n    #   being automatically deleted when an attachment is cleared or updated. Defaults to +false+.\n    # * +preserve_files+: Keep the existing attachment files in all cases, even if the parent\n    #   record is destroyed. Defaults to +false+.\n    # * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due\n    #   to a command line error. This will override the global setting for this attachment.\n    #   Defaults to true.\n    # * +convert_options+: When creating thumbnails, use this free-form options\n    #   array to pass in various convert command options.  Typical options are \"-strip\" to\n    #   remove all Exif data from the image (save space for thumbnails and avatars) or\n    #   \"-depth 8\" to specify the bit depth of the resulting conversion.  See ImageMagick\n    #   convert documentation for more options: (http://www.imagemagick.org/script/convert.php)\n    #   Note that this option takes a hash of options, each of which correspond to the style\n    #   of thumbnail being generated. You can also specify :all as a key, which will apply\n    #   to all of the thumbnails being generated. If you specify options for the :original,\n    #   it would be best if you did not specify destructive options, as the intent of keeping\n    #   the original around is to regenerate all the thumbnails when requirements change.\n    #     has_attached_file :avatar, :styles => { :large => \"300x300\", :negative => \"100x100\" }\n    #                                :convert_options => {\n    #                                  :all => \"-strip\",\n    #                                  :negative => \"-negate\"\n    #                                }\n    #   NOTE: While not deprecated yet, it is not recommended to specify options this way.\n    #   It is recommended that :convert_options option be included in the hash passed to each\n    #   :styles for compatibility with future versions.\n    #   NOTE: Strings supplied to :convert_options are split on space in order to undergo\n    #   shell quoting for safety. If your options require a space, please pre-split them\n    #   and pass an array to :convert_options instead.\n    # * +storage+: Chooses the storage backend where the files will be stored. The current\n    #   choices are :filesystem, :fog and :s3. The default is :filesystem. Make sure you read the\n    #   documentation for Paperclip::Storage::Filesystem, Paperclip::Storage::Fog and Paperclip::Storage::S3\n    #   for backend-specific options.\n    #\n    # It's also possible for you to dynamically define your interpolation string for :url,\n    # :default_url, and :path in your model by passing a method name as a symbol as a argument\n    # for your has_attached_file definition:\n    #\n    #   class Person\n    #     has_attached_file :avatar, :default_url => :default_url_by_gender\n    #\n    #     private\n    #\n    #     def default_url_by_gender\n    #       \"/assets/avatars/default_#{gender}.png\"\n    #     end\n    #   end\n    def has_attached_file(name, options = {})\n      HasAttachedFile.define_on(self, name, options)\n    end\n  end\nend\n\n# This stuff needs to be run after Paperclip is defined.\nrequire 'paperclip/io_adapters/registry'\nrequire 'paperclip/io_adapters/abstract_adapter'\nrequire 'paperclip/io_adapters/empty_string_adapter'\nrequire 'paperclip/io_adapters/identity_adapter'\nrequire 'paperclip/io_adapters/file_adapter'\nrequire 'paperclip/io_adapters/stringio_adapter'\nrequire 'paperclip/io_adapters/data_uri_adapter'\nrequire 'paperclip/io_adapters/nil_adapter'\nrequire 'paperclip/io_adapters/attachment_adapter'\nrequire 'paperclip/io_adapters/uploaded_file_adapter'\nrequire 'paperclip/io_adapters/uri_adapter'\nrequire 'paperclip/io_adapters/http_url_proxy_adapter'\n"
  },
  {
    "path": "lib/tasks/paperclip.rake",
    "content": "require 'paperclip/attachment_registry'\n\nmodule Paperclip\n  module Task\n    def self.obtain_class\n      class_name = ENV['CLASS'] || ENV['class']\n      raise \"Must specify CLASS\" unless class_name\n      class_name\n    end\n\n    def self.obtain_attachments(klass)\n      klass = Paperclip.class_for(klass.to_s)\n      name = ENV['ATTACHMENT'] || ENV['attachment']\n\n      attachment_names = Paperclip::AttachmentRegistry.names_for(klass)\n\n      if attachment_names.empty?\n        raise \"Class #{klass.name} has no attachments specified\"\n      end\n\n      if name.present? && attachment_names.map(&:to_s).include?(name.to_s)\n        [ name ]\n      else\n        attachment_names\n      end\n    end\n\n    def self.log_error(error)\n      $stderr.puts error\n    end\n  end\nend\n\nnamespace :paperclip do\n  desc \"Refreshes both metadata and thumbnails.\"\n  task :refresh => [\"paperclip:refresh:metadata\", \"paperclip:refresh:thumbnails\"]\n\n  namespace :refresh do\n    desc \"Regenerates thumbnails for a given CLASS (and optional ATTACHMENT and STYLES splitted by comma).\"\n    task :thumbnails => :environment do\n      klass = Paperclip::Task.obtain_class\n      names = Paperclip::Task.obtain_attachments(klass)\n      styles = (ENV['STYLES'] || ENV['styles'] || '').split(',').map(&:to_sym)\n      names.each do |name|\n        Paperclip.each_instance_with_attachment(klass, name) do |instance|\n          attachment = instance.send(name)\n          begin\n            attachment.reprocess!(*styles)\n          rescue StandardError => e\n            Paperclip::Task.log_error(\"exception while processing #{klass} ID #{instance.id}:\")\n            Paperclip::Task.log_error(\" \" + e.message + \"\\n\")\n          end\n          unless instance.errors.blank?\n            Paperclip::Task.log_error(\"errors while processing #{klass} ID #{instance.id}:\")\n            Paperclip::Task.log_error(\" \" + instance.errors.full_messages.join(\"\\n \") + \"\\n\")\n          end\n        end\n      end\n    end\n\n    desc \"Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT).\"\n    task :metadata => :environment do\n      klass = Paperclip::Task.obtain_class\n      names = Paperclip::Task.obtain_attachments(klass)\n      names.each do |name|\n        Paperclip.each_instance_with_attachment(klass, name) do |instance|\n          attachment = instance.send(name)\n          if file = Paperclip.io_adapters.for(attachment, attachment.options[:adapter_options])\n            instance.send(\"#{name}_file_name=\", instance.send(\"#{name}_file_name\").strip)\n            instance.send(\"#{name}_content_type=\", file.content_type.to_s.strip)\n            instance.send(\"#{name}_file_size=\", file.size) if instance.respond_to?(\"#{name}_file_size\")\n            instance.save(:validate => false)\n          else\n            true\n          end\n        end\n      end\n    end\n\n    desc \"Regenerates missing thumbnail styles for all classes using Paperclip.\"\n    task :missing_styles => :environment do\n      Rails.application.eager_load!\n      Paperclip.missing_attachments_styles.each do |klass, attachment_definitions|\n        attachment_definitions.each do |attachment_name, missing_styles|\n          puts \"Regenerating #{klass} -> #{attachment_name} -> #{missing_styles.inspect}\"\n          ENV['CLASS'] = klass.to_s\n          ENV['ATTACHMENT'] = attachment_name.to_s\n          ENV['STYLES'] = missing_styles.join(',')\n          Rake::Task['paperclip:refresh:thumbnails'].execute\n        end\n      end\n      Paperclip.save_current_attachments_styles!\n    end\n\n    desc \"Regenerates fingerprints for a given CLASS (and optional ATTACHMENT). Useful when changing digest.\"\n    task :fingerprints => :environment do\n      klass = Paperclip::Task.obtain_class\n      names = Paperclip::Task.obtain_attachments(klass)\n      names.each do |name|\n        Paperclip.each_instance_with_attachment(klass, name) do |instance|\n          attachment = instance.send(name)\n          attachment.assign(attachment)\n          instance.save(:validate => false)\n        end\n      end\n    end\n  end\n\n  desc \"Cleans out invalid attachments. Useful after you've added new validations.\"\n  task :clean => :environment do\n    klass = Paperclip::Task.obtain_class\n    names = Paperclip::Task.obtain_attachments(klass)\n    names.each do |name|\n      Paperclip.each_instance_with_attachment(klass, name) do |instance|\n        unless instance.valid?\n          attributes = %w(file_size file_name content_type).map{ |suffix| \"#{name}_#{suffix}\".to_sym }\n          if attributes.any?{ |attribute| instance.errors[attribute].present? }\n            instance.send(\"#{name}=\", nil)\n            instance.save(:validate => false)\n          end\n        end\n      end\n    end\n  end\n\n  desc \"find missing attachments. Useful to know which attachments are broken\"\n  task :find_broken_attachments => :environment do\n    klass = Paperclip::Task.obtain_class\n    names = Paperclip::Task.obtain_attachments(klass)\n    names.each do |name|\n      Paperclip.each_instance_with_attachment(klass, name) do |instance|\n        attachment = instance.send(name)\n        if attachment.exists?\n          print \".\"\n        else\n          Paperclip::Task.log_error(\"#{instance.class}##{attachment.name}, #{instance.id}, #{attachment.url}\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "paperclip.gemspec",
    "content": "$LOAD_PATH.push File.expand_path(\"../lib\", __FILE__)\nrequire 'paperclip/version'\n\nGem::Specification.new do |s|\n  s.name              = \"paperclip\"\n  s.version           = Paperclip::VERSION\n  s.platform          = Gem::Platform::RUBY\n  s.author            = \"Jon Yurek\"\n  s.email             = [\"jyurek@thoughtbot.com\"]\n  s.homepage          = \"https://github.com/thoughtbot/paperclip\"\n  s.summary           = \"File attachments as attributes for ActiveRecord\"\n  s.description       = \"Easy upload management for ActiveRecord\"\n  s.license           = \"MIT\"\n\n  s.files         = `git ls-files`.split(\"\\n\")\n  s.test_files    = `git ls-files -- {spec,features}/*`.split(\"\\n\")\n  s.executables   = `git ls-files -- bin/*`.split(\"\\n\").map{ |f| File.basename(f) }\n  s.require_paths = [\"lib\"]\n\n  if File.exist?('UPGRADING')\n    s.post_install_message = File.read(\"UPGRADING\")\n  end\n\n  s.requirements << \"ImageMagick\"\n  s.required_ruby_version = \">= 2.1.0\"\n\n  s.add_dependency('activemodel', '>= 4.2.0')\n  s.add_dependency('activesupport', '>= 4.2.0')\n  s.add_dependency('terrapin', '~> 0.6.0')\n  s.add_dependency('mime-types')\n  s.add_dependency('mimemagic', '~> 0.3.0')\n\n  s.add_development_dependency('activerecord', '>= 4.2.0')\n  s.add_development_dependency('shoulda')\n  s.add_development_dependency('rspec', '~> 3.0')\n  s.add_development_dependency('appraisal')\n  s.add_development_dependency('mocha')\n  s.add_development_dependency('aws-sdk-s3')\n  s.add_development_dependency('bourne')\n  s.add_development_dependency('cucumber-rails')\n  s.add_development_dependency('cucumber-expressions', '4.0.3') # TODO: investigate failures on 4.0.4\n  s.add_development_dependency('aruba', '~> 0.9.0')\n  s.add_development_dependency('nokogiri')\n  s.add_development_dependency('capybara')\n  s.add_development_dependency('bundler')\n  s.add_development_dependency('fog-aws')\n  s.add_development_dependency('fog-local')\n  s.add_development_dependency('launchy')\n  s.add_development_dependency('rake')\n  s.add_development_dependency('fakeweb')\n  s.add_development_dependency('railties')\n  s.add_development_dependency('generator_spec')\n  s.add_development_dependency('timecop')\nend\n"
  },
  {
    "path": "shoulda_macros/paperclip.rb",
    "content": "require 'paperclip/matchers'\n\nmodule Paperclip\n  # =Paperclip Shoulda Macros\n  #\n  # These macros are intended for use with shoulda, and will be included into\n  # your tests automatically. All of the macros use the standard shoulda\n  # assumption that the name of the test is based on the name of the model\n  # you're testing (that is, UserTest is the test for the User model), and\n  # will load that class for testing purposes.\n  module Shoulda\n    include Matchers\n    # This will test whether you have defined your attachment correctly by\n    # checking for all the required fields exist after the definition of the\n    # attachment.\n    def should_have_attached_file name\n      klass   = self.name.gsub(/Test$/, '').constantize\n      matcher = have_attached_file name\n      should matcher.description do\n        assert_accepts(matcher, klass)\n      end\n    end\n\n    # Tests for validations on the presence of the attachment.\n    def should_validate_attachment_presence name\n      klass   = self.name.gsub(/Test$/, '').constantize\n      matcher = validate_attachment_presence name\n      should matcher.description do\n        assert_accepts(matcher, klass)\n      end\n    end\n\n    # Tests that you have content_type validations specified. There are two\n    # options, :valid and :invalid. Both accept an array of strings. The\n    # strings should be a list of content types which will pass and fail\n    # validation, respectively.\n    def should_validate_attachment_content_type name, options = {}\n      klass   = self.name.gsub(/Test$/, '').constantize\n      valid   = [options[:valid]].flatten\n      invalid = [options[:invalid]].flatten\n      matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)\n      should matcher.description do\n        assert_accepts(matcher, klass)\n      end\n    end\n\n    # Tests to ensure that you have file size validations turned on. You\n    # can pass the same options to this that you can to\n    # validate_attachment_file_size - :less_than, :greater_than, and :in.\n    # :less_than checks that a file is less than a certain size, :greater_than\n    # checks that a file is more than a certain size, and :in takes a Range or\n    # Array which specifies the lower and upper limits of the file size.\n    def should_validate_attachment_size name, options = {}\n      klass   = self.name.gsub(/Test$/, '').constantize\n      min     = options[:greater_than] || (options[:in] && options[:in].first) || 0\n      max     = options[:less_than]    || (options[:in] && options[:in].last)  || (1.0/0)\n      range   = (min..max)\n      matcher = validate_attachment_size(name).in(range)\n      should matcher.description do\n        assert_accepts(matcher, klass)\n      end\n    end\n\n    # Stubs the HTTP PUT for an attachment using S3 storage.\n    #\n    # @example\n    #   stub_paperclip_s3('user', 'avatar', 'png')\n    def stub_paperclip_s3(model, attachment, extension)\n      definition = model.gsub(\" \", \"_\").classify.constantize.\n                         attachment_definitions[attachment.to_sym]\n\n      path = \"http://s3.amazonaws.com/:id/#{definition[:path]}\"\n      path.gsub!(/:([^\\/\\.]+)/) do |match|\n        \"([^\\/\\.]+)\"\n      end\n\n      begin\n        FakeWeb.register_uri(:put, Regexp.new(path), :body => \"OK\")\n      rescue NameError\n        raise NameError, \"the stub_paperclip_s3 shoulda macro requires the fakeweb gem.\"\n      end\n    end\n\n    # Stub S3 and return a file for attachment. Best with Factory Girl.\n    # Uses a strict directory convention:\n    #\n    #     features/support/paperclip\n    #\n    # This method is used by the Paperclip-provided Cucumber step:\n    #\n    #     When I attach a \"demo_tape\" \"mp3\" file to a \"band\" on S3\n    #\n    # @example\n    #   Factory.define :band_with_demo_tape, :parent => :band do |band|\n    #     band.demo_tape { band.paperclip_fixture(\"band\", \"demo_tape\", \"png\") }\n    #   end\n    def paperclip_fixture(model, attachment, extension)\n      stub_paperclip_s3(model, attachment, extension)\n      base_path = File.join(File.dirname(__FILE__), \"..\", \"..\",\n                            \"features\", \"support\", \"paperclip\")\n      File.new(File.join(base_path, model, \"#{attachment}.#{extension}\"))\n    end\n  end\nend\n\nif defined?(ActionDispatch::Integration::Session)\n  class ActionDispatch::IntegrationTest::Session  #:nodoc:\n    include Paperclip::Shoulda\n  end\nelsif defined?(ActionController::Integration::Session)\n  class ActionController::Integration::Session  #:nodoc:\n    include Paperclip::Shoulda\n  end\nend\n\nif defined?(FactoryGirl::Factory)\n  class FactoryGirl::Factory\n    include Paperclip::Shoulda  #:nodoc:\n  end\nelse\n  class Factory\n    include Paperclip::Shoulda  #:nodoc:\n  end\nend\n\nif defined?(Minitest)\n  class Minitest::Unit::TestCase #:nodoc:\n    extend Paperclip::Shoulda\n  end\nelsif defined?(Test)\n  class Test::Unit::TestCase #:nodoc:\n    extend Paperclip::Shoulda\n  end\nend\n"
  },
  {
    "path": "spec/database.yml",
    "content": "test:\n  adapter: sqlite3\n  database: \":memory:\"\n\n"
  },
  {
    "path": "spec/paperclip/attachment_definitions_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe \"Attachment Definitions\" do\n  it 'returns all of the attachments on the class' do\n    reset_class \"Dummy\"\n    Dummy.has_attached_file :avatar, {path: \"abc\"}\n    Dummy.has_attached_file :other_attachment, {url: \"123\"}\n    Dummy.do_not_validate_attachment_file_type :avatar\n    expected = {avatar: {path: \"abc\"}, other_attachment: {url: \"123\"}}\n\n    expect(Dummy.attachment_definitions).to eq expected\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/attachment_processing_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe 'Attachment Processing' do\n  before { rebuild_class }\n\n  context 'using validates_attachment_content_type' do\n    it 'processes attachments given a valid assignment' do\n      file = File.new(fixture_file(\"5k.png\"))\n      Dummy.validates_attachment_content_type :avatar, content_type: \"image/png\"\n      instance = Dummy.new\n      attachment = instance.avatar\n      attachment.expects(:post_process_styles)\n\n      attachment.assign(file)\n    end\n\n    it 'does not process attachments given an invalid assignment with :not' do\n      file = File.new(fixture_file(\"5k.png\"))\n      Dummy.validates_attachment_content_type :avatar, not: \"image/png\"\n      instance = Dummy.new\n      attachment = instance.avatar\n      attachment.expects(:post_process_styles).never\n\n      attachment.assign(file)\n    end\n\n    it 'does not process attachments given an invalid assignment with :content_type' do\n      file = File.new(fixture_file(\"5k.png\"))\n      Dummy.validates_attachment_content_type :avatar, content_type: \"image/tiff\"\n      instance = Dummy.new\n      attachment = instance.avatar\n      attachment.expects(:post_process_styles).never\n\n      attachment.assign(file)\n    end\n\n    it 'allows what would be an invalid assignment when validation :if clause returns false' do\n      invalid_assignment = File.new(fixture_file(\"5k.png\"))\n      Dummy.validates_attachment_content_type :avatar, content_type: \"image/tiff\", if: lambda{false}\n      instance = Dummy.new\n      attachment = instance.avatar\n      attachment.expects(:post_process_styles)\n\n      attachment.assign(invalid_assignment)\n    end\n  end\n\n  context 'using validates_attachment' do\n    it 'processes attachments given a valid assignment' do\n      file = File.new(fixture_file(\"5k.png\"))\n      Dummy.validates_attachment :avatar, content_type: {content_type: \"image/png\"}\n      instance = Dummy.new\n      attachment = instance.avatar\n      attachment.expects(:post_process_styles)\n\n      attachment.assign(file)\n    end\n\n    it 'does not process attachments given an invalid assignment with :not' do\n      file = File.new(fixture_file(\"5k.png\"))\n      Dummy.validates_attachment :avatar, content_type: {not: \"image/png\"}\n      instance = Dummy.new\n      attachment = instance.avatar\n      attachment.expects(:post_process_styles).never\n\n      attachment.assign(file)\n    end\n\n    it 'does not process attachments given an invalid assignment with :content_type' do\n      file = File.new(fixture_file(\"5k.png\"))\n      Dummy.validates_attachment :avatar, content_type: {content_type: \"image/tiff\"}\n      instance = Dummy.new\n      attachment = instance.avatar\n      attachment.expects(:post_process_styles).never\n\n      attachment.assign(file)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/attachment_registry_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe 'Attachment Registry' do\n  before do\n    Paperclip::AttachmentRegistry.clear\n  end\n\n  context '.names_for' do\n    it 'includes attachment names for the given class' do\n      foo = Class.new\n      Paperclip::AttachmentRegistry.register(foo, :avatar, {})\n\n      assert_equal [:avatar], Paperclip::AttachmentRegistry.names_for(foo)\n    end\n\n    it 'does not include attachment names for other classes' do\n      foo = Class.new\n      bar = Class.new\n      Paperclip::AttachmentRegistry.register(foo, :avatar, {})\n      Paperclip::AttachmentRegistry.register(bar, :lover, {})\n\n      assert_equal [:lover], Paperclip::AttachmentRegistry.names_for(bar)\n    end\n\n    it 'produces the empty array for a missing key' do\n      assert_empty Paperclip::AttachmentRegistry.names_for(Class.new)\n    end\n  end\n\n  context '.each_definition' do\n    it 'calls the block with the class, attachment name, and options' do\n      foo = Class.new\n      expected_accumulations = [\n        [foo, :avatar, { yo: \"greeting\" }],\n        [foo, :greeter, { ciao: \"greeting\" }]\n      ]\n      expected_accumulations.each do |args|\n        Paperclip::AttachmentRegistry.register(*args)\n      end\n      accumulations = []\n\n      Paperclip::AttachmentRegistry.each_definition do |*args|\n        accumulations << args\n      end\n\n      assert_equal expected_accumulations, accumulations\n    end\n  end\n\n  context '.definitions_for' do\n    it 'produces the attachment name and options' do\n      expected_definitions = {\n        avatar: { yo: \"greeting\" },\n        greeter: { ciao: \"greeting\" }\n      }\n      foo = Class.new\n      Paperclip::AttachmentRegistry.register(\n        foo,\n        :avatar,\n        yo: \"greeting\"\n      )\n      Paperclip::AttachmentRegistry.register(\n        foo,\n        :greeter,\n        ciao: \"greeting\"\n      )\n\n      definitions = Paperclip::AttachmentRegistry.definitions_for(foo)\n\n      assert_equal expected_definitions, definitions\n    end\n\n    it 'produces defintions for subclasses' do\n      expected_definitions = { avatar: { yo: \"greeting\" } }\n      foo = Class.new\n      bar = Class.new(foo)\n      Paperclip::AttachmentRegistry.register(\n        foo,\n        :avatar,\n        expected_definitions[:avatar]\n      )\n\n      definitions = Paperclip::AttachmentRegistry.definitions_for(bar)\n\n      assert_equal expected_definitions, definitions\n    end\n\n    it 'produces defintions for subclasses but deep merging them' do\n      foo_definitions = { avatar: { yo: \"greeting\" } }\n      bar_definitions = { avatar: { ciao: \"greeting\" } }\n      expected_definitions = {\n        avatar: {\n          yo: \"greeting\",\n          ciao: \"greeting\"\n        }\n      }\n      foo = Class.new\n      bar = Class.new(foo)\n      Paperclip::AttachmentRegistry.register(\n        foo,\n        :avatar,\n        foo_definitions[:avatar]\n      )\n      Paperclip::AttachmentRegistry.register(\n        bar,\n        :avatar,\n        bar_definitions[:avatar]\n      )\n\n      definitions = Paperclip::AttachmentRegistry.definitions_for(bar)\n\n      assert_equal expected_definitions, definitions\n    end\n\n    it 'allows subclasses to override attachment defitions' do\n      foo_definitions = { avatar: { yo: \"greeting\" } }\n      bar_definitions = { avatar: { yo: \"hello\" } }\n\n      expected_definitions = {\n        avatar: {\n          yo: \"hello\"\n        }\n      }\n\n      foo = Class.new\n      bar = Class.new(foo)\n      Paperclip::AttachmentRegistry.register(\n        foo,\n        :avatar,\n        foo_definitions[:avatar]\n      )\n      Paperclip::AttachmentRegistry.register(\n        bar,\n        :avatar,\n        bar_definitions[:avatar]\n      )\n\n      definitions = Paperclip::AttachmentRegistry.definitions_for(bar)\n\n      assert_equal expected_definitions, definitions\n    end\n  end\n\n  context '.clear' do\n    it 'removes all of the existing attachment definitions' do\n      foo = Class.new\n      Paperclip::AttachmentRegistry.register(\n        foo,\n        :greeter,\n        ciao: \"greeting\"\n      )\n\n      Paperclip::AttachmentRegistry.clear\n\n      assert_empty Paperclip::AttachmentRegistry.names_for(foo)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/attachment_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Attachment do\n\n  it \"is not present when file not set\" do\n    rebuild_class\n    dummy = Dummy.new\n    expect(dummy.avatar).to be_blank\n    expect(dummy.avatar).to_not be_present\n  end\n\n  it \"is present when the file is set\" do\n    rebuild_class\n    dummy = Dummy.new\n    dummy.avatar = File.new(fixture_file(\"50x50.png\"), \"rb\")\n    expect(dummy.avatar).to_not be_blank\n    expect(dummy.avatar).to be_present\n  end\n\n  it \"processes :original style first\" do\n    file = File.new(fixture_file(\"50x50.png\"), 'rb')\n    rebuild_class styles: { small: '100x>', original: '42x42#' }\n    dummy = Dummy.new\n    dummy.avatar = file\n    dummy.save\n\n    # :small avatar should be 42px wide (processed original), not 50px (preprocessed original)\n    expect(`identify -format \"%w\" \"#{dummy.avatar.path(:small)}\"`.strip).to eq \"42\"\n\n    file.close\n  end\n\n  it \"does not delete styles that don't get reprocessed\" do\n    file = File.new(fixture_file(\"50x50.png\"), 'rb')\n    rebuild_class styles: {\n      small: \"100x>\",\n      large: \"500x>\",\n      original: \"42x42#\"\n    }\n\n    dummy = Dummy.new\n    dummy.avatar = file\n    dummy.save\n\n    expect(dummy.avatar.path(:small)).to exist\n    expect(dummy.avatar.path(:large)).to exist\n    expect(dummy.avatar.path(:original)).to exist\n\n    dummy.avatar.reprocess!(:small)\n\n    expect(dummy.avatar.path(:small)).to exist\n    expect(dummy.avatar.path(:large)).to exist\n    expect(dummy.avatar.path(:original)).to exist\n  end\n\n  it \"reprocess works with virtual content_type attribute\" do\n    rebuild_class styles: { small: \"100x>\" }\n    modify_table { |t| t.remove :avatar_content_type }\n    Dummy.send :attr_accessor, :avatar_content_type\n    Dummy.validates_attachment_content_type(\n      :avatar,\n      content_type: %w(image/jpeg image/png)\n    )\n    Dummy.create!(avatar: File.new(fixture_file(\"50x50.png\"), \"rb\"))\n\n    dummy = Dummy.first\n    dummy.avatar.reprocess!(:small)\n\n    expect(dummy.avatar.path(:small)).to exist\n  end\n\n  context \"having a not empty hash as a default option\" do\n    before do\n      @old_default_options = Paperclip::Attachment.default_options.dup\n      @new_default_options = { convert_options: { all: \"-background white\" } }\n      Paperclip::Attachment.default_options.merge!(@new_default_options)\n    end\n\n    after do\n      Paperclip::Attachment.default_options.merge!(@old_default_options)\n    end\n\n    it \"deep merges when it is overridden\" do\n      new_options = { convert_options: { thumb: \"-thumbnailize\" } }\n      attachment = Paperclip::Attachment.new(:name, :instance, new_options)\n\n      expect(Paperclip::Attachment.default_options.deep_merge(new_options)).to eq attachment.instance_variable_get(\"@options\")\n    end\n  end\n\n  it \"handles a boolean second argument to #url\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(\n      :name,\n      FakeModel.new,\n      url_generator: mock_url_generator_builder\n      )\n\n    attachment.url(:style_name, true)\n    expect(mock_url_generator_builder.has_generated_url_with_options?(timestamp: true, escape: true)).to eq true\n\n    attachment.url(:style_name, false)\n    expect(mock_url_generator_builder.has_generated_url_with_options?(timestamp: false, escape: true)).to eq true\n  end\n\n  it \"passes the style and options through to the URL generator on #url\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(\n      :name,\n      FakeModel.new,\n      url_generator: mock_url_generator_builder\n      )\n\n    attachment.url(:style_name, options: :values)\n    expect(mock_url_generator_builder.has_generated_url_with_options?(options: :values)).to eq true\n  end\n\n  it \"passes default options through when #url is given one argument\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(:name,\n                                           FakeModel.new,\n                                           url_generator: mock_url_generator_builder,\n                                           use_timestamp: true)\n\n    attachment.url(:style_name)\n    assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: true)\n  end\n\n  it \"passes default style and options through when #url is given no arguments\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(:name,\n                                           FakeModel.new,\n                                           default_style: 'default style',\n                                           url_generator: mock_url_generator_builder,\n                                           use_timestamp: true)\n\n    attachment.url\n    assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: true)\n    assert mock_url_generator_builder.has_generated_url_with_style_name?('default style')\n  end\n\n  it \"passes the option timestamp: true if :use_timestamp is true and :timestamp is not passed\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(:name,\n                                           FakeModel.new,\n                                           url_generator: mock_url_generator_builder,\n                                           use_timestamp: true)\n\n    attachment.url(:style_name)\n    assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: true)\n  end\n\n  it \"passes the option timestamp: false if :use_timestamp is false and :timestamp is not passed\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(:name,\n                                           FakeModel.new,\n                                           url_generator: mock_url_generator_builder,\n                                           use_timestamp: false)\n\n    attachment.url(:style_name)\n    assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: false)\n  end\n\n  it \"does not change the :timestamp if :timestamp is passed\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(:name,\n                                           FakeModel.new,\n                                           url_generator: mock_url_generator_builder,\n                                           use_timestamp: false)\n\n    attachment.url(:style_name, timestamp: true)\n    assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: true)\n  end\n\n  it \"renders JSON as default style\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(:name,\n                                           FakeModel.new,\n                                           default_style: 'default style',\n                                           url_generator: mock_url_generator_builder)\n\n    attachment.as_json\n    assert mock_url_generator_builder.has_generated_url_with_style_name?('default style')\n  end\n\n  it \"passes the option escape: true if :escape_url is true and :escape is not passed\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(:name,\n                                           FakeModel.new,\n                                           url_generator: mock_url_generator_builder,\n                                           escape_url: true)\n\n    attachment.url(:style_name)\n    assert mock_url_generator_builder.has_generated_url_with_options?(escape: true)\n  end\n\n  it \"passes the option escape: false if :escape_url is false and :escape is not passed\" do\n    mock_url_generator_builder = MockUrlGeneratorBuilder.new\n    attachment = Paperclip::Attachment.new(:name,\n                                           FakeModel.new,\n                                           url_generator: mock_url_generator_builder,\n                                           escape_url: false)\n\n    attachment.url(:style_name)\n    assert mock_url_generator_builder.has_generated_url_with_options?(escape: false)\n  end\n\n  it \"returns the path based on the url by default\" do\n    @attachment = attachment url: \"/:class/:id/:basename\"\n    @model = @attachment.instance\n    @model.id = 1234\n    @model.avatar_file_name = \"fake.jpg\"\n    assert_equal \"#{Rails.root}/public/fake_models/1234/fake\", @attachment.path\n  end\n\n  it \"defaults to a path that scales\" do\n    avatar_attachment = attachment\n    model = avatar_attachment.instance\n    model.id = 1234\n    model.avatar_file_name = \"fake.jpg\"\n    expected_path = \"#{Rails.root}/public/system/fake_models/avatars/000/001/234/original/fake.jpg\"\n    assert_equal expected_path, avatar_attachment.path\n  end\n\n  it \"renders JSON as the URL to the attachment\" do\n    avatar_attachment = attachment\n    model = avatar_attachment.instance\n    model.id = 1234\n    model.avatar_file_name = \"fake.jpg\"\n    assert_equal attachment.url, attachment.as_json\n  end\n\n  it \"renders JSON from the model when requested by :methods\" do\n    rebuild_model\n    dummy = Dummy.new\n    dummy.id = 1234\n    dummy.avatar_file_name = \"fake.jpg\"\n    dummy.stubs(:new_record?).returns(false)\n    expected_string = '{\"avatar\":\"/system/dummies/avatars/000/001/234/original/fake.jpg\"}'\n    # active_model pre-3.2 checks only by calling any? on it, thus it doesn't work if it is empty\n    assert_equal expected_string, dummy.to_json(only: [:dummy_key_for_old_active_model], methods: [:avatar])\n  end\n\n  context \"Attachment default_options\" do\n    before do\n      rebuild_model\n      @old_default_options = Paperclip::Attachment.default_options.dup\n      @new_default_options = @old_default_options.merge({\n        path: \"argle/bargle\",\n        url: \"fooferon\",\n        default_url: \"not here.png\"\n      })\n    end\n\n    after do\n      Paperclip::Attachment.default_options.merge! @old_default_options\n    end\n\n    it \"is overrideable\" do\n      Paperclip::Attachment.default_options.merge!(@new_default_options)\n      @new_default_options.keys.each do |key|\n        assert_equal @new_default_options[key],\n                     Paperclip::Attachment.default_options[key]\n      end\n    end\n\n    context \"without an Attachment\" do\n      before do\n        rebuild_model default_url: \"default.url\"\n        @dummy = Dummy.new\n      end\n\n      it \"returns false when asked exists?\" do\n        assert !@dummy.avatar.exists?\n      end\n\n      it \"#url returns the default_url\" do\n        expect(@dummy.avatar.url).to eq \"default.url\"\n      end\n    end\n\n    context \"on an Attachment\" do\n      before do\n        @dummy = Dummy.new\n        @attachment = @dummy.avatar\n      end\n\n      Paperclip::Attachment.default_options.keys.each do |key|\n        it \"is the default_options for #{key}\" do\n          assert_equal @old_default_options[key],\n                       @attachment.instance_variable_get(\"@options\")[key],\n                       key.to_s\n        end\n      end\n\n      context \"when redefined\" do\n        before do\n          Paperclip::Attachment.default_options.merge!(@new_default_options)\n          @dummy = Dummy.new\n          @attachment = @dummy.avatar\n        end\n\n        Paperclip::Attachment.default_options.keys.each do |key|\n          it \"is the new default_options for #{key}\" do\n            assert_equal @new_default_options[key],\n                         @attachment.instance_variable_get(\"@options\")[key],\n                         key.to_s\n          end\n        end\n      end\n    end\n  end\n\n  context \"An attachment with similarly named interpolations\" do\n    before do\n      rebuild_model path: \":id.omg/:id-bbq/:idwhat/:id_partition.wtf\"\n      @dummy = Dummy.new\n      @dummy.stubs(:id).returns(1024)\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummy.avatar = @file\n    end\n\n    after { @file.close }\n\n    it \"makes sure that they are interpolated correctly\" do\n      assert_equal \"1024.omg/1024-bbq/1024what/000/001/024.wtf\", @dummy.avatar.path\n    end\n  end\n\n  context \"An attachment with :timestamp interpolations\" do\n    before do\n      @file = StringIO.new(\"...\")\n      @zone = 'UTC'\n      Time.stubs(:zone).returns(@zone)\n      @zone_default = 'Eastern Time (US & Canada)'\n      Time.stubs(:zone_default).returns(@zone_default)\n    end\n\n    context \"using default time zone\" do\n      before do\n        rebuild_model path: \":timestamp\", use_default_time_zone: true\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      it \"returns a time in the default zone\" do\n        assert_equal @dummy.avatar_updated_at.in_time_zone(@zone_default).to_s, @dummy.avatar.path\n      end\n    end\n\n    context \"using per-thread time zone\" do\n      before do\n        rebuild_model path: \":timestamp\", use_default_time_zone: false\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      it \"returns a time in the per-thread zone\" do\n        assert_equal @dummy.avatar_updated_at.in_time_zone(@zone).to_s, @dummy.avatar.path\n      end\n    end\n  end\n\n  context \"An attachment with :hash interpolations\" do\n    before do\n      @file = File.open(fixture_file(\"5k.png\"))\n    end\n\n    after do\n      @file.close\n    end\n\n    it \"raises if no secret is provided\" do\n      rebuild_model path: \":hash\"\n      @attachment = Dummy.new.avatar\n      @attachment.assign @file\n\n      assert_raises ArgumentError do\n        @attachment.path\n      end\n    end\n\n    context \"when secret is set\" do\n      before do\n        rebuild_model path: \":hash\",\n          hash_secret: \"w00t\",\n          hash_data: \":class/:attachment/:style/:filename\"\n        @attachment = Dummy.new.avatar\n        @attachment.assign @file\n      end\n\n      it \"results in the correct interpolation\" do\n        assert_equal \"dummies/avatars/original/5k.png\",\n          @attachment.send(:interpolate, @attachment.options[:hash_data])\n        assert_equal \"dummies/avatars/thumb/5k.png\",\n          @attachment.send(:interpolate, @attachment.options[:hash_data], :thumb)\n      end\n\n      it \"results in a correct hash\" do\n        assert_equal \"0a59e9142bba11576de1d353d8747b1acad5ad34\", @attachment.path\n        assert_equal \"b39a062c1e62e85a6c785ed00cf3bebf5f850e2b\", @attachment.path(:thumb)\n      end\n    end\n  end\n\n  context \"An attachment with a :rails_env interpolation\" do\n    before do\n      @rails_env = \"blah\"\n      @id = 1024\n      rebuild_model path: \":rails_env/:id.png\"\n      @dummy = Dummy.new\n      @dummy.stubs(:id).returns(@id)\n      @file = StringIO.new(\".\")\n      @dummy.avatar = @file\n      Rails.stubs(:env).returns(@rails_env)\n    end\n\n    it \"returns the proper path\" do\n      assert_equal \"#{@rails_env}/#{@id}.png\", @dummy.avatar.path\n    end\n  end\n\n  context \"An attachment with a default style and an extension interpolation\" do\n    before do\n      rebuild_model path: \":basename.:extension\",\n        styles: { default: [\"100x100\", :jpg] },\n        default_style: :default\n      @attachment = Dummy.new.avatar\n      @file = File.open(fixture_file(\"5k.png\"))\n      @file.stubs(:original_filename).returns(\"file.png\")\n    end\n    it \"returns the right extension for the path\" do\n      @attachment.assign(@file)\n      assert_equal \"file.jpg\", @attachment.path\n    end\n  end\n\n  context \"An attachment with :convert_options\" do\n    before do\n      rebuild_model styles: {\n                      thumb: \"100x100\",\n                      large: \"400x400\"\n                    },\n                    convert_options: {\n                      all: \"-do_stuff\",\n                      thumb: \"-thumbnailize\"\n                    }\n      @dummy = Dummy.new\n      @dummy.avatar\n    end\n\n    it \"reports the correct options when sent #extra_options_for(:thumb)\" do\n      assert_equal \"-thumbnailize -do_stuff\", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect\n    end\n\n    it \"reports the correct options when sent #extra_options_for(:large)\" do\n      assert_equal \"-do_stuff\", @dummy.avatar.send(:extra_options_for, :large)\n    end\n  end\n\n  context \"An attachment with :source_file_options\" do\n    before do\n      rebuild_model styles: {\n                      thumb: \"100x100\",\n                      large: \"400x400\"\n                    },\n                    source_file_options: {\n                      all: \"-density 400\",\n                      thumb: \"-depth 8\"\n                    }\n      @dummy = Dummy.new\n      @dummy.avatar\n    end\n\n    it \"reports the correct options when sent #extra_source_file_options_for(:thumb)\" do\n      assert_equal \"-depth 8 -density 400\", @dummy.avatar.send(:extra_source_file_options_for, :thumb), @dummy.avatar.source_file_options.inspect\n    end\n\n    it \"reports the correct options when sent #extra_source_file_options_for(:large)\" do\n      assert_equal \"-density 400\", @dummy.avatar.send(:extra_source_file_options_for, :large)\n    end\n  end\n\n  context \"An attachment with :only_process\" do\n    before do\n      rebuild_model styles: {\n                      thumb: \"100x100\",\n                      large: \"400x400\"\n                    },\n                    only_process: [:thumb]\n      @file = StringIO.new(\"...\")\n      @attachment = Dummy.new.avatar\n    end\n\n    it \"only processes the provided style\" do\n      @attachment.expects(:post_process).with(:thumb)\n      @attachment.expects(:post_process).with(:large).never\n      @attachment.assign(@file)\n    end\n  end\n\n  context \"An attachment with :only_process that is a proc\" do\n    before do\n      rebuild_model styles: {\n                      thumb: \"100x100\",\n                      large: \"400x400\"\n                    },\n                    only_process: lambda { |attachment| [:thumb] }\n\n      @file = StringIO.new(\"...\")\n      @attachment = Dummy.new.avatar\n    end\n\n    it \"only processes the provided style\" do\n      @attachment.expects(:post_process).with(:thumb)\n      @attachment.expects(:post_process).with(:large).never\n      @attachment.assign(@file)\n      @attachment.save\n    end\n  end\n\n  context \"An attachment with :convert_options that is a proc\" do\n    before do\n      rebuild_model styles: {\n                      thumb: \"100x100\",\n                      large: \"400x400\"\n                    },\n                    convert_options: {\n                      all: lambda{|i| i.all },\n                      thumb: lambda{|i| i.thumb }\n                    }\n      Dummy.class_eval do\n        def all;   \"-all\";   end\n        def thumb; \"-thumb\"; end\n      end\n      @dummy = Dummy.new\n      @dummy.avatar\n    end\n\n    it \"reports the correct options when sent #extra_options_for(:thumb)\" do\n      assert_equal \"-thumb -all\", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect\n    end\n\n    it \"reports the correct options when sent #extra_options_for(:large)\" do\n      assert_equal \"-all\", @dummy.avatar.send(:extra_options_for, :large)\n    end\n  end\n\n  context \"An attachment with :path that is a proc\" do\n    before do\n      rebuild_model path: lambda{ |attachment| \"path/#{attachment.instance.other}.:extension\" }\n\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummyA = Dummy.new(other: 'a')\n      @dummyA.avatar = @file\n      @dummyB = Dummy.new(other: 'b')\n      @dummyB.avatar = @file\n    end\n\n    after { @file.close }\n\n    it \"returns correct path\" do\n      assert_equal \"path/a.png\", @dummyA.avatar.path\n      assert_equal \"path/b.png\", @dummyB.avatar.path\n    end\n  end\n\n  context \"An attachment with :styles that is a proc\" do\n    before do\n      rebuild_model styles: lambda{ |attachment| {thumb: \"50x50#\", large: \"400x400\"} }\n\n      @attachment = Dummy.new.avatar\n    end\n\n    it \"has the correct geometry\" do\n      assert_equal \"50x50#\", @attachment.styles[:thumb][:geometry]\n    end\n  end\n\n  context \"An attachment with conditional :styles that is a proc\" do\n    before do\n      rebuild_model styles: lambda{ |attachment| attachment.instance.other == 'a' ? {thumb: \"50x50#\"} : {large: \"400x400\"} }\n\n      @dummy = Dummy.new(other: 'a')\n    end\n\n    it \"has the correct styles for the assigned instance values\" do\n      assert_equal \"50x50#\", @dummy.avatar.styles[:thumb][:geometry]\n      assert_nil @dummy.avatar.styles[:large]\n\n      @dummy.other = 'b'\n\n      assert_equal \"400x400\", @dummy.avatar.styles[:large][:geometry]\n      assert_nil @dummy.avatar.styles[:thumb]\n    end\n  end\n\n  geometry_specs = [\n    [ lambda{|z| \"50x50#\" }, :png ],\n    lambda{|z| \"50x50#\" },\n    { geometry: lambda{|z| \"50x50#\" } }\n  ]\n  geometry_specs.each do |geometry_spec|\n    context \"An attachment geometry like #{geometry_spec}\" do\n      before do\n        rebuild_model styles: { normal: geometry_spec }\n        @attachment = Dummy.new.avatar\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = StringIO.new(\".\")\n          @attachment.assign(@file)\n        end\n\n        it \"has the correct geometry\" do\n          assert_equal \"50x50#\", @attachment.styles[:normal][:geometry]\n        end\n      end\n    end\n  end\n\n  context \"An attachment with both 'normal' and hash-style styles\" do\n    before do\n      rebuild_model styles: {\n                      normal: [\"50x50#\", :png],\n                      hash: { geometry: \"50x50#\", format: :png }\n                    }\n      @dummy = Dummy.new\n      @attachment = @dummy.avatar\n    end\n\n    [:processors, :whiny, :convert_options, :geometry, :format].each do |field|\n      it \"has the same #{field} field\" do\n        assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]\n      end\n    end\n  end\n\n  context \"An attachment with :processors that is a proc\" do\n    before do\n      class Paperclip::Test < Paperclip::Processor; end\n      @file = StringIO.new(\"...\")\n      Paperclip::Test.stubs(:make).returns(@file)\n\n      rebuild_model styles: { normal: '' }, processors: lambda { |a| [ :test ] }\n      @attachment = Dummy.new.avatar\n    end\n\n    context \"when assigned\" do\n      before do\n        @attachment.assign(StringIO.new(\".\"))\n      end\n\n      it \"has the correct processors\" do\n        assert_equal [ :test ], @attachment.styles[:normal][:processors]\n      end\n    end\n  end\n\n  context \"An attachment with erroring processor\" do\n    before do\n      rebuild_model processor: [:thumbnail], styles: { small: '' }, whiny_thumbnails: true\n      @dummy = Dummy.new\n      @file = StringIO.new(\"...\")\n      @file.stubs(:to_tempfile).returns(@file)\n    end\n\n    context \"when error is meaningful for the end user\" do\n      before do\n        Paperclip::Thumbnail.expects(:make).raises(\n          Paperclip::Errors::NotIdentifiedByImageMagickError,\n          \"cannot be processed.\"\n        )\n      end\n\n      it \"correctly forwards processing error message to the instance\" do\n        @dummy.avatar = @file\n        @dummy.valid?\n        assert_contains(\n          @dummy.errors.full_messages,\n          \"Avatar cannot be processed.\"\n        )\n      end\n    end\n\n    context \"when error is intended for the developer\" do\n      before do\n        Paperclip::Thumbnail.expects(:make).raises(\n          Paperclip::Errors::CommandNotFoundError\n        )\n      end\n\n      it \"propagates the error\" do\n        assert_raises(Paperclip::Errors::CommandNotFoundError) do\n          @dummy.avatar = @file\n        end\n      end\n    end\n  end\n\n  context \"An attachment with multiple processors\" do\n    before do\n      class Paperclip::Test < Paperclip::Processor; end\n      @style_params = { once: {one: 1, two: 2} }\n      rebuild_model processors: [:thumbnail, :test], styles: @style_params\n      @dummy = Dummy.new\n      @file = StringIO.new(\"...\")\n      @file.stubs(:close)\n      Paperclip::Test.stubs(:make).returns(@file)\n      Paperclip::Thumbnail.stubs(:make).returns(@file)\n    end\n\n    context \"when assigned\" do\n      it \"calls #make on all specified processors\" do\n        @dummy.avatar = @file\n\n        expect(Paperclip::Thumbnail).to have_received(:make)\n        expect(Paperclip::Test).to have_received(:make)\n      end\n\n      it \"calls #make with the right parameters passed as second argument\" do\n        expected_params = @style_params[:once].merge({\n          style: :once,\n          processors: [:thumbnail, :test],\n          whiny: true,\n          convert_options: \"\",\n          source_file_options: \"\"\n        })\n\n        @dummy.avatar = @file\n\n        expect(Paperclip::Thumbnail).to have_received(:make).with(anything, expected_params, anything)\n      end\n\n      it \"calls #make with attachment passed as third argument\" do\n        @dummy.avatar = @file\n\n        expect(Paperclip::Test).to have_received(:make).with(anything, anything, @dummy.avatar)\n      end\n\n      it \"calls #make and unlinks intermediary files afterward\" do\n        @dummy.avatar.expects(:unlink_files).with([@file, @file])\n\n        @dummy.avatar = @file\n      end\n    end\n  end\n\n  context \"An attachment with a processor that returns original file\" do\n    before do\n      class Paperclip::Test < Paperclip::Processor\n        def make; @file; end\n      end\n      rebuild_model processors: [:test], styles: { once: \"100x100\" }\n      @file = StringIO.new(\"...\")\n      @file.stubs(:close)\n      @dummy = Dummy.new\n    end\n\n    context \"when assigned\" do\n      it \"#calls #make and doesn't unlink the original file\" do\n        @dummy.avatar.expects(:unlink_files).with([])\n\n        @dummy.avatar = @file\n      end\n    end\n  end\n\n  it \"includes the filesystem module when loading the filesystem storage\" do\n    rebuild_model storage: :filesystem\n    @dummy = Dummy.new\n    assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)\n  end\n\n  it \"includes the filesystem module even if capitalization is wrong\" do\n    rebuild_model storage: :FileSystem\n    @dummy = Dummy.new\n    assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)\n\n    rebuild_model storage: :Filesystem\n    @dummy = Dummy.new\n    assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)\n  end\n\n  it \"converts underscored storage name to camelcase\" do\n    rebuild_model storage: :not_here\n    @dummy = Dummy.new\n    exception = assert_raises(Paperclip::Errors::StorageMethodNotFound, /NotHere/) do\n      @dummy.avatar\n    end\n  end\n\n  it \"raises an error if you try to include a storage module that doesn't exist\" do\n    rebuild_model storage: :not_here\n    @dummy = Dummy.new\n    assert_raises(Paperclip::Errors::StorageMethodNotFound) do\n      @dummy.avatar\n    end\n  end\n\n  context \"An attachment with styles but no processors defined\" do\n    before do\n      rebuild_model processors: [], styles: {something: '1'}\n      @dummy = Dummy.new\n      @file = StringIO.new(\"...\")\n    end\n    it \"raises when assigned to\" do\n      assert_raises(RuntimeError){ @dummy.avatar = @file }\n    end\n  end\n\n  context \"An attachment without styles and with no processors defined\" do\n    before do\n      rebuild_model processors: [], styles: {}\n      @dummy = Dummy.new\n      @file = StringIO.new(\"...\")\n    end\n    it \"does not raise when assigned to\" do\n      @dummy.avatar = @file\n    end\n  end\n\n  context \"Assigning an attachment with post_process hooks\" do\n    before do\n      rebuild_class styles: { something: \"100x100#\" }\n      Dummy.class_eval do\n        before_avatar_post_process :do_before_avatar\n        after_avatar_post_process :do_after_avatar\n        before_post_process :do_before_all\n        after_post_process :do_after_all\n        def do_before_avatar; end\n        def do_after_avatar; end\n        def do_before_all; end\n        def do_after_all; end\n      end\n      @file  = StringIO.new(\".\")\n      @file.stubs(:to_tempfile).returns(@file)\n      @dummy = Dummy.new\n      Paperclip::Thumbnail.stubs(:make).returns(@file)\n      @attachment = @dummy.avatar\n    end\n\n    it \"calls the defined callbacks when assigned\" do\n      @dummy.expects(:do_before_avatar).with()\n      @dummy.expects(:do_after_avatar).with()\n      @dummy.expects(:do_before_all).with()\n      @dummy.expects(:do_after_all).with()\n      Paperclip::Thumbnail.expects(:make).returns(@file)\n      @dummy.avatar = @file\n    end\n\n    it \"does not cancel the processing if a before_post_process returns nil\" do\n      @dummy.expects(:do_before_avatar).with().returns(nil)\n      @dummy.expects(:do_after_avatar).with()\n      @dummy.expects(:do_before_all).with().returns(nil)\n      @dummy.expects(:do_after_all).with()\n      Paperclip::Thumbnail.expects(:make).returns(@file)\n      @dummy.avatar = @file\n    end\n\n    it \"cancels the processing if a before_post_process returns false\" do\n      @dummy.expects(:do_before_avatar).never\n      @dummy.expects(:do_after_avatar).never\n      @dummy.expects(:do_before_all).with().returns(false)\n      @dummy.expects(:do_after_all)\n      Paperclip::Thumbnail.expects(:make).never\n      @dummy.avatar = @file\n    end\n\n    it \"cancels the processing if a before_avatar_post_process returns false\" do\n      @dummy.expects(:do_before_avatar).with().returns(false)\n      @dummy.expects(:do_after_avatar)\n      @dummy.expects(:do_before_all).with().returns(true)\n      @dummy.expects(:do_after_all)\n      Paperclip::Thumbnail.expects(:make).never\n      @dummy.avatar = @file\n    end\n  end\n\n  context \"Assigning an attachment\" do\n    before do\n      rebuild_model styles: { something: \"100x100#\" }\n      @file = File.new(fixture_file(\"5k.png\"), \"rb\")\n      @dummy = Dummy.new\n      @dummy.avatar = @file\n    end\n\n    it \"strips whitespace from original_filename field\" do\n      assert_equal \"5k.png\", @dummy.avatar.original_filename\n    end\n\n    it \"strips whitespace from content_type field\" do\n      assert_equal \"image/png\", @dummy.avatar.instance.avatar_content_type\n    end\n  end\n\n  context \"Assigning an attachment\" do\n    before do\n      rebuild_model styles: { something: \"100x100#\" }\n      @file = File.new(fixture_file(\"5k.png\"), \"rb\")\n      @dummy = Dummy.new\n      @dummy.avatar = @file\n    end\n\n    it \"makes sure the content_type is a string\" do\n      assert_equal \"image/png\", @dummy.avatar.instance.avatar_content_type\n    end\n  end\n\n  context \"Attachment with strange letters\" do\n    before do\n      rebuild_model\n      @file = File.new(fixture_file(\"5k.png\"), \"rb\")\n      @file.stubs(:original_filename).returns(\"sheep_say_bæ.png\")\n      @dummy = Dummy.new\n      @dummy.avatar = @file\n    end\n\n    it \"does not remove strange letters\" do\n      assert_equal \"sheep_say_bæ.png\", @dummy.avatar.original_filename\n    end\n  end\n\n  context \"Attachment with reserved filename\" do\n    before do\n      rebuild_model\n      @file = Tempfile.new([\"filename\",\"png\"])\n    end\n\n    after do\n      @file.unlink\n    end\n\n    context \"with default configuration\" do\n      \"&$+,/:;=?@<>[]{}|\\^~%# \".split(//).each do |character|\n        context \"with character #{character}\" do\n\n          context \"at beginning of filename\" do\n            before do\n              @file.stubs(:original_filename).returns(\"#{character}filename.png\")\n              @dummy = Dummy.new\n              @dummy.avatar = @file\n            end\n\n            it \"converts special character into underscore\" do\n              assert_equal \"_filename.png\", @dummy.avatar.original_filename\n            end\n          end\n\n          context \"at end of filename\" do\n            before do\n              @file.stubs(:original_filename).returns(\"filename.png#{character}\")\n              @dummy = Dummy.new\n              @dummy.avatar = @file\n            end\n\n            it \"converts special character into underscore\" do\n              assert_equal \"filename.png_\", @dummy.avatar.original_filename\n            end\n          end\n\n          context \"in the middle of filename\" do\n            before do\n              @file.stubs(:original_filename).returns(\"file#{character}name.png\")\n              @dummy = Dummy.new\n              @dummy.avatar = @file\n            end\n\n            it \"converts special character into underscore\" do\n              assert_equal \"file_name.png\", @dummy.avatar.original_filename\n            end\n          end\n\n        end\n      end\n    end\n\n    context \"with specified regexp replacement\" do\n      before do\n        @old_defaults = Paperclip::Attachment.default_options.dup\n      end\n\n      after do\n        Paperclip::Attachment.default_options.merge! @old_defaults\n      end\n\n      context 'as another regexp' do\n        before do\n          Paperclip::Attachment.default_options.merge! restricted_characters: /o/\n\n          @file.stubs(:original_filename).returns(\"goood.png\")\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        it \"matches and converts that character\" do\n          assert_equal \"g___d.png\", @dummy.avatar.original_filename\n        end\n      end\n\n      context 'as nil' do\n        before do\n          Paperclip::Attachment.default_options.merge! restricted_characters: nil\n\n          @file.stubs(:original_filename).returns(\"goood.png\")\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        it \"ignores and returns the original file name\" do\n          assert_equal \"goood.png\", @dummy.avatar.original_filename\n        end\n      end\n    end\n\n    context 'with specified cleaner' do\n      before do\n        @old_defaults = Paperclip::Attachment.default_options.dup\n      end\n\n      after do\n        Paperclip::Attachment.default_options.merge! @old_defaults\n      end\n\n      it 'calls the given proc and take the result as cleaned filename' do\n        Paperclip::Attachment.default_options[:filename_cleaner] = lambda do |str|\n          \"from_proc_#{str}\"\n        end\n\n        @file.stubs(:original_filename).returns(\"goood.png\")\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n        assert_equal \"from_proc_goood.png\", @dummy.avatar.original_filename\n      end\n\n      it 'calls the given object and take the result as the cleaned filename' do\n        class MyCleaner\n          def call(filename)\n            \"foo\"\n          end\n        end\n        Paperclip::Attachment.default_options[:filename_cleaner] = MyCleaner.new\n\n        @file.stubs(:original_filename).returns(\"goood.png\")\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n        assert_equal \"foo\", @dummy.avatar.original_filename\n      end\n    end\n  end\n\n  context \"Attachment with uppercase extension and a default style\" do\n    before do\n      @old_defaults = Paperclip::Attachment.default_options.dup\n      Paperclip::Attachment.default_options.merge!({\n        path: \":rails_root/:attachment/:class/:style/:id/:basename.:extension\"\n      })\n      FileUtils.rm_rf(\"tmp\")\n      rebuild_model styles: { large: [\"400x400\", :jpg],\n                             medium: [\"100x100\", :jpg],\n                             small: [\"32x32#\", :jpg]},\n                    default_style: :small\n      @instance = Dummy.new\n      @instance.stubs(:id).returns 123\n      @file = File.new(fixture_file(\"uppercase.PNG\"), 'rb')\n\n      @attachment = @instance.avatar\n\n      now = Time.now\n      Time.stubs(:now).returns(now)\n      @attachment.assign(@file)\n      @attachment.save\n    end\n\n    after do\n      @file.close\n      Paperclip::Attachment.default_options.merge!(@old_defaults)\n    end\n\n    it \"has matching to_s and url methods\" do\n      assert_equal @attachment.to_s, @attachment.url\n      assert_equal @attachment.to_s(:small), @attachment.url(:small)\n    end\n\n    it \"has matching expiring_url and url methods when using the filesystem storage\" do\n      assert_equal @attachment.expiring_url, @attachment.url\n    end\n  end\n\n  context \"An attachment\" do\n    before do\n      @old_defaults = Paperclip::Attachment.default_options.dup\n      Paperclip::Attachment.default_options.merge!({\n        path: \":rails_root/:attachment/:class/:style/:id/:basename.:extension\"\n      })\n      FileUtils.rm_rf(\"tmp\")\n      rebuild_model\n      @instance = Dummy.new\n      @instance.stubs(:id).returns 123\n      # @attachment = Paperclip::Attachment.new(:avatar, @instance)\n      @attachment = @instance.avatar\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n    end\n\n    after do\n      @file.close\n      Paperclip::Attachment.default_options.merge!(@old_defaults)\n    end\n\n    it \"raises if there are not the correct columns when you try to assign\" do\n      @other_attachment = Paperclip::Attachment.new(:not_here, @instance)\n      assert_raises(Paperclip::Error) do\n        @other_attachment.assign(@file)\n      end\n    end\n\n    it 'clears out the previous assignment when assigned nil' do\n      @attachment.assign(@file)\n      @attachment.queued_for_write[:original]\n      @attachment.assign(nil)\n      assert_nil @attachment.queued_for_write[:original]\n    end\n\n    it 'does not do anything when it is assigned an empty string' do\n      @attachment.assign(@file)\n      original_file = @attachment.queued_for_write[:original]\n      @attachment.assign(\"\")\n      assert_equal original_file, @attachment.queued_for_write[:original]\n    end\n\n    it \"returns nil as path when no file assigned\" do\n      assert_equal nil, @attachment.path\n      assert_equal nil, @attachment.path(:blah)\n    end\n\n    context \"with a file assigned but not saved yet\" do\n      it \"clears out any attached files\" do\n        @attachment.assign(@file)\n        assert @attachment.queued_for_write.present?\n        @attachment.clear\n        assert @attachment.queued_for_write.blank?\n      end\n    end\n\n    context \"with a file assigned in the database\" do\n      before do\n        @attachment.stubs(:instance_read).with(:file_name).returns(\"5k.png\")\n        @attachment.stubs(:instance_read).with(:content_type).returns(\"image/png\")\n        @attachment.stubs(:instance_read).with(:file_size).returns(12345)\n        dtnow = DateTime.now\n        @now = Time.now\n        Time.stubs(:now).returns(@now)\n        @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)\n      end\n\n      it \"returns the proper path when filename has a single .'s\" do\n        assert_equal File.expand_path(\"tmp/avatars/dummies/original/#{@instance.id}/5k.png\"), File.expand_path(@attachment.path)\n      end\n\n      it \"returns the proper path when filename has multiple .'s\" do\n        @attachment.stubs(:instance_read).with(:file_name).returns(\"5k.old.png\")\n        assert_equal File.expand_path(\"tmp/avatars/dummies/original/#{@instance.id}/5k.old.png\"), File.expand_path(@attachment.path)\n      end\n\n      context \"when expecting three styles\" do\n        before do\n          rebuild_class styles: {\n            large: [\"400x400\", :png],\n            medium: [\"100x100\", :gif],\n            small: [\"32x32#\", :jpg]\n          }\n          @instance = Dummy.new\n          @instance.stubs(:id).returns 123\n          @file = File.new(fixture_file(\"5k.png\"), 'rb')\n          @attachment = @instance.avatar\n        end\n\n        context \"and assigned a file\" do\n          before do\n            now = Time.now\n            Time.stubs(:now).returns(now)\n            @attachment.assign(@file)\n          end\n\n          it \"is dirty\" do\n            assert @attachment.dirty?\n          end\n\n          context \"and saved\" do\n            before do\n              @attachment.save\n            end\n\n            it \"commits the files to disk\" do\n              [:large, :medium, :small].each do |style|\n                expect(@attachment.path(style)).to exist\n              end\n            end\n\n            it \"saves the files as the right formats and sizes\" do\n              [[:large, 400, 61, \"PNG\"],\n               [:medium, 100, 15, \"GIF\"],\n               [:small, 32, 32, \"JPEG\"]].each do |style|\n                cmd = %Q[identify -format \"%w %h %b %m\" \"#{@attachment.path(style.first)}\"]\n                out = `#{cmd}`\n                width, height, _size, format = out.split(\" \")\n                assert_equal style[1].to_s, width.to_s\n                assert_equal style[2].to_s, height.to_s\n                assert_equal style[3].to_s, format.to_s\n              end\n            end\n\n            context \"and trying to delete\" do\n              before do\n                @existing_names = @attachment.styles.keys.collect do |style|\n                  @attachment.path(style)\n                end\n              end\n\n              it \"deletes the files after assigning nil\" do\n                @attachment.expects(:instance_write).with(:file_name, nil)\n                @attachment.expects(:instance_write).with(:content_type, nil)\n                @attachment.expects(:instance_write).with(:file_size, nil)\n                @attachment.expects(:instance_write).with(:fingerprint, nil)\n                @attachment.expects(:instance_write).with(:updated_at, nil)\n                @attachment.assign nil\n                @attachment.save\n                @existing_names.each{|f| assert_file_not_exists(f) }\n              end\n\n              it \"deletes the files when you call #clear and #save\" do\n                @attachment.expects(:instance_write).with(:file_name, nil)\n                @attachment.expects(:instance_write).with(:content_type, nil)\n                @attachment.expects(:instance_write).with(:file_size, nil)\n                @attachment.expects(:instance_write).with(:fingerprint, nil)\n                @attachment.expects(:instance_write).with(:updated_at, nil)\n                @attachment.clear\n                @attachment.save\n                @existing_names.each{|f| assert_file_not_exists(f) }\n              end\n\n              it \"deletes the files when you call #delete\" do\n                @attachment.expects(:instance_write).with(:file_name, nil)\n                @attachment.expects(:instance_write).with(:content_type, nil)\n                @attachment.expects(:instance_write).with(:file_size, nil)\n                @attachment.expects(:instance_write).with(:fingerprint, nil)\n                @attachment.expects(:instance_write).with(:updated_at, nil)\n                @attachment.destroy\n                @existing_names.each{|f| assert_file_not_exists(f) }\n              end\n\n              context \"when keeping old files\" do\n                before do\n                  @attachment.options[:keep_old_files] = true\n                end\n\n                it \"keeps the files after assigning nil\" do\n                  @attachment.expects(:instance_write).with(:file_name, nil)\n                  @attachment.expects(:instance_write).with(:content_type, nil)\n                  @attachment.expects(:instance_write).with(:file_size, nil)\n                  @attachment.expects(:instance_write).with(:fingerprint, nil)\n                  @attachment.expects(:instance_write).with(:updated_at, nil)\n                  @attachment.assign nil\n                  @attachment.save\n                  @existing_names.each{|f| assert_file_exists(f) }\n                end\n\n                it \"keeps the files when you call #clear and #save\" do\n                  @attachment.expects(:instance_write).with(:file_name, nil)\n                  @attachment.expects(:instance_write).with(:content_type, nil)\n                  @attachment.expects(:instance_write).with(:file_size, nil)\n                  @attachment.expects(:instance_write).with(:fingerprint, nil)\n                  @attachment.expects(:instance_write).with(:updated_at, nil)\n                  @attachment.clear\n                  @attachment.save\n                  @existing_names.each{|f| assert_file_exists(f) }\n                end\n\n                it \"keeps the files when you call #delete\" do\n                  @attachment.expects(:instance_write).with(:file_name, nil)\n                  @attachment.expects(:instance_write).with(:content_type, nil)\n                  @attachment.expects(:instance_write).with(:file_size, nil)\n                  @attachment.expects(:instance_write).with(:fingerprint, nil)\n                  @attachment.expects(:instance_write).with(:updated_at, nil)\n                  @attachment.destroy\n                  @existing_names.each{|f| assert_file_exists(f) }\n                end\n              end\n            end\n          end\n        end\n      end\n    end\n\n    context \"when trying a nonexistant storage type\" do\n      before do\n        rebuild_model storage: :not_here\n      end\n\n      it \"is not able to find the module\" do\n        assert_raises(Paperclip::Errors::StorageMethodNotFound){ Dummy.new.avatar }\n      end\n    end\n  end\n\n  context \"An attachment with only a avatar_file_name column\" do\n    before do\n      ActiveRecord::Base.connection.create_table :dummies, force: true do |table|\n        table.column :avatar_file_name, :string\n      end\n      rebuild_class\n      @dummy = Dummy.new\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n    end\n\n    after { @file.close }\n\n    it \"does not error when assigned an attachment\" do\n      assert_nothing_raised { @dummy.avatar = @file }\n    end\n\n    it \"does not return the time when sent #avatar_updated_at\" do\n      @dummy.avatar = @file\n      assert_nil @dummy.avatar.updated_at\n    end\n\n    it \"returns the right value when sent #avatar_file_size\" do\n      @dummy.avatar = @file\n      assert_equal File.size(@file), @dummy.avatar.size\n    end\n\n    context \"and avatar_created_at column\" do\n      before do\n        ActiveRecord::Base.connection.add_column :dummies, :avatar_created_at, :timestamp\n        rebuild_class\n        @dummy = Dummy.new\n      end\n\n      it \"does not error when assigned an attachment\" do\n        assert_nothing_raised { @dummy.avatar = @file }\n      end\n\n      it \"returns the creation time when sent #avatar_created_at\" do\n        now = Time.now\n        Time.stubs(:now).returns(now)\n        @dummy.avatar = @file\n        assert_equal now.to_i, @dummy.avatar.created_at\n      end\n\n      it \"returns the creation time when sent #avatar_created_at and the entry has been updated\" do\n        creation = 2.hours.ago\n        now = Time.now\n        Time.stubs(:now).returns(creation)\n        @dummy.avatar = @file\n        Time.stubs(:now).returns(now)\n        @dummy.avatar = @file\n        assert_equal creation.to_i, @dummy.avatar.created_at\n        assert_not_equal now.to_i, @dummy.avatar.created_at\n      end\n\n      it \"sets changed? to true on attachment assignment\" do\n        @dummy.avatar = @file\n        @dummy.save!\n        @dummy.avatar = @file\n        assert @dummy.changed?\n      end\n    end\n\n    context \"and avatar_updated_at column\" do\n      before do\n        ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp\n        rebuild_class\n        @dummy = Dummy.new\n      end\n\n      it \"does not error when assigned an attachment\" do\n        assert_nothing_raised { @dummy.avatar = @file }\n      end\n\n      it \"returns the right value when sent #avatar_updated_at\" do\n        now = Time.now\n        Time.stubs(:now).returns(now)\n        @dummy.avatar = @file\n        assert_equal now.to_i, @dummy.avatar.updated_at\n      end\n    end\n\n    it \"does not calculate fingerprint\" do\n      Digest::MD5.stubs(:file)\n      @dummy.avatar = @file\n      expect(Digest::MD5).to have_received(:file).never\n    end\n\n    it \"does not assign fingerprint\" do\n      @dummy.avatar = @file\n      assert_nil @dummy.avatar.fingerprint\n    end\n\n    context \"and avatar_content_type column\" do\n      before do\n        ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string\n        rebuild_class\n        @dummy = Dummy.new\n      end\n\n      it \"does not error when assigned an attachment\" do\n        assert_nothing_raised { @dummy.avatar = @file }\n      end\n\n      it \"returns the right value when sent #avatar_content_type\" do\n        @dummy.avatar = @file\n        assert_equal \"image/png\", @dummy.avatar.content_type\n      end\n    end\n\n    context \"and avatar_file_size column\" do\n      before do\n        ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :bigint\n        rebuild_class\n        @dummy = Dummy.new\n      end\n\n      it \"does not error when assigned an attachment\" do\n        assert_nothing_raised { @dummy.avatar = @file }\n      end\n\n      it \"returns the right value when sent #avatar_file_size\" do\n        @dummy.avatar = @file\n        assert_equal File.size(@file), @dummy.avatar.size\n      end\n\n      it \"returns the right value when saved, reloaded, and sent #avatar_file_size\" do\n        @dummy.avatar = @file\n        @dummy.save\n        @dummy = Dummy.find(@dummy.id)\n        assert_equal File.size(@file), @dummy.avatar.size\n      end\n    end\n\n    context \"and avatar_fingerprint column\" do\n      before do\n        ActiveRecord::Base.connection.add_column :dummies, :avatar_fingerprint, :string\n        rebuild_class\n        @dummy = Dummy.new\n      end\n\n      it \"does not error when assigned an attachment\" do\n        assert_nothing_raised { @dummy.avatar = @file }\n      end\n\n      context \"with explicitly set digest\" do\n        before do\n          rebuild_class adapter_options: { hash_digest: Digest::SHA256 }\n          @dummy = Dummy.new\n        end\n\n        it \"returns the right value when sent #avatar_fingerprint\" do\n          @dummy.avatar = @file\n          assert_equal \"734016d801a497f5579cdd4ef2ae1d020088c1db754dc434482d76dd5486520a\",\n                       @dummy.avatar_fingerprint\n        end\n\n        it \"returns the right value when saved, reloaded, and sent #avatar_fingerprint\" do\n          @dummy.avatar = @file\n          @dummy.save\n          @dummy = Dummy.find(@dummy.id)\n          assert_equal \"734016d801a497f5579cdd4ef2ae1d020088c1db754dc434482d76dd5486520a\",\n                       @dummy.avatar_fingerprint\n        end\n      end\n\n      context \"with the default digest\" do\n        before do\n          rebuild_class # MD5 is the default\n          @dummy = Dummy.new\n        end\n\n        it \"returns the right value when sent #avatar_fingerprint\" do\n          @dummy.avatar = @file\n          assert_equal \"aec488126c3b33c08a10c3fa303acf27\",\n                       @dummy.avatar_fingerprint\n        end\n\n        it \"returns the right value when saved, reloaded, and sent #avatar_fingerprint\" do\n          @dummy.avatar = @file\n          @dummy.save\n          @dummy = Dummy.find(@dummy.id)\n          assert_equal \"aec488126c3b33c08a10c3fa303acf27\",\n                       @dummy.avatar_fingerprint\n        end\n      end\n    end\n  end\n\n  context \"an attachment with delete_file option set to false\" do\n    before do\n      rebuild_model preserve_files: true\n      @dummy = Dummy.new\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummy.avatar = @file\n      @dummy.save!\n      @attachment = @dummy.avatar\n      @path = @attachment.path\n    end\n\n    after { @file.close }\n\n    it \"does not delete the files from storage when attachment is destroyed\" do\n      @attachment.destroy\n      assert_file_exists(@path)\n    end\n\n    it \"clears out attachment data when attachment is destroyed\" do\n      @attachment.destroy\n      assert !@attachment.exists?\n      assert_nil @dummy.avatar_file_name\n    end\n\n    it \"does not delete the file when model is destroyed\" do\n      @dummy.destroy\n      assert_file_exists(@path)\n    end\n  end\n\n  context \"An attached file\" do\n    before do\n      rebuild_model\n      @dummy = Dummy.new\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummy.avatar = @file\n      @dummy.save!\n      @attachment = @dummy.avatar\n      @path = @attachment.path\n    end\n\n    after { @file.close }\n\n    it \"is not deleted when the model fails to destroy\" do\n      @dummy.stubs(:destroy).raises(Exception)\n\n      assert_raises Exception do\n        @dummy.destroy\n      end\n\n      assert_file_exists(@path)\n    end\n\n    it \"is deleted when the model is destroyed\" do\n      @dummy.destroy\n      assert_file_not_exists(@path)\n    end\n\n    it \"is not deleted when transaction rollbacks after model is destroyed\" do\n      ActiveRecord::Base.transaction do\n        @dummy.destroy\n        raise ActiveRecord::Rollback\n      end\n\n      assert_file_exists(@path)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/content_type_detector_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::ContentTypeDetector do\n  it 'returns a meaningful content type for open xml spreadsheets' do\n    file = File.new(fixture_file(\"empty.xlsx\"))\n    assert_equal \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n                 Paperclip::ContentTypeDetector.new(file.path).detect\n  end\n\n  it 'gives a sensible default when the name is empty' do\n    assert_equal \"application/octet-stream\", Paperclip::ContentTypeDetector.new(\"\").detect\n  end\n\n  it 'returns the empty content type when the file is empty' do\n    tempfile = Tempfile.new(\"empty\")\n    assert_equal \"inode/x-empty\", Paperclip::ContentTypeDetector.new(tempfile.path).detect\n    tempfile.close\n  end\n\n  it 'returns content type of file if it is an acceptable type' do\n    MIME::Types.stubs(:type_for).returns([MIME::Type.new('application/mp4'), MIME::Type.new('video/mp4'), MIME::Type.new('audio/mp4')])\n    Paperclip::ContentTypeDetector.any_instance\n      .stubs(:type_from_file_contents).returns(\"video/mp4\")\n    @filename = \"my_file.mp4\"\n    assert_equal \"video/mp4\", Paperclip::ContentTypeDetector.new(@filename).detect\n  end\n\n  it 'finds the right type in the list via the file command' do\n    @filename = \"#{Dir.tmpdir}/something.hahalolnotreal\"\n    File.open(@filename, \"w+\") do |file|\n      file.puts \"This is a text file.\"\n      file.rewind\n      assert_equal \"text/plain\", Paperclip::ContentTypeDetector.new(file.path).detect\n    end\n    FileUtils.rm @filename\n  end\n\n  it 'returns a sensible default if something is wrong, like the file is gone' do\n    @filename = \"/path/to/nothing\"\n    assert_equal \"application/octet-stream\", Paperclip::ContentTypeDetector.new(@filename).detect\n  end\n\n  it 'returns a sensible default when the file command is missing' do\n    Paperclip.stubs(:run).raises(Terrapin::CommandLineError.new)\n    @filename = \"/path/to/something\"\n    assert_equal \"application/octet-stream\", Paperclip::ContentTypeDetector.new(@filename).detect\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/file_command_content_type_detector_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::FileCommandContentTypeDetector do\n  it 'returns a content type based on the content of the file' do\n    tempfile = Tempfile.new(\"something\")\n    tempfile.write(\"This is a file.\")\n    tempfile.rewind\n\n    assert_equal \"text/plain\", Paperclip::FileCommandContentTypeDetector.new(tempfile.path).detect\n\n    tempfile.close\n  end\n\n  it 'returns a sensible default when the file command is missing' do\n    Paperclip.stubs(:run).raises(Terrapin::CommandLineError.new)\n    @filename = \"/path/to/something\"\n    assert_equal \"application/octet-stream\",\n      Paperclip::FileCommandContentTypeDetector.new(@filename).detect\n  end\n\n  it 'returns a sensible default on the odd chance that run returns nil' do\n    Paperclip.stubs(:run).returns(nil)\n    assert_equal \"application/octet-stream\",\n      Paperclip::FileCommandContentTypeDetector.new(\"windows\").detect\n  end\n\n  context \"#type_from_file_command\" do\n    let(:detector) { Paperclip::FileCommandContentTypeDetector.new(\"html\") }\n\n    it \"does work with the output of old versions of file\" do\n      Paperclip.stubs(:run).returns(\"text/html charset=us-ascii\")\n      expect(detector.detect).to eq(\"text/html\")\n    end\n\n    it \"does work with the output of new versions of file\" do\n      Paperclip.stubs(:run).returns(\"text/html; charset=us-ascii\")\n      expect(detector.detect).to eq(\"text/html\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/filename_cleaner_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::FilenameCleaner do\n  it 'converts invalid characters to underscores' do\n    cleaner = Paperclip::FilenameCleaner.new(/[aeiou]/)\n    expect(cleaner.call(\"baseball\")).to eq \"b_s_b_ll\"\n  end\n\n  it 'does not convert anything if the character regex is nil' do\n    cleaner = Paperclip::FilenameCleaner.new(nil)\n    expect(cleaner.call(\"baseball\")).to eq \"baseball\"\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/geometry_detector_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::GeometryDetector do\n  it 'identifies an image and extract its dimensions' do\n    Paperclip::GeometryParser.stubs(:new).with(\"434x66,\").returns(stub(make: :correct))\n    file = fixture_file(\"5k.png\")\n    factory = Paperclip::GeometryDetector.new(file)\n\n    output = factory.make\n\n    expect(output).to eq :correct\n  end\n\n  it 'identifies an image and extract its dimensions and orientation' do\n    Paperclip::GeometryParser.stubs(:new).with(\"300x200,6\").returns(stub(make: :correct))\n    file = fixture_file(\"rotated.jpg\")\n    factory = Paperclip::GeometryDetector.new(file)\n\n    output = factory.make\n\n    expect(output).to eq :correct\n  end\n\n  it 'avoids reading EXIF orientation if so configured' do\n    begin\n      Paperclip.options[:use_exif_orientation] = false\n      Paperclip::GeometryParser.stubs(:new).with(\"300x200,1\").returns(stub(make: :correct))\n      file = fixture_file(\"rotated.jpg\")\n      factory = Paperclip::GeometryDetector.new(file)\n\n      output = factory.make\n\n      expect(output).to eq :correct\n    ensure\n      Paperclip.options[:use_exif_orientation] = true\n    end\n  end\nend\n\n"
  },
  {
    "path": "spec/paperclip/geometry_parser_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::GeometryParser do\n  it 'identifies an image and extract its dimensions with no orientation' do\n    Paperclip::Geometry.stubs(:new).with(\n      height: '73',\n      width: '434',\n      modifier: nil,\n      orientation: nil\n    ).returns(:correct)\n    factory = Paperclip::GeometryParser.new(\"434x73\")\n\n    output = factory.make\n\n    assert_equal :correct, output\n  end\n\n  it 'identifies an image and extract its dimensions with an empty orientation' do\n    Paperclip::Geometry.stubs(:new).with(\n      height: '73',\n      width: '434',\n      modifier: nil,\n      orientation: ''\n    ).returns(:correct)\n    factory = Paperclip::GeometryParser.new(\"434x73,\")\n\n    output = factory.make\n\n    assert_equal :correct, output\n  end\n\n  it 'identifies an image and extract its dimensions and orientation' do\n    Paperclip::Geometry.stubs(:new).with(\n      height: '200',\n      width: '300',\n      modifier: nil,\n      orientation: '6'\n    ).returns(:correct)\n    factory = Paperclip::GeometryParser.new(\"300x200,6\")\n\n    output = factory.make\n\n    assert_equal :correct, output\n  end\n\n  it 'identifies an image and extract its dimensions and modifier' do\n    Paperclip::Geometry.stubs(:new).with(\n      height: '64',\n      width: '64',\n      modifier: '#',\n      orientation: nil\n    ).returns(:correct)\n    factory = Paperclip::GeometryParser.new(\"64x64#\")\n\n    output = factory.make\n\n    assert_equal :correct, output\n  end\n\n  it 'identifies an image and extract its dimensions, orientation, and modifier' do\n    Paperclip::Geometry.stubs(:new).with(\n      height: '50',\n      width: '100',\n      modifier: '>',\n      orientation: '7'\n    ).returns(:correct)\n    factory = Paperclip::GeometryParser.new(\"100x50,7>\")\n\n    output = factory.make\n\n    assert_equal :correct, output\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/geometry_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Geometry do\n  context \"Paperclip::Geometry\" do\n    it \"correctly reports its given dimensions\" do\n      assert @geo = Paperclip::Geometry.new(1024, 768)\n      assert_equal 1024, @geo.width\n      assert_equal 768, @geo.height\n    end\n\n    it \"sets height to 0 if height dimension is missing\" do\n      assert @geo = Paperclip::Geometry.new(1024)\n      assert_equal 1024, @geo.width\n      assert_equal 0, @geo.height\n    end\n\n    it \"sets width to 0 if width dimension is missing\" do\n      assert @geo = Paperclip::Geometry.new(nil, 768)\n      assert_equal 0, @geo.width\n      assert_equal 768, @geo.height\n    end\n\n    it \"is generated from a WxH-formatted string\" do\n      assert @geo = Paperclip::Geometry.parse(\"800x600\")\n      assert_equal 800, @geo.width\n      assert_equal 600, @geo.height\n    end\n\n    it \"is generated from a xH-formatted string\" do\n      assert @geo = Paperclip::Geometry.parse(\"x600\")\n      assert_equal 0, @geo.width\n      assert_equal 600, @geo.height\n    end\n\n    it \"is generated from a Wx-formatted string\" do\n      assert @geo = Paperclip::Geometry.parse(\"800x\")\n      assert_equal 800, @geo.width\n      assert_equal 0, @geo.height\n    end\n\n    it \"is generated from a W-formatted string\" do\n      assert @geo = Paperclip::Geometry.parse(\"800\")\n      assert_equal 800, @geo.width\n      assert_equal 0, @geo.height\n    end\n\n    it \"ensures the modifier is nil if not present\" do\n      assert @geo = Paperclip::Geometry.parse(\"123x456\")\n      assert_nil @geo.modifier\n    end\n\n    it \"recognizes an EXIF orientation and not rotate with auto_orient if not necessary\" do\n      geo = Paperclip::Geometry.new(width: 1024, height: 768, orientation: 1)\n      assert geo\n      assert_equal 1024, geo.width\n      assert_equal 768, geo.height\n\n      geo.auto_orient\n\n      assert_equal 1024, geo.width\n      assert_equal 768, geo.height\n    end\n\n    it \"recognizes an EXIF orientation and rotate with auto_orient if necessary\" do\n      geo = Paperclip::Geometry.new(width: 1024, height: 768, orientation: 6)\n      assert geo\n      assert_equal 1024, geo.width\n      assert_equal 768, geo.height\n\n      geo.auto_orient\n\n      assert_equal 768, geo.width\n      assert_equal 1024, geo.height\n    end\n\n    it \"treats x and X the same in geometries\" do\n      @lower = Paperclip::Geometry.parse(\"123x456\")\n      @upper = Paperclip::Geometry.parse(\"123X456\")\n      assert_equal 123, @lower.width\n      assert_equal 123, @upper.width\n      assert_equal 456, @lower.height\n      assert_equal 456, @upper.height\n    end\n\n    ['>', '<', '#', '@', '@>', '>@', '%', '^', '!', nil].each do |mod|\n      it \"ensures the modifier #{description} is preserved\" do\n        assert @geo = Paperclip::Geometry.parse(\"123x456#{mod}\")\n        assert_equal mod, @geo.modifier\n        assert_equal \"123x456#{mod}\", @geo.to_s\n      end\n\n      it \"ensures the modifier #{description} is preserved with no height\" do\n        assert @geo = Paperclip::Geometry.parse(\"123x#{mod}\")\n        assert_equal mod, @geo.modifier\n        assert_equal \"123#{mod}\", @geo.to_s\n      end\n    end\n\n    it \"makes sure the modifier gets passed during transformation_to\" do\n      assert @src = Paperclip::Geometry.parse(\"123x456\")\n      assert @dst = Paperclip::Geometry.parse(\"123x456>\")\n      assert_equal [\"123x456>\", nil], @src.transformation_to(@dst)\n    end\n\n    it \"generates correct ImageMagick formatting string for W-formatted string\" do\n      assert @geo = Paperclip::Geometry.parse(\"800\")\n      assert_equal \"800\", @geo.to_s\n    end\n\n    it \"generates correct ImageMagick formatting string for Wx-formatted string\" do\n      assert @geo = Paperclip::Geometry.parse(\"800x\")\n      assert_equal \"800\", @geo.to_s\n    end\n\n    it \"generates correct ImageMagick formatting string for xH-formatted string\" do\n      assert @geo = Paperclip::Geometry.parse(\"x600\")\n      assert_equal \"x600\", @geo.to_s\n    end\n\n    it \"generates correct ImageMagick formatting string for WxH-formatted string\" do\n      assert @geo = Paperclip::Geometry.parse(\"800x600\")\n      assert_equal \"800x600\", @geo.to_s\n    end\n\n    it \"is generated from a file\" do\n      file = fixture_file(\"5k.png\")\n      file = File.new(file, 'rb')\n      assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }\n      assert_equal 66, @geo.height\n      assert_equal 434, @geo.width\n    end\n\n    it \"is generated from a file path\" do\n      file = fixture_file(\"5k.png\")\n      assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }\n      assert_equal 66, @geo.height\n      assert_equal 434, @geo.width\n    end\n\n    it 'calculates an EXIF-rotated image dimensions from a path' do\n      file = fixture_file(\"rotated.jpg\")\n      assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }\n      @geo.auto_orient\n      assert_equal 300, @geo.height\n      assert_equal 200, @geo.width\n    end\n\n    it \"does not generate from a bad file\" do\n      file = \"/home/This File Does Not Exist.omg\"\n      expect { @geo = Paperclip::Geometry.from_file(file) }.to raise_error(Paperclip::Errors::NotIdentifiedByImageMagickError)\n    end\n\n    it \"does not generate from a blank filename\" do\n      file = \"\"\n      expect { @geo = Paperclip::Geometry.from_file(file) }.to raise_error(Paperclip::Errors::NotIdentifiedByImageMagickError)\n    end\n\n    it \"does not generate from a nil file\" do\n      file = nil\n      expect { @geo = Paperclip::Geometry.from_file(file) }.to raise_error(Paperclip::Errors::NotIdentifiedByImageMagickError)\n    end\n\n    it \"does not generate from a file with no path\" do\n      file = mock(\"file\", path: \"\")\n      file.stubs(:respond_to?).with(:path).returns(true)\n      expect { @geo = Paperclip::Geometry.from_file(file) }.to raise_error(Paperclip::Errors::NotIdentifiedByImageMagickError)\n    end\n\n    it \"lets us know when a command isn't found versus a processing error\" do\n      old_path = ENV['PATH']\n      begin\n        ENV['PATH'] = ''\n        assert_raises(Paperclip::Errors::CommandNotFoundError) do\n          file = fixture_file(\"5k.png\")\n          @geo = Paperclip::Geometry.from_file(file)\n        end\n      ensure\n        ENV['PATH'] = old_path\n      end\n    end\n\n    [['vertical',   900,  1440, true,  false, false, 1440, 900, 0.625],\n     ['horizontal', 1024, 768,  false, true,  false, 1024, 768, 1.3333],\n     ['square',     100,  100,  false, false, true,  100,  100, 1]].each do |args|\n      context \"performing calculations on a #{args[0]} viewport\" do\n        before do\n          @geo = Paperclip::Geometry.new(args[1], args[2])\n        end\n\n        it \"is #{args[3] ? \"\" : \"not\"} vertical\" do\n          assert_equal args[3], @geo.vertical?\n        end\n\n        it \"is #{args[4] ? \"\" : \"not\"} horizontal\" do\n          assert_equal args[4], @geo.horizontal?\n        end\n\n        it \"is #{args[5] ? \"\" : \"not\"} square\" do\n          assert_equal args[5], @geo.square?\n        end\n\n        it \"reports that #{args[6]} is the larger dimension\" do\n          assert_equal args[6], @geo.larger\n        end\n\n        it \"reports that #{args[7]} is the smaller dimension\" do\n          assert_equal args[7], @geo.smaller\n        end\n\n        it \"has an aspect ratio of #{args[8]}\" do\n          expect(@geo.aspect).to be_within(0.0001).of(args[8])\n        end\n      end\n    end\n\n    [[ [1000, 100], [64, 64],  \"x64\", \"64x64+288+0\" ],\n     [ [100, 1000], [50, 950], \"x950\", \"50x950+22+0\" ],\n     [ [100, 1000], [50, 25],  \"50x\", \"50x25+0+237\" ]]. each do |args|\n      context \"of #{args[0].inspect} and given a Geometry #{args[1].inspect} and sent transform_to\" do\n        before do\n          @geo = Paperclip::Geometry.new(*args[0])\n          @dst = Paperclip::Geometry.new(*args[1])\n          @scale, @crop = @geo.transformation_to @dst, true\n        end\n\n        it \"is able to return the correct scaling transformation geometry #{args[2]}\" do\n          assert_equal args[2], @scale\n        end\n\n        it \"is able to return the correct crop transformation geometry #{args[3]}\" do\n          assert_equal args[3], @crop\n        end\n      end\n    end\n\n    [['256x256', {'150x150!' => [150, 150], '150x150#' => [150, 150], '150x150>' => [150, 150], '150x150<' => [256, 256], '150x150' => [150, 150]}],\n     ['256x256', {'512x512!' => [512, 512], '512x512#' => [512, 512], '512x512>' => [256, 256], '512x512<' => [512, 512], '512x512' => [512, 512]}],\n     ['600x400', {'512x512!' => [512, 512], '512x512#' => [512, 512], '512x512>' => [512, 341], '512x512<' => [600, 400], '512x512' => [512, 341]}]].each do |original_size, options|\n      options.each_pair do |size, dimensions|\n        context \"#{original_size} resize_to #{size}\" do\n          before do\n            @source = Paperclip::Geometry.parse original_size\n            @new_geometry = @source.resize_to size\n          end\n          it \"has #{dimensions.first} width\" do\n            assert_equal dimensions.first, @new_geometry.width\n          end\n          it \"has #{dimensions.last} height\" do\n            assert_equal dimensions.last, @new_geometry.height\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/glue_spec.rb",
    "content": "# require \"spec_helper\"\n\ndescribe Paperclip::Glue do\n  describe \"when ActiveRecord does not exist\" do\n    before do\n      ActiveRecordSaved = ActiveRecord\n      Object.send :remove_const, \"ActiveRecord\"\n    end\n\n    after do\n      ActiveRecord = ActiveRecordSaved\n      Object.send :remove_const, \"ActiveRecordSaved\"\n    end\n\n    it \"does not fail\" do\n      NonActiveRecordModel = Class.new\n      NonActiveRecordModel.send :include, Paperclip::Glue\n      Object.send :remove_const, \"NonActiveRecordModel\"\n    end\n  end\n\n  describe \"when ActiveRecord does exist\" do\n    before do\n      if Object.const_defined?(\"ActiveRecord\")\n        @defined_active_record = false\n      else\n        ActiveRecord = :defined\n        @defined_active_record = true\n      end\n    end\n\n    after do\n      if @defined_active_record\n        Object.send :remove_const, \"ActiveRecord\"\n      end\n    end\n\n    it \"does not fail\" do\n      NonActiveRecordModel = Class.new\n      NonActiveRecordModel.send :include, Paperclip::Glue\n      Object.send :remove_const, \"NonActiveRecordModel\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/has_attached_file_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::HasAttachedFile do\n  context '#define_on' do\n    it 'defines a setter on the class object' do\n      assert_adding_attachment('avatar').defines_method('avatar=')\n    end\n\n    it 'defines a getter on the class object' do\n      assert_adding_attachment('avatar').defines_method('avatar')\n    end\n\n    it 'defines a query on the class object' do\n      assert_adding_attachment('avatar').defines_method('avatar?')\n    end\n\n    it 'defines a method on the class to get all of its attachments' do\n      assert_adding_attachment('avatar').defines_class_method('attachment_definitions')\n    end\n\n    it 'flushes errors as part of validations' do\n      assert_adding_attachment('avatar').defines_validation\n    end\n\n    it 'registers the attachment with Paperclip::AttachmentRegistry' do\n      assert_adding_attachment('avatar').registers_attachment\n    end\n\n    it 'defines an after_save callback' do\n      assert_adding_attachment('avatar').defines_callback('after_save')\n    end\n\n    it 'defines a before_destroy callback' do\n      assert_adding_attachment('avatar').defines_callback('before_destroy')\n    end\n\n    it 'defines an after_commit callback' do\n      assert_adding_attachment('avatar').defines_callback('after_commit')\n    end\n\n    context 'when the class does not allow after_commit callbacks' do\n      it 'defines an after_destroy callback' do\n        assert_adding_attachment(\n          'avatar',\n          unstub_methods: [:after_commit]\n        ).defines_callback('after_destroy')\n      end\n    end\n\n    it 'defines the Paperclip-specific callbacks' do\n      assert_adding_attachment('avatar').defines_callback('define_paperclip_callbacks')\n    end\n\n    it 'does not define a media_type check if told not to' do\n      assert_adding_attachment('avatar').does_not_set_up_media_type_check_validation\n    end\n\n    it 'does define a media_type check if told to' do\n      assert_adding_attachment('avatar').sets_up_media_type_check_validation\n    end\n  end\n\n  private\n\n  def assert_adding_attachment(attachment_name, options={})\n    AttachmentAdder.new(attachment_name, options)\n  end\n\n  class AttachmentAdder\n    include Mocha::API\n    include RSpec::Matchers\n\n    def initialize(attachment_name, options = {})\n      @attachment_name = attachment_name\n      @stubbed_class = stub_class\n      if options.present?\n        options[:unstub_methods].each do |method|\n          @stubbed_class.unstub(method)\n        end\n      end\n    end\n\n    def defines_method(method_name)\n      a_class = @stubbed_class\n\n      Paperclip::HasAttachedFile.define_on(a_class, @attachment_name, {})\n\n      expect(a_class).to have_received(:define_method).with(method_name)\n    end\n\n    def defines_class_method(method_name)\n      a_class = @stubbed_class\n      a_class.class.stubs(:define_method)\n\n      Paperclip::HasAttachedFile.define_on(a_class, @attachment_name, {})\n\n      expect(a_class).to have_received(:extend).with(Paperclip::HasAttachedFile::ClassMethods)\n    end\n\n    def defines_validation\n      a_class = @stubbed_class\n\n      Paperclip::HasAttachedFile.define_on(a_class, @attachment_name, {})\n\n      expect(a_class).to have_received(:validates_each).with(@attachment_name)\n    end\n\n    def registers_attachment\n      a_class = @stubbed_class\n      Paperclip::AttachmentRegistry.stubs(:register)\n\n      Paperclip::HasAttachedFile.define_on(a_class, @attachment_name, {size: 1})\n\n      expect(Paperclip::AttachmentRegistry).to have_received(:register).with(a_class, @attachment_name, {size: 1})\n    end\n\n    def defines_callback(callback_name)\n      a_class = @stubbed_class\n\n      Paperclip::HasAttachedFile.define_on(a_class, @attachment_name, {})\n\n      expect(a_class).to have_received(callback_name.to_sym)\n    end\n\n    def does_not_set_up_media_type_check_validation\n      a_class = stub_class\n\n      Paperclip::HasAttachedFile.define_on(a_class, @attachment_name, { validate_media_type: false })\n\n      expect(a_class).to have_received(:validates_media_type_spoof_detection).never\n    end\n\n    def sets_up_media_type_check_validation\n      a_class = stub_class\n\n      Paperclip::HasAttachedFile.define_on(a_class, @attachment_name, { validate_media_type: true })\n\n      expect(a_class).to have_received(:validates_media_type_spoof_detection)\n    end\n\n    private\n\n    def stub_class\n      stub('class',\n           validates_each: nil,\n           define_method: nil,\n           after_save: nil,\n           before_destroy: nil,\n           after_commit: nil,\n           after_destroy: nil,\n           define_paperclip_callbacks: nil,\n           extend: nil,\n           name: 'Billy',\n           validates_media_type_spoof_detection: nil\n          )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/integration_spec.rb",
    "content": "require 'spec_helper'\nrequire 'open-uri'\n\ndescribe 'Paperclip' do\n  around do |example|\n    files_before = ObjectSpace.each_object(Tempfile).select do |file|\n      file.path && File.file?(file.path)\n    end\n\n    example.run\n\n    files_after = ObjectSpace.each_object(Tempfile).select do |file|\n      file.path && File.file?(file.path)\n    end\n\n    diff = files_after - files_before\n    expect(diff).to eq([]), \"Leaked tempfiles: #{diff.inspect}\"\n  end\n\n  context \"Many models at once\" do\n    before do\n      rebuild_model\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      # Deals with `Too many open files` error\n      dummies = Array.new(300) { Dummy.new avatar: @file }\n      Dummy.import dummies\n      # save attachment instances to run after hooks including tempfile cleanup\n      # since activerecord-import does not use our usually hooked-in hooks\n      # (such as after_save)\n      dummies.each { |dummy| dummy.avatar.save }\n    end\n\n    after { @file.close }\n\n    it \"does not exceed the open file limit\" do\n       assert_nothing_raised do\n         Dummy.all.each { |dummy| dummy.avatar }\n       end\n    end\n  end\n\n  context \"An attachment\" do\n    before do\n      rebuild_model styles: { thumb: \"50x50#\" }\n      @dummy = Dummy.new\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummy.avatar = @file\n      assert @dummy.save\n    end\n\n    after { @file.close }\n\n    it \"creates its thumbnails properly\" do\n      assert_match(/\\b50x50\\b/, `identify \"#{@dummy.avatar.path(:thumb)}\"`)\n    end\n\n    context 'reprocessing with unreadable original' do\n      before { File.chmod(0000, @dummy.avatar.path) }\n\n      it \"does not raise an error\" do\n        assert_nothing_raised do\n          silence_stream(STDERR) do\n            @dummy.avatar.reprocess!\n          end\n        end\n      end\n\n      it \"returns false\" do\n        silence_stream(STDERR) do\n          assert !@dummy.avatar.reprocess!\n        end\n      end\n\n      after { File.chmod(0644, @dummy.avatar.path) }\n    end\n\n    context \"redefining its attachment styles\" do\n      before do\n        Dummy.class_eval do\n          has_attached_file :avatar, styles: { thumb: \"150x25#\", dynamic: lambda { |a| '50x50#' } }\n        end\n        @d2 = Dummy.find(@dummy.id)\n        @original_timestamp = @d2.avatar_updated_at\n        @d2.avatar.reprocess!\n        @d2.save\n      end\n\n      it \"creates its thumbnails properly\" do\n        assert_match(/\\b150x25\\b/, `identify \"#{@dummy.avatar.path(:thumb)}\"`)\n        assert_match(/\\b50x50\\b/, `identify \"#{@dummy.avatar.path(:dynamic)}\"`)\n      end\n\n      it \"changes the timestamp\" do\n        assert_not_equal @original_timestamp, @d2.avatar_updated_at\n      end\n    end\n  end\n\n  context \"Attachment\" do\n    before do\n      @thumb_path = \"tmp/public/system/dummies/avatars/000/000/001/thumb/5k.png\"\n      File.delete(@thumb_path) if File.exist?(@thumb_path)\n      rebuild_model styles: { thumb: \"50x50#\" }\n      @dummy = Dummy.new\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n\n    end\n\n    after { @file.close }\n\n    it \"does not create the thumbnails upon saving when post-processing is disabled\" do\n      @dummy.avatar.post_processing = false\n      @dummy.avatar = @file\n      assert @dummy.save\n      assert_file_not_exists @thumb_path\n    end\n\n    it \"creates the thumbnails upon saving when post_processing is enabled\" do\n      @dummy.avatar.post_processing = true\n      @dummy.avatar = @file\n      assert @dummy.save\n      assert_file_exists @thumb_path\n    end\n  end\n\n  context \"Attachment with no generated thumbnails\" do\n    before do\n      @thumb_small_path = \"tmp/public/system/dummies/avatars/000/000/001/thumb_small/5k.png\"\n      @thumb_large_path = \"tmp/public/system/dummies/avatars/000/000/001/thumb_large/5k.png\"\n      File.delete(@thumb_small_path) if File.exist?(@thumb_small_path)\n      File.delete(@thumb_large_path) if File.exist?(@thumb_large_path)\n      rebuild_model styles: { thumb_small: \"50x50#\", thumb_large: \"60x60#\" }\n      @dummy = Dummy.new\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n\n      @dummy.avatar.post_processing = false\n      @dummy.avatar = @file\n      assert @dummy.save\n      @dummy.avatar.post_processing = true\n    end\n\n    after { @file.close }\n\n    it \"allows us to create all thumbnails in one go\" do\n      assert_file_not_exists(@thumb_small_path)\n      assert_file_not_exists(@thumb_large_path)\n\n      @dummy.avatar.reprocess!\n\n      assert_file_exists(@thumb_small_path)\n      assert_file_exists(@thumb_large_path)\n    end\n\n    it \"allows us to selectively create each thumbnail\" do\n      skip <<-EXPLANATION\n\t#reprocess! calls #assign which calls Paperclip.io_adapters.for \n\twhich creates the tempfile. #assign then calls #post_process_file which\n\tcalls MediaTypeSpoofDetectionValidator#validate_each which calls \n\tPaperclip.io_adapters.for, which creates another tempfile. That first\n\ttempfile is the one that leaks.\n      EXPLANATION\n\n      assert_file_not_exists(@thumb_small_path)\n      assert_file_not_exists(@thumb_large_path)\n\n      @dummy.avatar.reprocess! :thumb_small\n      assert_file_exists(@thumb_small_path)\n      assert_file_not_exists(@thumb_large_path)\n\n      @dummy.avatar.reprocess! :thumb_large\n      assert_file_exists(@thumb_large_path)\n    end\n  end\n\n  context \"A model that modifies its original\" do\n    before do\n      rebuild_model styles: { original: \"2x2#\" }\n      @dummy = Dummy.new\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummy.avatar = @file\n    end\n\n    it \"reports the file size of the processed file and not the original\" do\n      assert_not_equal File.size(@file.path), @dummy.avatar.size\n    end\n\n    after do\n      @file.close\n      # save attachment instance to run after hooks (including tempfile cleanup)\n      @dummy.avatar.save\n    end\n  end\n\n  context \"A model with attachments scoped under an id\" do\n    before do\n      rebuild_model styles: { large: \"100x100\",\n                                 medium: \"50x50\" },\n                    path: \":rails_root/tmp/:id/:attachments/:style.:extension\"\n      @dummy = Dummy.new\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummy.avatar = @file\n    end\n\n    after { @file.close }\n\n    context \"when saved\" do\n      before do\n        @dummy.save\n        @saved_path = @dummy.avatar.path(:large)\n      end\n\n      it \"has a large file in the right place\" do\n        assert_file_exists(@dummy.avatar.path(:large))\n      end\n\n      context \"and deleted\" do\n        before do\n          @dummy.avatar.clear\n          @dummy.save\n        end\n\n        it \"does not have a large file in the right place anymore\" do\n          assert_file_not_exists(@saved_path)\n        end\n\n        it \"does not have its next two parent directories\" do\n          assert_file_not_exists(File.dirname(@saved_path))\n          assert_file_not_exists(File.dirname(File.dirname(@saved_path)))\n        end\n      end\n\n      context 'and deleted where the delete fails' do\n        it \"does not die if an unexpected SystemCallError happens\" do\n          FileUtils.stubs(:rmdir).raises(Errno::EPIPE)\n          assert_nothing_raised do\n            @dummy.avatar.clear\n            @dummy.save\n          end\n        end\n      end\n    end\n  end\n\n  [000,002,022].each do |umask|\n    context \"when the umask is #{umask}\" do\n      before do\n        rebuild_model\n        @dummy = Dummy.new\n        @file  = File.new(fixture_file(\"5k.png\"), 'rb')\n        @umask = File.umask(umask)\n      end\n\n      after do\n        File.umask @umask\n        @file.close\n      end\n\n      it \"respects the current umask\" do\n        @dummy.avatar = @file\n        @dummy.save\n        assert_equal 0666&~umask, 0666&File.stat(@dummy.avatar.path).mode\n      end\n    end\n  end\n\n  [0666,0664,0640].each do |perms|\n    context \"when the perms are #{perms}\" do\n      before do\n        rebuild_model override_file_permissions: perms\n        @dummy = Dummy.new\n        @file  = File.new(fixture_file(\"5k.png\"), 'rb')\n      end\n\n      after do\n        @file.close\n      end\n\n      it \"respects the current perms\" do\n        @dummy.avatar = @file\n        @dummy.save\n        assert_equal perms, File.stat(@dummy.avatar.path).mode & 0777\n      end\n    end\n  end\n\n  it \"skips chmod operation, when override_file_permissions is set to false (e.g. useful when using CIFS mounts)\" do\n    FileUtils.expects(:chmod).never\n\n    rebuild_model override_file_permissions: false\n    dummy = Dummy.create!\n    dummy.avatar = @file\n    dummy.save\n  end\n\n  context \"A model with a filesystem attachment\" do\n    before do\n      rebuild_model styles: { large: \"300x300>\",\n                                 medium: \"100x100\",\n                                 thumb: [\"32x32#\", :gif] },\n                    default_style: :medium,\n                    url: \"/:attachment/:class/:style/:id/:basename.:extension\",\n                    path: \":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension\"\n      @dummy     = Dummy.new\n      @file      = File.new(fixture_file(\"5k.png\"), 'rb')\n      @bad_file  = File.new(fixture_file(\"bad.png\"), 'rb')\n\n      assert @dummy.avatar = @file\n      assert @dummy.valid?, @dummy.errors.full_messages.join(\", \")\n      assert @dummy.save\n    end\n\n    after { [@file, @bad_file].each(&:close) }\n\n    it \"writes and delete its files\" do\n      [[\"434x66\", :original],\n       [\"300x46\", :large],\n       [\"100x15\", :medium],\n       [\"32x32\", :thumb]].each do |geo, style|\n        cmd = %Q[identify -format \"%wx%h\" \"#{@dummy.avatar.path(style)}\"]\n        assert_equal geo, `#{cmd}`.chomp, cmd\n      end\n\n      saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }\n\n      @d2 = Dummy.find(@dummy.id)\n      assert_equal \"100x15\", `identify -format \"%wx%h\" \"#{@d2.avatar.path}\"`.chomp\n      assert_equal \"434x66\", `identify -format \"%wx%h\" \"#{@d2.avatar.path(:original)}\"`.chomp\n      assert_equal \"300x46\", `identify -format \"%wx%h\" \"#{@d2.avatar.path(:large)}\"`.chomp\n      assert_equal \"100x15\", `identify -format \"%wx%h\" \"#{@d2.avatar.path(:medium)}\"`.chomp\n      assert_equal \"32x32\",  `identify -format \"%wx%h\" \"#{@d2.avatar.path(:thumb)}\"`.chomp\n\n      assert @dummy.valid?\n      assert @dummy.save\n\n      saved_paths.each do |p|\n        assert_file_exists(p)\n      end\n\n      @dummy.avatar.clear\n      assert_nil @dummy.avatar_file_name\n      assert @dummy.valid?\n      assert @dummy.save\n\n      saved_paths.each do |p|\n        assert_file_not_exists(p)\n      end\n\n      @d2 = Dummy.find(@dummy.id)\n      assert_nil @d2.avatar_file_name\n    end\n\n    it \"works exactly the same when new as when reloaded\" do\n      @d2 = Dummy.find(@dummy.id)\n\n      assert_equal @dummy.avatar_file_name, @d2.avatar_file_name\n      [:thumb, :medium, :large, :original].each do |style|\n        assert_equal @dummy.avatar.path(style), @d2.avatar.path(style)\n      end\n\n      saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }\n\n      @d2.avatar.clear\n      assert @d2.save\n\n      saved_paths.each do |p|\n        assert_file_not_exists(p)\n      end\n    end\n\n    it \"does not abide things that don't have adapters\" do\n      assert_raises(Paperclip::AdapterRegistry::NoHandlerError) do\n        @dummy.avatar = \"not a file\"\n      end\n    end\n\n    it \"is not ok with bad files\" do\n      @dummy.avatar = @bad_file\n      assert ! @dummy.valid?\n      # save attachment instance to run after hooks (including tempfile cleanup)\n      @dummy.avatar.save\n    end\n\n    it \"knows the difference between good files, bad files, and not files when validating\" do\n      Dummy.validates_attachment_presence :avatar\n      @d2 = Dummy.find(@dummy.id)\n      @d2.avatar = @file\n      assert  @d2.valid?, @d2.errors.full_messages.inspect\n      # save attachment instance to run after hooks (including tempfile cleanup)\n      @d2.avatar.save\n\n      @d2.avatar = @bad_file\n      assert ! @d2.valid?\n      # save attachment instance to run after hooks (including tempfile cleanup)\n      @d2.avatar.save\n    end\n\n    it \"is able to reload without saving and not have the file disappear\" do\n      @dummy.avatar = @file\n      assert @dummy.save, @dummy.errors.full_messages.inspect\n      @dummy.avatar.clear\n      assert_nil @dummy.avatar_file_name\n      @dummy.reload\n      assert_equal \"5k.png\", @dummy.avatar_file_name\n    end\n\n    context \"that is assigned its file from another Paperclip attachment\" do\n      before do\n        @dummy2 = Dummy.new\n        @file2 = File.new(fixture_file(\"12k.png\"), 'rb')\n        assert @dummy2.avatar = @file2\n        @dummy2.save\n      end\n\n      after { @file2.close }\n\n      it \"works when assigned a file\" do\n        assert_not_equal `identify -format \"%wx%h\" \"#{@dummy.avatar.path(:original)}\"`,\n          `identify -format \"%wx%h\" \"#{@dummy2.avatar.path(:original)}\"`\n\n        assert @dummy.avatar = @dummy2.avatar\n        @dummy.save\n        assert_equal @dummy.avatar_file_name, @dummy2.avatar_file_name\n        assert_equal `identify -format \"%wx%h\" \"#{@dummy.avatar.path(:original)}\"`,\n          `identify -format \"%wx%h\" \"#{@dummy2.avatar.path(:original)}\"`\n      end\n    end\n\n  end\n\n  context \"A model with an attachments association and a Paperclip attachment\" do\n    before do\n      Dummy.class_eval do\n        has_many :attachments, class_name: 'Dummy'\n      end\n\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummy = Dummy.new\n      @dummy.avatar = @file\n    end\n\n    after { @file.close }\n\n    it \"does not error when saving\" do\n      @dummy.save!\n    end\n  end\n\n  context \"A model with an attachment with hash in file name\" do\n    before do\n      @settings = { styles: { thumb: \"50x50#\" },\n        path: \":rails_root/public/system/:attachment/:id_partition/:style/:hash.:extension\",\n        url: \"/system/:attachment/:id_partition/:style/:hash.:extension\",\n        hash_secret: \"somesecret\" }\n\n      rebuild_model @settings\n\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      @dummy = Dummy.create! avatar: @file\n    end\n\n    after do\n      @file.close\n    end\n\n    it \"is accessible\" do\n      assert_file_exists(@dummy.avatar.path(:original))\n      assert_file_exists(@dummy.avatar.path(:thumb))\n    end\n\n    context \"when new style is added\" do\n      before do\n        @dummy.avatar.options[:styles][:mini] = \"25x25#\"\n        @dummy.avatar.instance_variable_set :@normalized_styles, nil\n        Time.stubs(now: Time.now + 10)\n        @dummy.avatar.reprocess!\n        @dummy.reload\n      end\n\n      it \"makes all the styles accessible\" do\n        assert_file_exists(@dummy.avatar.path(:original))\n        assert_file_exists(@dummy.avatar.path(:thumb))\n        assert_file_exists(@dummy.avatar.path(:mini))\n      end\n    end\n  end\n\n  if ENV['S3_BUCKET']\n    def s3_files_for attachment\n      [:thumb, :medium, :large, :original].inject({}) do |files, style|\n        data = `curl \"#{attachment.url(style)}\" 2>/dev/null`.chomp\n        t = Tempfile.new(\"paperclip-test\")\n        t.binmode\n        t.write(data)\n        t.rewind\n        files[style] = t\n        files\n      end\n    end\n\n    def s3_headers_for attachment, style\n      `curl --head \"#{attachment.url(style)}\" 2>/dev/null`.split(\"\\n\").inject({}) do |h,head|\n        split_head = head.chomp.split(/\\s*:\\s*/, 2)\n        h[split_head.first.downcase] = split_head.last unless split_head.empty?\n        h\n      end\n    end\n\n    context \"A model with an S3 attachment\" do\n      before do\n        rebuild_model(\n          styles: {\n            large: \"300x300>\",\n            medium: \"100x100\",\n            thumb: [\"32x32#\", :gif],\n            custom: {\n              geometry: \"32x32#\",\n              s3_headers: { 'Cache-Control' => 'max-age=31557600' },\n              s3_metadata: { 'foo' => 'bar'}\n            }\n          },\n          storage: :s3,\n          s3_credentials: File.new(fixture_file('s3.yml')),\n          s3_options: { logger: Paperclip.logger },\n          default_style: :medium,\n          bucket: ENV['S3_BUCKET'],\n          path: \":class/:attachment/:id/:style/:basename.:extension\"\n        )\n\n        @dummy     = Dummy.new\n        @file      = File.new(fixture_file('5k.png'), 'rb')\n        @bad_file  = File.new(fixture_file('bad.png'), 'rb')\n\n        @dummy.avatar = @file\n        @dummy.valid?\n        @dummy.save!\n\n        @files_on_s3 = s3_files_for(@dummy.avatar)\n      end\n\n      after do\n        @file.close\n        @bad_file.close\n        @files_on_s3.values.each(&:close) if @files_on_s3\n      end\n\n      context 'assigning itself to a new model' do\n        before do\n          @d2 = Dummy.new\n          @d2.avatar = @dummy.avatar\n          @d2.save\n        end\n\n        it \"has the same name as the old file\" do\n          assert_equal @d2.avatar.original_filename, @dummy.avatar.original_filename\n        end\n      end\n\n      it \"has the same contents as the original\" do\n        assert_equal @file.read, @files_on_s3[:original].read\n      end\n\n      it \"writes and delete its files\" do\n        [[\"434x66\", :original],\n         [\"300x46\", :large],\n         [\"100x15\", :medium],\n         [\"32x32\", :thumb]].each do |geo, style|\n          cmd = %Q[identify -format \"%wx%h\" \"#{@files_on_s3[style].path}\"]\n          assert_equal geo, `#{cmd}`.chomp, cmd\n        end\n\n        @d2 = Dummy.find(@dummy.id)\n        @d2_files = s3_files_for @d2.avatar\n        [[\"434x66\", :original],\n         [\"300x46\", :large],\n         [\"100x15\", :medium],\n         [\"32x32\", :thumb]].each do |geo, style|\n          cmd = %Q[identify -format \"%wx%h\" \"#{@d2_files[style].path}\"]\n          assert_equal geo, `#{cmd}`.chomp, cmd\n        end\n\n        @dummy.avatar.clear\n        assert_nil @dummy.avatar_file_name\n        assert @dummy.valid?\n        assert @dummy.save\n\n        [:thumb, :medium, :large, :original].each do |style|\n          assert ! @dummy.avatar.exists?(style)\n        end\n\n        @d2 = Dummy.find(@dummy.id)\n        assert_nil @d2.avatar_file_name\n      end\n\n      it \"works exactly the same when new as when reloaded\" do\n        @d2 = Dummy.find(@dummy.id)\n\n        assert_equal @dummy.avatar_file_name, @d2.avatar_file_name\n\n        [:thumb, :medium, :large, :original].each do |style|\n          begin\n            first_file = open(@dummy.avatar.url(style))\n            second_file = open(@dummy.avatar.url(style))\n            assert_equal first_file.read, second_file.read\n          ensure\n            first_file.close if first_file\n            second_file.close if second_file\n          end\n        end\n\n        @d2.avatar.clear\n        assert @d2.save\n\n        [:thumb, :medium, :large, :original].each do |style|\n          assert ! @dummy.avatar.exists?(style)\n        end\n      end\n\n      it \"knows the difference between good files, bad files, and nil\" do\n        @dummy.avatar = @bad_file\n        assert ! @dummy.valid?\n        @dummy.avatar = nil\n        assert @dummy.valid?\n\n        Dummy.validates_attachment_presence :avatar\n        @d2 = Dummy.find(@dummy.id)\n        @d2.avatar = @file\n        assert   @d2.valid?\n        @d2.avatar = @bad_file\n        assert ! @d2.valid?\n        @d2.avatar = nil\n        assert ! @d2.valid?\n      end\n\n      it \"is able to reload without saving and not have the file disappear\" do\n        @dummy.avatar = @file\n        assert @dummy.save\n        @dummy.avatar = nil\n        assert_nil @dummy.avatar_file_name\n        @dummy.reload\n        assert_equal \"5k.png\", @dummy.avatar_file_name\n      end\n\n      it \"has the right content type\" do\n        headers = s3_headers_for(@dummy.avatar, :original)\n        assert_equal 'image/png', headers['content-type']\n      end\n\n      it \"has the right style-specific headers\" do\n        headers = s3_headers_for(@dummy.avatar, :custom)\n        assert_equal 'max-age=31557600', headers['cache-control']\n      end\n\n      it \"has the right style-specific metadata\" do\n        headers = s3_headers_for(@dummy.avatar, :custom)\n        assert_equal 'bar', headers['x-amz-meta-foo']\n      end\n\n      context \"with non-english character in the file name\" do\n        before do\n          @file.stubs(:original_filename).returns(\"クリップ.png\")\n          @dummy.avatar = @file\n        end\n\n        it \"does not raise any error\" do\n          @dummy.save!\n        end\n      end\n    end\n  end\n\n  context \"Copying attachments between models\" do\n    before do\n      rebuild_model\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n    end\n\n    after { @file.close }\n\n    it \"succeeds when original attachment is a file\" do\n      original = Dummy.new\n      original.avatar = @file\n      assert original.save\n\n      copy = Dummy.new\n      copy.avatar = original.avatar\n      assert copy.save\n\n      assert copy.avatar.present?\n    end\n\n    it \"succeeds when original attachment is empty\" do\n      original = Dummy.create!\n\n      copy = Dummy.new\n      copy.avatar = @file\n      assert copy.save\n      assert copy.avatar.present?\n\n      copy.avatar = original.avatar\n      assert copy.save\n      assert !copy.avatar.present?\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/interpolations_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Interpolations do\n  it \"returns all methods but the infrastructure when sent #all\" do\n    methods = Paperclip::Interpolations.all\n    assert ! methods.include?(:[])\n    assert ! methods.include?(:[]=)\n    assert ! methods.include?(:all)\n    methods.each do |m|\n      assert Paperclip::Interpolations.respond_to?(m)\n    end\n  end\n\n  it \"returns the Rails.root\" do\n    assert_equal Rails.root, Paperclip::Interpolations.rails_root(:attachment, :style)\n  end\n\n  it \"returns the Rails.env\" do\n    assert_equal Rails.env, Paperclip::Interpolations.rails_env(:attachment, :style)\n  end\n\n  it \"returns the class of the Interpolations module when called with no params\" do\n    assert_equal Module, Paperclip::Interpolations.class\n  end\n\n  it \"returns the class of the instance\" do\n    class Thing ; end\n    attachment = mock\n    attachment.expects(:instance).returns(attachment)\n    attachment.expects(:class).returns(Thing)\n    assert_equal \"things\", Paperclip::Interpolations.class(attachment, :style)\n  end\n\n  it \"returns the basename of the file\" do\n    attachment = mock\n    attachment.expects(:original_filename).returns(\"one.jpg\").times(1)\n    assert_equal \"one\", Paperclip::Interpolations.basename(attachment, :style)\n  end\n\n  it \"returns the extension of the file\" do\n    attachment = mock\n    attachment.expects(:original_filename).returns(\"one.jpg\")\n    attachment.expects(:styles).returns({})\n    assert_equal \"jpg\", Paperclip::Interpolations.extension(attachment, :style)\n  end\n\n  it \"returns the extension of the file as the format if defined in the style\" do\n    attachment = mock\n    attachment.expects(:original_filename).never\n    attachment.expects(:styles).twice.returns({style: {format: \"png\"}})\n\n    [:style, 'style'].each do |style|\n      assert_equal \"png\", Paperclip::Interpolations.extension(attachment, style)\n    end\n  end\n\n  it \"returns the extension of the file based on the content type\" do\n    attachment = mock\n    attachment.expects(:content_type).returns('image/png')\n    attachment.expects(:styles).returns({})\n    interpolations = Paperclip::Interpolations\n    interpolations.expects(:extension).returns('random')\n    assert_equal \"png\", interpolations.content_type_extension(attachment, :style)\n  end\n\n  it \"returns the original extension of the file if it matches a content type extension\" do\n    attachment = mock\n    attachment.expects(:content_type).returns('image/jpeg')\n    attachment.expects(:styles).returns({})\n    interpolations = Paperclip::Interpolations\n    interpolations.expects(:extension).returns('jpe')\n    assert_equal \"jpe\", interpolations.content_type_extension(attachment, :style)\n  end\n\n  it \"returns the extension of the file with a dot\" do\n    attachment = mock\n    attachment.expects(:original_filename).returns(\"one.jpg\")\n    attachment.expects(:styles).returns({})\n    assert_equal \".jpg\", Paperclip::Interpolations.dotextension(attachment, :style)\n  end\n\n  it \"returns the extension of the file without a dot if the extension is empty\" do\n    attachment = mock\n    attachment.expects(:original_filename).returns(\"one\")\n    attachment.expects(:styles).returns({})\n    assert_equal \"\", Paperclip::Interpolations.dotextension(attachment, :style)\n  end\n\n  it \"returns the latter half of the content type of the extension if no match found\" do\n    attachment = mock\n    attachment.expects(:content_type).at_least_once().returns('not/found')\n    attachment.expects(:styles).returns({})\n    interpolations = Paperclip::Interpolations\n    interpolations.expects(:extension).returns('random')\n    assert_equal \"found\", interpolations.content_type_extension(attachment, :style)\n  end\n\n  it \"returns the format if defined in the style, ignoring the content type\" do\n    attachment = mock\n    attachment.expects(:content_type).returns('image/jpeg')\n    attachment.expects(:styles).returns({style: {format: \"png\"}})\n    interpolations = Paperclip::Interpolations\n    interpolations.expects(:extension).returns('random')\n    assert_equal \"png\", interpolations.content_type_extension(attachment, :style)\n  end\n\n  it \"is able to handle numeric style names\" do\n    attachment = mock(\n      styles: {:\"4\" => {format: :expected_extension}}\n    )\n    assert_equal :expected_extension, Paperclip::Interpolations.extension(attachment, 4)\n  end\n\n  it \"returns the #to_param of the attachment\" do\n    attachment = mock\n    attachment.expects(:to_param).returns(\"23-awesome\")\n    attachment.expects(:instance).returns(attachment)\n    assert_equal \"23-awesome\", Paperclip::Interpolations.param(attachment, :style)\n  end\n\n  it \"returns the id of the attachment\" do\n    attachment = mock\n    attachment.expects(:id).returns(23)\n    attachment.expects(:instance).returns(attachment)\n    assert_equal 23, Paperclip::Interpolations.id(attachment, :style)\n  end\n\n  it \"returns nil for attachments to new records\" do\n    attachment = mock\n    attachment.expects(:id).returns(nil)\n    attachment.expects(:instance).returns(attachment)\n    assert_nil Paperclip::Interpolations.id(attachment, :style)\n  end\n\n  it \"returns the partitioned id of the attachment when the id is an integer\" do\n    attachment = mock\n    attachment.expects(:id).returns(23)\n    attachment.expects(:instance).returns(attachment)\n    assert_equal \"000/000/023\", Paperclip::Interpolations.id_partition(attachment, :style)\n  end\n\n  it \"returns the partitioned id when the id is above 999_999_999\" do\n    attachment = mock\n    attachment.expects(:id).\n      returns(Paperclip::Interpolations::ID_PARTITION_LIMIT)\n    attachment.expects(:instance).returns(attachment)\n    assert_equal \"001/000/000/000\",\n      Paperclip::Interpolations.id_partition(attachment, :style)\n  end\n\n  it \"returns the partitioned id of the attachment when the id is a string\" do\n    attachment = mock\n    attachment.expects(:id).returns(\"32fnj23oio2f\")\n    attachment.expects(:instance).returns(attachment)\n    assert_equal \"32f/nj2/3oi\", Paperclip::Interpolations.id_partition(attachment, :style)\n  end\n\n  it \"returns nil for the partitioned id of an attachment to a new record (when the id is nil)\" do\n    attachment = mock\n    attachment.expects(:id).returns(nil)\n    attachment.expects(:instance).returns(attachment)\n    assert_nil Paperclip::Interpolations.id_partition(attachment, :style)\n  end\n\n  it \"returns the name of the attachment\" do\n    attachment = mock\n    attachment.expects(:name).returns(\"file\")\n    assert_equal \"files\", Paperclip::Interpolations.attachment(attachment, :style)\n  end\n\n  it \"returns the style\" do\n    assert_equal :style, Paperclip::Interpolations.style(:attachment, :style)\n  end\n\n  it \"returns the default style\" do\n    attachment = mock\n    attachment.expects(:default_style).returns(:default_style)\n    assert_equal :default_style, Paperclip::Interpolations.style(attachment, nil)\n  end\n\n  it \"reinterpolates :url\" do\n    attachment = mock\n    attachment.expects(:url).with(:style, timestamp: false, escape: false).returns(\"1234\")\n    assert_equal \"1234\", Paperclip::Interpolations.url(attachment, :style)\n  end\n\n  it \"raises if infinite loop detcted reinterpolating :url\" do\n    attachment = Object.new\n    class << attachment\n      def url(*args)\n        Paperclip::Interpolations.url(self, :style)\n      end\n    end\n    assert_raises(Paperclip::Errors::InfiniteInterpolationError){ Paperclip::Interpolations.url(attachment, :style) }\n  end\n\n  it \"returns the filename as basename.extension\" do\n    attachment = mock\n    attachment.expects(:styles).returns({})\n    attachment.expects(:original_filename).returns(\"one.jpg\").times(2)\n    assert_equal \"one.jpg\", Paperclip::Interpolations.filename(attachment, :style)\n  end\n\n  it \"returns the filename as basename.extension when format supplied\" do\n    attachment = mock\n    attachment.expects(:styles).returns({style: {format: :png}})\n    attachment.expects(:original_filename).returns(\"one.jpg\").times(1)\n    assert_equal \"one.png\", Paperclip::Interpolations.filename(attachment, :style)\n  end\n\n  it \"returns the filename as basename when extension is blank\" do\n    attachment = mock\n    attachment.stubs(:styles).returns({})\n    attachment.stubs(:original_filename).returns(\"one\")\n    assert_equal \"one\", Paperclip::Interpolations.filename(attachment, :style)\n  end\n  \n  it \"returns the basename when the extension contains regexp special characters\" do\n    attachment = mock\n    attachment.stubs(:styles).returns({})\n    attachment.stubs(:original_filename).returns(\"one.ab)\")\n    assert_equal \"one\", Paperclip::Interpolations.basename(attachment, :style)\n  end\n\n  it \"returns the timestamp\" do\n    now = Time.now\n    zone = 'UTC'\n    attachment = mock\n    attachment.expects(:instance_read).with(:updated_at).returns(now)\n    attachment.expects(:time_zone).returns(zone)\n    assert_equal now.in_time_zone(zone).to_s, Paperclip::Interpolations.timestamp(attachment, :style)\n  end\n\n  it \"returns updated_at\" do\n    attachment = mock\n    seconds_since_epoch = 1234567890\n    attachment.expects(:updated_at).returns(seconds_since_epoch)\n    assert_equal seconds_since_epoch, Paperclip::Interpolations.updated_at(attachment, :style)\n  end\n\n  it \"returns attachment's hash when passing both arguments\" do\n    attachment = mock\n    fake_hash = \"a_wicked_secure_hash\"\n    attachment.expects(:hash_key).returns(fake_hash)\n    assert_equal fake_hash, Paperclip::Interpolations.hash(attachment, :style)\n  end\n\n  it \"returns Object#hash when passing no argument\" do\n    attachment = mock\n    fake_hash = \"a_wicked_secure_hash\"\n    attachment.expects(:hash_key).never.returns(fake_hash)\n    assert_not_equal fake_hash, Paperclip::Interpolations.hash\n  end\n\n  it \"calls all expected interpolations with the given arguments\" do\n    Paperclip::Interpolations.expects(:id).with(:attachment, :style).returns(1234)\n    Paperclip::Interpolations.expects(:attachment).with(:attachment, :style).returns(\"attachments\")\n    Paperclip::Interpolations.expects(:notreal).never\n    value = Paperclip::Interpolations.interpolate(\":notreal/:id/:attachment\", :attachment, :style)\n    assert_equal \":notreal/1234/attachments\", value\n  end\n\n  it \"handles question marks\" do\n    Paperclip.interpolates :foo? do\n      \"bar\"\n    end\n    Paperclip::Interpolations.expects(:fool).never\n    value = Paperclip::Interpolations.interpolate(\":fo/:foo?\")\n    assert_equal \":fo/bar\", value\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/abstract_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::AbstractAdapter do\n  class TestAdapter < Paperclip::AbstractAdapter\n    attr_accessor :tempfile\n\n    def content_type\n      Paperclip::ContentTypeDetector.new(path).detect\n    end\n  end\n\n  subject { TestAdapter.new(nil) }\n\n  context \"content type from file contents\" do\n    before do\n      subject.stubs(:path).returns(\"image.png\")\n      Paperclip.stubs(:run).returns(\"image/png\\n\")\n      Paperclip::ContentTypeDetector.any_instance.stubs(:type_from_mime_magic).returns(\"image/png\")\n    end\n\n    it \"returns the content type without newline\" do\n      assert_equal \"image/png\", subject.content_type\n    end\n  end\n\n  context \"nil?\" do\n    it \"returns false\" do\n      assert !subject.nil?\n    end\n  end\n\n  context \"delegation\" do\n    before do\n      subject.tempfile = stub(\"Tempfile\")\n    end\n\n    [:binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink].each do |method|\n      it \"delegates #{method} to @tempfile\" do\n        subject.tempfile.stubs(method)\n        subject.public_send(method)\n        assert_received subject.tempfile, method\n      end\n    end\n  end\n\n  it 'gets rid of slashes and colons in filenames' do\n    subject.original_filename = \"awesome/file:name.png\"\n\n    assert_equal \"awesome_file_name.png\", subject.original_filename\n  end\n\n  it 'is an assignment' do\n    assert subject.assignment?\n  end\n\n  it 'is not nil' do\n    assert !subject.nil?\n  end\n\n  it \"generates a destination filename with no original filename\" do\n    expect(subject.send(:destination).path).to_not be_nil\n  end\n\n  it 'uses the original filename to generate the tempfile' do\n    subject.original_filename = \"file.png\"\n    expect(subject.send(:destination).path).to end_with(\".png\")\n  end\n\n  context \"generates a fingerprint\" do\n    subject { TestAdapter.new(nil, options) }\n\n    before do\n      subject.stubs(:path).returns(fixture_file(\"50x50.png\"))\n    end\n\n    context \"MD5\" do\n      let(:options) { { hash_digest: Digest::MD5 } }\n\n      it \"returns a fingerprint\" do\n        expect(subject.fingerprint).to be_a String\n        expect(subject.fingerprint).to eq \"a790b00c9b5d58a8fd17a1ec5a187129\"\n      end\n    end\n\n    context \"SHA256\" do\n      let(:options) { { hash_digest: Digest::SHA256 } }\n\n      it \"returns a fingerprint\" do\n        expect(subject.fingerprint).to be_a String\n        expect(subject.fingerprint).\n          to eq \"243d7ce1099719df25f600f1c369c629fb979f88d5a01dbe7d0d48c8e6715bb1\"\n      end\n    end\n  end\n\n  context \"#copy_to_tempfile\" do\n    around do |example|\n      FileUtils.module_eval do\n        class << self\n          alias paperclip_ln ln\n\n          def ln(*)\n            raise Errno::EXDEV\n          end\n        end\n      end\n\n      example.run\n\n      FileUtils.module_eval do\n        class << self\n          alias ln paperclip_ln\n          undef paperclip_ln\n        end\n      end\n    end\n\n    it \"should return a readable file even when linking fails\" do\n      src = open(fixture_file(\"5k.png\"), \"rb\")\n      expect(subject.send(:copy_to_tempfile, src).read).to eq src.read\n    end\n  end\n\n  context \"#original_filename=\" do\n    it \"should not fail with a nil original filename\" do\n      expect { subject.original_filename = nil }.not_to raise_error\n    end\n  end\n\n  context \"#link_or_copy_file\" do\n    class TestLinkOrCopyAdapter < Paperclip::AbstractAdapter\n      public :copy_to_tempfile, :destination\n    end\n\n    subject { TestLinkOrCopyAdapter.new(nil) }\n    let(:body) { \"body\" }\n\n    let(:file) do\n      t = Tempfile.new(\"destination\")\n      t.print(body)\n      t.rewind\n      t\n    end\n\n    after do\n      file.close\n      file.unlink\n    end\n\n    it \"should be able to read the file\" do\n      expect(subject.copy_to_tempfile(file).read).to eq(body)\n    end\n\n    it \"should be able to reopen the file after symlink has failed\" do\n      FileUtils.expects(:ln).raises(Errno::EXDEV)\n\n      expect(subject.copy_to_tempfile(file).read).to eq(body)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/attachment_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::AttachmentAdapter do\n  before do\n    rebuild_model path: \"tmp/:class/:attachment/:style/:filename\", styles: {thumb: '50x50'}\n    @attachment = Dummy.new.avatar\n  end\n\n  context \"for an attachment\" do\n    before do\n      @file = File.new(fixture_file(\"5k.png\"))\n      @file.binmode\n\n      @attachment.assign(@file)\n      @attachment.save\n      @subject = Paperclip.io_adapters.for(@attachment,\n                                           hash_digest: Digest::MD5)\n    end\n\n    after do\n      @file.close\n      @subject.close\n    end\n\n    it \"gets the right filename\" do\n      assert_equal \"5k.png\", @subject.original_filename\n    end\n\n    it \"forces binmode on tempfile\" do\n      assert @subject.instance_variable_get(\"@tempfile\").binmode?\n    end\n\n    it \"gets the content type\" do\n      assert_equal \"image/png\", @subject.content_type\n    end\n\n    it \"gets the file's size\" do\n      assert_equal 4456, @subject.size\n    end\n\n    it \"returns false for a call to nil?\" do\n      assert ! @subject.nil?\n    end\n\n    it \"generates a MD5 hash of the contents\" do\n      expected = Digest::MD5.file(@file.path).to_s\n      assert_equal expected, @subject.fingerprint\n    end\n\n    it \"reads the contents of the file\" do\n      expected = @file.read\n      actual = @subject.read\n      assert expected.length > 0\n      assert_equal expected.length, actual.length\n      assert_equal expected, actual\n    end\n\n  end\n\n  context \"for a file with restricted characters in the name\" do\n    before do\n      file_contents = IO.read(fixture_file(\"animated.gif\"))\n      @file = StringIO.new(file_contents)\n      @file.stubs(:original_filename).returns('image:restricted.gif')\n      @file.binmode\n\n      @attachment.assign(@file)\n      @attachment.save\n      @subject = Paperclip.io_adapters.for(@attachment,\n                                           hash_digest: Digest::MD5)\n    end\n\n    after do\n      @subject.close\n    end\n\n    it \"does not generate paths that include restricted characters\" do\n      expect(@subject.path).to_not match(/:/)\n    end\n\n    it \"does not generate filenames that include restricted characters\" do\n      assert_equal 'image_restricted.gif', @subject.original_filename\n    end\n  end\n\n  context \"for a style\" do\n    before do\n      @file = File.new(fixture_file(\"5k.png\"))\n      @file.binmode\n\n      @attachment.assign(@file)\n\n      @thumb = Tempfile.new(\"thumbnail\").tap(&:binmode)\n      FileUtils.cp @attachment.queued_for_write[:thumb].path, @thumb.path\n\n      @attachment.save\n      @subject = Paperclip.io_adapters.for(@attachment.styles[:thumb],\n                                           hash_digest: Digest::MD5)\n    end\n\n    after do\n      @file.close\n      @thumb.close\n      @subject.close\n    end\n\n    it \"gets the original filename\" do\n      assert_equal \"5k.png\", @subject.original_filename\n    end\n\n    it \"forces binmode on tempfile\" do\n      assert @subject.instance_variable_get(\"@tempfile\").binmode?\n    end\n\n    it \"gets the content type\" do\n      assert_equal \"image/png\", @subject.content_type\n    end\n\n    it \"gets the thumbnail's file size\" do\n      assert_equal @thumb.size, @subject.size\n    end\n\n    it \"returns false for a call to nil?\" do\n      assert ! @subject.nil?\n    end\n\n    it \"generates a MD5 hash of the contents\" do\n      expected = Digest::MD5.file(@thumb.path).to_s\n      assert_equal expected, @subject.fingerprint\n    end\n\n    it \"reads the contents of the thumbnail\" do\n      @thumb.rewind\n      expected = @thumb.read\n      actual = @subject.read\n      assert expected.length > 0\n      assert_equal expected.length, actual.length\n      assert_equal expected, actual\n    end\n\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/data_uri_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::DataUriAdapter do\n  before do\n    Paperclip::DataUriAdapter.register\n  end\n\n  after do\n    Paperclip.io_adapters.unregister(described_class)\n\n    if @subject\n      @subject.close\n    end\n  end\n\n  it 'allows a missing mime-type' do\n    adapter = Paperclip.io_adapters.for(\"data:;base64,#{original_base64_content}\")\n    assert_equal Paperclip::DataUriAdapter, adapter.class\n  end\n\n  it 'alows mime type that has dot in it' do\n    adapter = Paperclip.io_adapters.for(\"data:image/vnd.microsoft.icon;base64,#{original_base64_content}\")\n    assert_equal Paperclip::DataUriAdapter, adapter.class\n  end\n\n  context \"a new instance\" do\n    before do\n      @contents = \"data:image/png;base64,#{original_base64_content}\"\n      @subject = Paperclip.io_adapters.for(@contents, hash_digest: Digest::MD5)\n    end\n\n    it \"returns a nondescript file name\" do\n      assert_equal \"data\", @subject.original_filename\n    end\n\n    it \"returns a content type\" do\n      assert_equal \"image/png\", @subject.content_type\n    end\n\n    it \"returns the size of the data\" do\n      assert_equal 4456, @subject.size\n    end\n\n    it \"generates a correct MD5 hash of the contents\" do\n      assert_equal(\n        Digest::MD5.hexdigest(Base64.decode64(original_base64_content)),\n        @subject.fingerprint\n      )\n    end\n\n    it \"generates correct fingerprint after read\" do\n      fingerprint = Digest::MD5.hexdigest(@subject.read)\n      assert_equal fingerprint, @subject.fingerprint\n    end\n\n    it \"generates same fingerprint\" do\n      assert_equal @subject.fingerprint, @subject.fingerprint\n    end\n\n    it 'accepts a content_type' do\n      @subject.content_type = 'image/png'\n      assert_equal 'image/png', @subject.content_type\n    end\n\n    it 'accepts an original_filename' do\n      @subject.original_filename = 'image.png'\n      assert_equal 'image.png', @subject.original_filename\n    end\n\n    it \"does not generate filenames that include restricted characters\" do\n      @subject.original_filename = 'image:restricted.png'\n      assert_equal 'image_restricted.png', @subject.original_filename\n    end\n\n    it \"does not generate paths that include restricted characters\" do\n      @subject.original_filename = 'image:restricted.png'\n      expect(@subject.path).to_not match(/:/)\n    end\n\n  end\n\n  def original_base64_content\n    Base64.encode64(original_file_contents)\n  end\n\n  def original_file_contents\n    @original_file_contents ||= File.read(fixture_file('5k.png'))\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/empty_string_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::EmptyStringAdapter do\n  context 'a new instance' do\n    before do\n      @subject = Paperclip.io_adapters.for('')\n    end\n\n    it \"returns false for a call to nil?\" do\n      assert !@subject.nil?\n    end\n\n    it 'returns false for a call to assignment?' do\n      assert !@subject.assignment?\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/file_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::FileAdapter do\n  context \"a new instance\" do\n    context \"with normal file\" do\n      before do\n        @file = File.new(fixture_file(\"5k.png\"))\n        @file.binmode\n      end\n\n      after do\n        @file.close\n        @subject.close if @subject\n      end\n\n      context 'doing normal things' do\n        before do\n          @subject = Paperclip.io_adapters.for(@file, hash_digest: Digest::MD5)\n        end\n\n        it 'uses the original filename to generate the tempfile' do\n          assert @subject.path.ends_with?(\".png\")\n        end\n\n        it \"gets the right filename\" do\n          assert_equal \"5k.png\", @subject.original_filename\n        end\n\n        it \"forces binmode on tempfile\" do\n          assert @subject.instance_variable_get(\"@tempfile\").binmode?\n        end\n\n        it \"gets the content type\" do\n          assert_equal \"image/png\", @subject.content_type\n        end\n\n        it \"returns content type as a string\" do\n          expect(@subject.content_type).to be_a String\n        end\n\n        it \"gets the file's size\" do\n          assert_equal 4456, @subject.size\n        end\n\n        it \"returns false for a call to nil?\" do\n          assert ! @subject.nil?\n        end\n\n        it \"generates a MD5 hash of the contents\" do\n          expected = Digest::MD5.file(@file.path).to_s\n          assert_equal expected, @subject.fingerprint\n        end\n\n        it \"reads the contents of the file\" do\n          expected = @file.read\n          assert expected.length > 0\n          assert_equal expected, @subject.read\n        end\n      end\n\n      context \"file with multiple possible content type\" do\n        before do\n          MIME::Types.stubs(:type_for).returns([MIME::Type.new('image/x-png'), MIME::Type.new('image/png')])\n          @subject = Paperclip.io_adapters.for(@file, hash_digest: Digest::MD5)\n        end\n\n        it \"prefers officially registered mime type\" do\n          assert_equal \"image/png\", @subject.content_type\n        end\n\n        it \"returns content type as a string\" do\n          expect(@subject.content_type).to be_a String\n        end\n      end\n\n      context \"file with content type derived from file contents on *nix\" do\n        before do\n          MIME::Types.stubs(:type_for).returns([])\n          Paperclip.stubs(:run).returns(\"application/vnd.ms-office\\n\")\n          Paperclip::ContentTypeDetector.any_instance\n            .stubs(:type_from_mime_magic).returns(\"application/vnd.ms-office\")\n\n          @subject = Paperclip.io_adapters.for(@file)\n        end\n\n        it \"returns content type without newline character\" do\n          assert_equal \"application/vnd.ms-office\", @subject.content_type\n        end\n      end\n    end\n\n    context \"filename with restricted characters\" do\n      before do\n        @file = File.open(fixture_file(\"animated.gif\")) do |file|\n          StringIO.new(file.read)\n        end\n        @file.stubs(:original_filename).returns('image:restricted.gif')\n        @subject = Paperclip.io_adapters.for(@file)\n      end\n\n      after do\n        @file.close\n        @subject.close\n      end\n\n      it \"does not generate filenames that include restricted characters\" do\n        assert_equal 'image_restricted.gif', @subject.original_filename\n      end\n\n      it \"does not generate paths that include restricted characters\" do\n        expect(@subject.path).to_not match(/:/)\n      end\n    end\n\n    context \"empty file\" do\n      before do\n        @file = Tempfile.new(\"file_adapter_test\")\n        @subject = Paperclip.io_adapters.for(@file)\n      end\n\n      after do\n        @file.close\n        @subject.close\n      end\n\n      it \"provides correct mime-type\" do\n        assert_match %r{.*/x-empty}, @subject.content_type\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::HttpUrlProxyAdapter do\n  before do\n    @open_return = StringIO.new(\"xxx\")\n    @open_return.stubs(:meta).returns(\"content-type\" => \"image/png\")\n    Paperclip::HttpUrlProxyAdapter.any_instance.stubs(:download_content).\n      returns(@open_return)\n    Paperclip::HttpUrlProxyAdapter.register\n  end\n\n  after do\n    Paperclip.io_adapters.unregister(described_class)\n  end\n\n  context \"a new instance\" do\n    before do\n      @url = \"http://thoughtbot.com/images/thoughtbot-logo.png\"\n      @subject = Paperclip.io_adapters.for(@url, hash_digest: Digest::MD5)\n    end\n\n    after do\n      @subject.close\n    end\n\n    it \"returns a file name\" do\n      assert_equal \"thoughtbot-logo.png\", @subject.original_filename\n    end\n\n    it 'closes open handle after reading' do\n      assert_equal true, @open_return.closed?\n    end\n\n    it \"returns a content type\" do\n      assert_equal \"image/png\", @subject.content_type\n    end\n\n    it \"returns the size of the data\" do\n      assert_equal @open_return.size, @subject.size\n    end\n\n    it \"generates an MD5 hash of the contents\" do\n      assert_equal Digest::MD5.hexdigest(\"xxx\"), @subject.fingerprint\n    end\n\n    it \"generates correct fingerprint after read\" do\n      fingerprint = Digest::MD5.hexdigest(@subject.read)\n      assert_equal fingerprint, @subject.fingerprint\n    end\n\n    it \"generates same fingerprint\" do\n      assert_equal @subject.fingerprint, @subject.fingerprint\n    end\n\n    it \"returns the data contained in the StringIO\" do\n      assert_equal \"xxx\", @subject.read\n    end\n\n    it 'accepts a content_type' do\n      @subject.content_type = 'image/png'\n      assert_equal 'image/png', @subject.content_type\n    end\n\n    it 'accepts an original_filename' do\n      @subject.original_filename = 'image.png'\n      assert_equal 'image.png', @subject.original_filename\n    end\n  end\n\n  context \"a url with query params\" do\n    subject { Paperclip.io_adapters.for(url) }\n\n    after { subject.close }\n\n    let(:url) { \"https://github.com/thoughtbot/paperclip?file=test\" }\n\n    it \"returns a file name\" do\n      assert_equal \"paperclip\", subject.original_filename\n    end\n\n    it \"preserves params\" do\n      assert_equal url, subject.instance_variable_get(:@target).to_s\n    end\n  end\n\n  context \"a url with restricted characters in the filename\" do\n    before do\n      @url = \"https://github.com/thoughtbot/paper:clip.jpg\"\n      @subject = Paperclip.io_adapters.for(@url)\n    end\n\n    after do\n      begin\n        @subject.close\n      rescue Exception\n        true\n      end\n    end\n\n    it \"does not generate filenames that include restricted characters\" do\n      assert_equal \"paper_clip.jpg\", @subject.original_filename\n    end\n\n    it \"does not generate paths that include restricted characters\" do\n      expect(@subject.path).to_not match(/:/)\n    end\n  end\n\n  context \"a url with special characters in the filename\" do\n    before do\n      Paperclip::HttpUrlProxyAdapter.any_instance.stubs(:download_content).\n        returns(@open_return)\n    end\n\n    let(:filename) do\n      \"paperclip-%C3%B6%C3%A4%C3%BC%E5%AD%97%C2%B4%C2%BD%E2%99%A5\"\\\n        \"%C3%98%C2%B2%C3%88.png\"\n    end\n    let(:url) { \"https://github.com/thoughtbot/paperclip-öäü字´½♥Ø²È.png\" }\n\n    subject { Paperclip.io_adapters.for(url) }\n\n    it \"returns a encoded filename\" do\n      assert_equal filename, subject.original_filename\n    end\n\n    context \"when already URI encoded\" do\n      let(:url) do\n        \"https://github.com/thoughtbot/paperclip-%C3%B6%C3%A4%C3%BC%E5%AD%97\"\\\n        \"%C2%B4%C2%BD%E2%99%A5%C3%98%C2%B2%C3%88.png\"\n      end\n\n      it \"returns a encoded filename\" do\n        assert_equal filename, subject.original_filename\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/identity_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::IdentityAdapter do\n  it \"responds to #new by returning the argument\" do\n    adapter = Paperclip::IdentityAdapter.new\n    assert_equal :target, adapter.new(:target, nil)\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/nil_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::NilAdapter do\n  context 'a new instance' do\n    before do\n      @subject = Paperclip.io_adapters.for(nil)\n    end\n\n    it \"gets the right filename\" do\n      assert_equal \"\", @subject.original_filename\n    end\n\n    it \"gets the content type\" do\n      assert_equal \"\", @subject.content_type\n    end\n\n    it \"gets the file's size\" do\n      assert_equal 0, @subject.size\n    end\n\n    it \"returns true for a call to nil?\" do\n      assert @subject.nil?\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/registry_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::AttachmentRegistry do\n  context \"for\" do\n    before do\n      class AdapterTest\n        def initialize(_target, _ = {}); end\n      end\n      @subject = Paperclip::AdapterRegistry.new\n      @subject.register(AdapterTest){|t| Symbol === t }\n    end\n\n    it \"returns the class registered for the adapted type\" do\n      assert_equal AdapterTest, @subject.for(:target).class\n    end\n  end\n\n  context \"registered?\" do\n    before do\n      class AdapterTest\n        def initialize(_target, _ = {}); end\n      end\n      @subject = Paperclip::AdapterRegistry.new\n      @subject.register(AdapterTest){|t| Symbol === t }\n    end\n\n    it \"returns true when the class of this adapter has been registered\" do\n      assert @subject.registered?(AdapterTest.new(:target))\n    end\n\n    it \"returns false when the adapter has not been registered\" do\n      assert ! @subject.registered?(Object)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/stringio_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::StringioAdapter do\n  context \"a new instance\" do\n    before do\n      @contents = \"abc123\"\n      @stringio = StringIO.new(@contents)\n      @subject = Paperclip.io_adapters.for(@stringio, hash_digest: Digest::MD5)\n    end\n\n    it \"returns a file name\" do\n      assert_equal \"data\", @subject.original_filename\n    end\n\n    it \"returns a content type\" do\n      assert_equal \"text/plain\", @subject.content_type\n    end\n\n    it \"returns the size of the data\" do\n      assert_equal 6, @subject.size\n    end\n\n    it \"returns the length of the data\" do\n      assert_equal 6, @subject.length\n    end\n\n    it \"generates an MD5 hash of the contents\" do\n      assert_equal Digest::MD5.hexdigest(@contents), @subject.fingerprint\n    end\n\n    it \"generates correct fingerprint after read\" do\n      fingerprint = Digest::MD5.hexdigest(@subject.read)\n      assert_equal fingerprint, @subject.fingerprint\n    end\n\n    it \"generates same fingerprint\" do\n      assert_equal @subject.fingerprint, @subject.fingerprint\n    end\n\n    it \"returns the data contained in the StringIO\" do\n      assert_equal \"abc123\", @subject.read\n    end\n\n    it 'accepts a content_type' do\n      @subject.content_type = 'image/png'\n      assert_equal 'image/png', @subject.content_type\n    end\n\n    it 'accepts an original_filename' do\n      @subject.original_filename = 'image.png'\n      assert_equal 'image.png', @subject.original_filename\n    end\n\n    it \"does not generate filenames that include restricted characters\" do\n      @subject.original_filename = 'image:restricted.png'\n      assert_equal 'image_restricted.png', @subject.original_filename\n    end\n\n    it \"does not generate paths that include restricted characters\" do\n      @subject.original_filename = 'image:restricted.png'\n      expect(@subject.path).to_not match(/:/)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::UploadedFileAdapter do\n  context \"a new instance\" do\n    context \"with UploadedFile responding to #tempfile\" do\n      before do\n        Paperclip::UploadedFileAdapter.content_type_detector = nil\n\n        class UploadedFile < OpenStruct; end\n        tempfile = File.new(fixture_file(\"5k.png\"))\n        tempfile.binmode\n\n        @file = UploadedFile.new(\n          original_filename: \"5k.png\",\n          content_type: \"image/x-png-by-browser\\r\",\n          head: \"\",\n          tempfile: tempfile,\n          path: tempfile.path\n        )\n        @subject = Paperclip.io_adapters.for(@file, hash_digest: Digest::MD5)\n      end\n\n      it \"gets the right filename\" do\n        assert_equal \"5k.png\", @subject.original_filename\n      end\n\n      it \"forces binmode on tempfile\" do\n        assert @subject.instance_variable_get(\"@tempfile\").binmode?\n      end\n\n      it \"gets the content type\" do\n        assert_equal \"image/png\", @subject.content_type\n      end\n\n      it \"gets the file's size\" do\n        assert_equal 4456, @subject.size\n      end\n\n      it \"returns false for a call to nil?\" do\n        assert ! @subject.nil?\n      end\n\n      it \"generates a MD5 hash of the contents\" do\n        expected = Digest::MD5.file(@file.tempfile.path).to_s\n        assert_equal expected, @subject.fingerprint\n      end\n\n      it \"reads the contents of the file\" do\n        expected = @file.tempfile.read\n        assert expected.length > 0\n        assert_equal expected, @subject.read\n      end\n    end\n\n    context \"with UploadedFile that has restricted characters\" do\n      before do\n        Paperclip::UploadedFileAdapter.content_type_detector = nil\n\n        class UploadedFile < OpenStruct; end\n        @file = UploadedFile.new(\n          original_filename: \"image:restricted.gif\",\n          content_type: \"image/x-png-by-browser\",\n          head: \"\",\n          path: fixture_file(\"5k.png\")\n        )\n        @subject = Paperclip.io_adapters.for(@file, hash_digest: Digest::MD5)\n      end\n\n      it \"does not generate paths that include restricted characters\" do\n        expect(@subject.path).to_not match(/:/)\n      end\n\n      it \"does not generate filenames that include restricted characters\" do\n        assert_equal 'image_restricted.gif', @subject.original_filename\n      end\n    end\n\n    context \"with UploadFile responding to #path\" do\n      before do\n        Paperclip::UploadedFileAdapter.content_type_detector = nil\n\n        class UploadedFile < OpenStruct; end\n        @file = UploadedFile.new(\n          original_filename: \"5k.png\",\n          content_type: \"image/x-png-by-browser\",\n          head: \"\",\n          path: fixture_file(\"5k.png\")\n        )\n        @subject = Paperclip.io_adapters.for(@file, hash_digest: Digest::MD5)\n      end\n\n      it \"gets the right filename\" do\n        assert_equal \"5k.png\", @subject.original_filename\n      end\n\n      it \"forces binmode on tempfile\" do\n        assert @subject.instance_variable_get(\"@tempfile\").binmode?\n      end\n\n      it \"gets the content type\" do\n        assert_equal \"image/png\", @subject.content_type\n      end\n\n      it \"gets the file's size\" do\n        assert_equal 4456, @subject.size\n      end\n\n      it \"returns false for a call to nil?\" do\n        assert ! @subject.nil?\n      end\n\n      it \"generates a MD5 hash of the contents\" do\n        expected = Digest::MD5.file(@file.path).to_s\n        assert_equal expected, @subject.fingerprint\n      end\n\n      it \"reads the contents of the file\" do\n        expected_file = File.new(@file.path)\n        expected_file.binmode\n        expected = expected_file.read\n        assert expected.length > 0\n        assert_equal expected, @subject.read\n      end\n\n      context \"don't trust client-given MIME type\" do\n        before do\n          Paperclip::UploadedFileAdapter.content_type_detector =\n            Paperclip::FileCommandContentTypeDetector\n\n          class UploadedFile < OpenStruct; end\n          @file = UploadedFile.new(\n            original_filename: \"5k.png\",\n            content_type: \"image/x-png-by-browser\",\n            head: \"\",\n            path: fixture_file(\"5k.png\")\n          )\n          @subject = Paperclip.io_adapters.for(@file)\n        end\n\n        it \"gets the content type\" do\n          assert_equal \"image/png\", @subject.content_type\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/io_adapters/uri_adapter_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::UriAdapter do\n  let(:content_type) { \"image/png\" }\n  let(:meta) { {} }\n\n  before do\n    @open_return = StringIO.new(\"xxx\")\n    @open_return.stubs(:content_type).returns(content_type)\n    @open_return.stubs(:meta).returns(meta)\n    Paperclip::UriAdapter.register\n  end\n\n  after do\n    Paperclip.io_adapters.unregister(described_class)\n  end\n\n  context \"a new instance\" do\n    let(:meta) { { \"content-type\" => \"image/png\" } }\n\n    before do\n      Paperclip::UriAdapter.any_instance.\n        stubs(:download_content).returns(@open_return)\n\n      @uri = URI.parse(\"http://thoughtbot.com/images/thoughtbot-logo.png\")\n      @subject = Paperclip.io_adapters.for(@uri, hash_digest: Digest::MD5)\n    end\n\n    it \"returns a file name\" do\n      assert_equal \"thoughtbot-logo.png\", @subject.original_filename\n    end\n\n    it 'closes open handle after reading' do\n      assert_equal true, @open_return.closed?\n    end\n\n    it \"returns a content type\" do\n      assert_equal \"image/png\", @subject.content_type\n    end\n\n    it \"returns the size of the data\" do\n      assert_equal @open_return.size, @subject.size\n    end\n\n    it \"generates an MD5 hash of the contents\" do\n      assert_equal Digest::MD5.hexdigest(\"xxx\"), @subject.fingerprint\n    end\n\n    it \"generates correct fingerprint after read\" do\n      fingerprint = Digest::MD5.hexdigest(@subject.read)\n      assert_equal fingerprint, @subject.fingerprint\n    end\n\n    it \"generates same fingerprint\" do\n      assert_equal @subject.fingerprint, @subject.fingerprint\n    end\n\n    it \"returns the data contained in the StringIO\" do\n      assert_equal \"xxx\", @subject.read\n    end\n\n    it 'accepts a content_type' do\n      @subject.content_type = 'image/png'\n      assert_equal 'image/png', @subject.content_type\n    end\n\n    it \"accepts an original_filename\" do\n      @subject.original_filename = 'image.png'\n      assert_equal 'image.png', @subject.original_filename\n    end\n\n  end\n\n  context \"a directory index url\" do\n    let(:content_type) { \"text/html\" }\n    let(:meta) { { \"content-type\" => \"text/html\" } }\n\n    before do\n      Paperclip::UriAdapter.any_instance.\n        stubs(:download_content).returns(@open_return)\n\n      @uri = URI.parse(\"http://thoughtbot.com\")\n      @subject = Paperclip.io_adapters.for(@uri)\n    end\n\n    it \"returns a file name\" do\n      assert_equal \"index.html\", @subject.original_filename\n    end\n\n    it \"returns a content type\" do\n      assert_equal \"text/html\", @subject.content_type\n    end\n  end\n\n  context \"a url with query params\" do\n    before do\n      Paperclip::UriAdapter.any_instance.\n        stubs(:download_content).returns(@open_return)\n\n      @uri = URI.parse(\"https://github.com/thoughtbot/paperclip?file=test\")\n      @subject = Paperclip.io_adapters.for(@uri)\n    end\n\n    it \"returns a file name\" do\n      assert_equal \"paperclip\", @subject.original_filename\n    end\n  end\n\n  context \"a url with content disposition headers\" do\n    let(:file_name) { \"test_document.pdf\" }\n    let(:filename_from_path) { \"paperclip\" }\n\n    before do\n      Paperclip::UriAdapter.any_instance.\n        stubs(:download_content).returns(@open_return)\n\n      @uri = URI.parse(\n        \"https://github.com/thoughtbot/#{filename_from_path}?file=test\")\n    end\n\n    it \"returns file name from path\" do\n      meta[\"content-disposition\"] = \"inline;\"\n\n      @subject = Paperclip.io_adapters.for(@uri)\n\n      assert_equal filename_from_path, @subject.original_filename\n    end\n\n    it \"returns a file name enclosed in double quotes\" do\n      file_name = \"john's test document.pdf\"\n      meta[\"content-disposition\"] = \"attachment; filename=\\\"#{file_name}\\\";\"\n\n      @subject = Paperclip.io_adapters.for(@uri)\n\n      assert_equal file_name, @subject.original_filename\n    end\n\n    it \"returns a file name not enclosed in double quotes\" do\n      meta[\"content-disposition\"] = \"ATTACHMENT; FILENAME=#{file_name};\"\n\n      @subject = Paperclip.io_adapters.for(@uri)\n\n      assert_equal file_name, @subject.original_filename\n    end\n\n    it \"does not crash when an empty filename is given\" do\n      meta[\"content-disposition\"] = \"ATTACHMENT; FILENAME=\\\"\\\";\"\n\n      @subject = Paperclip.io_adapters.for(@uri)\n\n      assert_equal \"\", @subject.original_filename\n    end\n\n    it \"returns a file name ignoring RFC 5987 encoding\" do\n      meta[\"content-disposition\"] =\n        \"attachment; filename=#{file_name}; filename* = utf-8''%e2%82%ac%20rates\"\n\n      @subject = Paperclip.io_adapters.for(@uri)\n\n      assert_equal file_name, @subject.original_filename\n    end\n\n    context \"when file name has consecutive periods\" do\n      let(:file_name) { \"test_document..pdf\" }\n\n      it \"returns a file name\" do\n        @uri = URI.parse(\n          \"https://github.com/thoughtbot/#{file_name}?file=test\")\n        @subject = Paperclip.io_adapters.for(@uri)\n        assert_equal file_name, @subject.original_filename\n      end\n    end\n  end\n\n  context \"a url with restricted characters in the filename\" do\n    before do\n      Paperclip::UriAdapter.any_instance.\n        stubs(:download_content).returns(@open_return)\n\n      @uri = URI.parse(\"https://github.com/thoughtbot/paper:clip.jpg\")\n      @subject = Paperclip.io_adapters.for(@uri)\n    end\n\n    it \"does not generate filenames that include restricted characters\" do\n      assert_equal \"paper_clip.jpg\", @subject.original_filename\n    end\n\n    it \"does not generate paths that include restricted characters\" do\n      expect(@subject.path).to_not match(/:/)\n    end\n  end\n\n  describe \"#download_content\" do\n    before do\n      Paperclip::UriAdapter.any_instance.stubs(:open).returns(@open_return)\n      @uri = URI.parse(\"https://github.com/thoughtbot/paper:clip.jpg\")\n      @subject = Paperclip.io_adapters.for(@uri)\n    end\n\n    after do\n      @subject.send(:download_content)\n    end\n\n    context \"with default read_timeout\" do\n      it \"calls open without options\" do\n        @subject.expects(:open).with(@uri, {}).at_least_once\n      end\n    end\n\n    context \"with custom read_timeout\" do\n      before do\n        Paperclip.options[:read_timeout] = 120\n      end\n\n      it \"calls open with read_timeout option\" do\n        @subject.expects(:open).with(@uri, read_timeout: 120).at_least_once\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/matchers/have_attached_file_matcher_spec.rb",
    "content": "require 'spec_helper'\nrequire 'paperclip/matchers'\n\ndescribe Paperclip::Shoulda::Matchers::HaveAttachedFileMatcher do\n  extend Paperclip::Shoulda::Matchers\n\n  it \"rejects the dummy class if it has no attachment\" do\n    reset_table \"dummies\"\n    reset_class \"Dummy\"\n    matcher = self.class.have_attached_file(:avatar)\n    expect(matcher).to_not accept(Dummy)\n  end\n\n  it 'accepts the dummy class if it has an attachment' do\n    rebuild_model\n    matcher = self.class.have_attached_file(:avatar)\n    expect(matcher).to accept(Dummy)\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb",
    "content": "require 'spec_helper'\nrequire 'paperclip/matchers'\n\ndescribe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do\n  extend Paperclip::Shoulda::Matchers\n\n  before do\n    reset_table(\"dummies\") do |d|\n      d.string :title\n      d.string :avatar_file_name\n      d.string :avatar_content_type\n    end\n    reset_class \"Dummy\"\n    Dummy.do_not_validate_attachment_file_type :avatar\n    Dummy.has_attached_file :avatar\n  end\n\n  it \"rejects a class with no validation\" do\n    expect(matcher).to_not accept(Dummy)\n    expect { matcher.failure_message }.to_not raise_error\n  end\n\n  it 'rejects a class when the validation fails' do\n    Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}\n    expect(matcher).to_not accept(Dummy)\n    expect { matcher.failure_message }.to_not raise_error\n  end\n\n  it \"accepts a class with a matching validation\" do\n    Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}\n    expect(matcher).to accept(Dummy)\n    expect { matcher.failure_message }.to_not raise_error\n  end\n\n  it \"accepts a class with other validations but matching types\" do\n    Dummy.validates_presence_of :title\n    Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}\n    expect(matcher).to accept(Dummy)\n    expect { matcher.failure_message }.to_not raise_error\n  end\n\n  it \"accepts a class that matches and a matcher that only specifies 'allowing'\" do\n    Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}\n    matcher = plain_matcher.allowing(%w(image/png image/jpeg))\n\n    expect(matcher).to accept(Dummy)\n    expect { matcher.failure_message }.to_not raise_error\n  end\n\n  it \"rejects a class that does not match and a matcher that only specifies 'allowing'\" do\n    Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}\n    matcher = plain_matcher.allowing(%w(image/png image/jpeg))\n\n    expect(matcher).to_not accept(Dummy)\n    expect { matcher.failure_message }.to_not raise_error\n  end\n\n  it \"accepts a class that matches and a matcher that only specifies 'rejecting'\" do\n    Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}\n    matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))\n\n    expect(matcher).to accept(Dummy)\n    expect { matcher.failure_message }.to_not raise_error\n  end\n\n  it \"rejects a class that does not match and a matcher that only specifies 'rejecting'\" do\n    Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}\n    matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))\n\n    expect(matcher).to_not accept(Dummy)\n    expect { matcher.failure_message }.to_not raise_error\n  end\n\n  context \"using an :if to control the validation\" do\n    before do\n      Dummy.class_eval do\n        validates_attachment_content_type :avatar, content_type: %r{image/*} , if: :go\n        attr_accessor :go\n      end\n    end\n\n    it \"runs the validation if the control is true\" do\n      dummy = Dummy.new\n      dummy.go = true\n      expect(matcher).to accept(dummy)\n      expect { matcher.failure_message }.to_not raise_error\n    end\n\n    it \"does not run the validation if the control is false\" do\n      dummy = Dummy.new\n      dummy.go = false\n      expect(matcher).to_not accept(dummy)\n      expect { matcher.failure_message }.to_not raise_error\n    end\n  end\n\n  private\n\n  def plain_matcher\n    self.class.validate_attachment_content_type(:avatar)\n  end\n\n  def matcher\n    plain_matcher.\n      allowing(%w(image/png image/jpeg)).\n      rejecting(%w(audio/mp3 application/octet-stream))\n  end\n\nend\n"
  },
  {
    "path": "spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb",
    "content": "require 'spec_helper'\nrequire 'paperclip/matchers'\n\ndescribe Paperclip::Shoulda::Matchers::ValidateAttachmentPresenceMatcher do\n  extend Paperclip::Shoulda::Matchers\n\n  before do\n    reset_table(\"dummies\") do |d|\n      d.string :avatar_file_name\n    end\n    reset_class \"Dummy\"\n    Dummy.has_attached_file :avatar\n    Dummy.do_not_validate_attachment_file_type :avatar\n  end\n\n  it \"rejects a class with no validation\" do\n    expect(matcher).to_not accept(Dummy)\n  end\n\n  it \"accepts a class with a matching validation\" do\n    Dummy.validates_attachment_presence :avatar\n    expect(matcher).to accept(Dummy)\n  end\n\n  it \"accepts an instance with other attachment validations\" do\n    reset_table(\"dummies\") do |d|\n      d.string :avatar_file_name\n      d.string :avatar_content_type\n    end\n    Dummy.class_eval do\n      validates_attachment_presence :avatar\n      validates_attachment_content_type :avatar, content_type: 'image/gif'\n    end\n    dummy = Dummy.new\n\n    dummy.avatar = File.new fixture_file('5k.png')\n\n    expect(matcher).to accept(dummy)\n  end\n\n  context \"using an :if to control the validation\" do\n    before do\n      Dummy.class_eval do\n        validates_attachment_presence :avatar, if: :go\n        attr_accessor :go\n      end\n    end\n\n    it \"runs the validation if the control is true\" do\n      dummy = Dummy.new\n      dummy.avatar = nil\n      dummy.go = true\n      expect(matcher).to accept(dummy)\n    end\n\n    it \"does not run the validation if the control is false\" do\n      dummy = Dummy.new\n      dummy.avatar = nil\n      dummy.go = false\n      expect(matcher).to_not accept(dummy)\n    end\n  end\n\n  private\n\n  def matcher\n    self.class.validate_attachment_presence(:avatar)\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb",
    "content": "require 'spec_helper'\nrequire 'paperclip/matchers'\n\ndescribe Paperclip::Shoulda::Matchers::ValidateAttachmentSizeMatcher do\n  extend Paperclip::Shoulda::Matchers\n\n  before do\n    reset_table(\"dummies\") do |d|\n      d.string :avatar_file_name\n      d.bigint :avatar_file_size\n    end\n    reset_class \"Dummy\"\n    Dummy.do_not_validate_attachment_file_type :avatar\n    Dummy.has_attached_file :avatar\n  end\n\n  context \"Limiting size\" do\n    it \"rejects a class with no validation\" do\n      expect(matcher.in(256..1024)).to_not accept(Dummy)\n    end\n\n    it \"rejects a class with a validation that's too high\" do\n      Dummy.validates_attachment_size :avatar, in: 256..2048\n      expect(matcher.in(256..1024)).to_not accept(Dummy)\n    end\n\n    it \"accepts a class with a validation that's too low\" do\n      Dummy.validates_attachment_size :avatar, in: 0..1024\n      expect(matcher.in(256..1024)).to_not accept(Dummy)\n    end\n\n    it \"accepts a class with a validation that matches\" do\n      Dummy.validates_attachment_size :avatar, in: 256..1024\n      expect(matcher.in(256..1024)).to accept(Dummy)\n    end\n  end\n\n  context \"allowing anything\" do\n    it \"given a class with an upper limit\" do\n      Dummy.validates_attachment_size :avatar, less_than: 1\n      expect(matcher).to accept(Dummy)\n    end\n\n    it \"given a class with a lower limit\" do\n      Dummy.validates_attachment_size :avatar, greater_than: 1\n      expect(matcher).to accept(Dummy)\n    end\n  end\n\n  context \"using an :if to control the validation\" do\n    before do\n      Dummy.class_eval do\n        validates_attachment_size :avatar, greater_than: 1024, if: :go\n        attr_accessor :go\n      end\n    end\n\n    it \"run the validation if the control is true\" do\n      dummy = Dummy.new\n      dummy.go = true\n      expect(matcher.greater_than(1024)).to accept(dummy)\n    end\n\n    it \"not run the validation if the control is false\" do\n      dummy = Dummy.new\n      dummy.go = false\n      expect(matcher.greater_than(1024)).to_not accept(dummy)\n    end\n  end\n\n  context \"post processing\" do\n    before do\n      Dummy.validates_attachment_size :avatar, greater_than: 1024\n    end\n\n    it \"be skipped\" do\n      dummy = Dummy.new\n      dummy.avatar.expects(:post_process).never\n      expect(matcher.greater_than(1024)).to accept(dummy)\n    end\n  end\n\n  private\n\n  def matcher\n    self.class.validate_attachment_size(:avatar)\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/media_type_spoof_detector_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::MediaTypeSpoofDetector do\n  it 'rejects a file that is named .html and identifies as PNG' do\n    file = File.open(fixture_file(\"5k.png\"))\n    assert Paperclip::MediaTypeSpoofDetector.using(file, \"5k.html\", \"image/png\").spoofed?\n  end\n\n  it 'does not reject a file that is named .jpg and identifies as PNG' do\n    file = File.open(fixture_file(\"5k.png\"))\n    assert ! Paperclip::MediaTypeSpoofDetector.using(file, \"5k.jpg\", \"image/png\").spoofed?\n  end\n\n  it 'does not reject a file that is named .html and identifies as HTML' do\n    file = File.open(fixture_file(\"empty.html\"))\n    assert ! Paperclip::MediaTypeSpoofDetector.using(file, \"empty.html\", \"text/html\").spoofed?\n  end\n\n  it 'does not reject a file that does not have a name' do\n    file = File.open(fixture_file(\"empty.html\"))\n    assert ! Paperclip::MediaTypeSpoofDetector.using(file, \"\", \"text/html\").spoofed?\n  end\n\n  it 'does not reject a file that does have an extension' do\n    file = File.open(fixture_file(\"empty.html\"))\n    assert ! Paperclip::MediaTypeSpoofDetector.using(file, \"data\", \"text/html\").spoofed?\n  end\n\n  it 'does not reject when the supplied file is an IOAdapter' do\n    adapter = Paperclip.io_adapters.for(File.new(fixture_file(\"5k.png\")))\n    assert ! Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename, adapter.content_type).spoofed?\n  end\n\n  it 'does not reject when the extension => content_type is in :content_type_mappings' do\n    begin\n      Paperclip.options[:content_type_mappings] = { pem: \"text/plain\" }\n      file = Tempfile.open([\"test\", \".PEM\"])\n      file.puts \"Certificate!\"\n      file.close\n      adapter = Paperclip.io_adapters.for(File.new(file.path));\n      assert ! Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename, adapter.content_type).spoofed?\n    ensure\n      Paperclip.options[:content_type_mappings] = {}\n    end\n  end\n\n  context \"file named .html and is as HTML, but we're told JPG\" do\n    let(:file) { File.open(fixture_file(\"empty.html\")) }\n    let(:spoofed?) { Paperclip::MediaTypeSpoofDetector.using(file, \"empty.html\", \"image/jpg\").spoofed? }\n\n    it \"rejects the file\" do\n      assert spoofed?\n    end\n\n    it \"logs info about the detected spoof\" do\n      Paperclip.expects(:log).with('Content Type Spoof: Filename empty.html (image/jpg from Headers, [\"text/html\"] from Extension), content type discovered from file command: text/html. See documentation to allow this combination.')\n      spoofed?\n    end\n  end\n\n  context \"GIF file named without extension, but we're told GIF\" do\n    let(:file) { File.open(fixture_file(\"animated\")) }\n    let(:spoofed?) do\n      Paperclip::MediaTypeSpoofDetector.\n        using(file, \"animated\", \"image/gif\").\n        spoofed?\n    end\n\n    it \"accepts the file\" do\n      assert !spoofed?\n    end\n  end\n\n  context \"GIF file named without extension, but we're told HTML\" do\n    let(:file) { File.open(fixture_file(\"animated\")) }\n    let(:spoofed?) do\n      Paperclip::MediaTypeSpoofDetector.\n        using(file, \"animated\", \"text/html\").\n        spoofed?\n    end\n\n    it \"rejects the file\" do\n      assert spoofed?\n    end\n  end\n\n  it \"does not reject if content_type is empty but otherwise checks out\" do\n    file = File.open(fixture_file(\"empty.html\"))\n    assert ! Paperclip::MediaTypeSpoofDetector.using(file, \"empty.html\", \"\").spoofed?\n  end\n\n  it 'does allow array as :content_type_mappings' do\n    begin\n      Paperclip.options[:content_type_mappings] = {\n        html: ['binary', 'text/html']\n      }\n      file = File.open(fixture_file('empty.html'))\n      spoofed = Paperclip::MediaTypeSpoofDetector\n                .using(file, \"empty.html\", \"text/html\").spoofed?\n      assert !spoofed\n    ensure\n      Paperclip.options[:content_type_mappings] = {}\n    end\n  end\n\n  context \"#type_from_file_command\" do\n    let(:file) { File.new(fixture_file(\"empty.html\")) }\n    let(:detector) { Paperclip::MediaTypeSpoofDetector.new(file, \"html\", \"\") }\n\n    it \"does work with the output of old versions of file\" do\n      Paperclip.stubs(:run).returns(\"text/html charset=us-ascii\")\n      expect(detector.send(:type_from_file_command)).to eq(\"text/html\")\n    end\n\n    it \"does work with the output of new versions of file\" do\n      Paperclip.stubs(:run).returns(\"text/html; charset=us-ascii\")\n      expect(detector.send(:type_from_file_command)).to eq(\"text/html\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/meta_class_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe 'Metaclasses' do\n  context \"A meta-class of dummy\" do\n    if active_support_version >= \"4.1\" || ruby_version < \"2.1\"\n      before do\n        rebuild_model\n        reset_class(\"Dummy\")\n      end\n\n      it \"is able to use Paperclip like a normal class\" do\n        @dummy = Dummy.new\n\n        assert_nothing_raised do\n          rebuild_meta_class_of(@dummy)\n        end\n      end\n\n      it \"works like any other instance\" do\n        @dummy = Dummy.new\n        rebuild_meta_class_of(@dummy)\n\n        assert_nothing_raised do\n          @dummy.avatar = File.new(fixture_file(\"5k.png\"), 'rb')\n        end\n        assert @dummy.save\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/paperclip_missing_attachment_styles_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe 'Missing Attachment Styles' do\n  before do\n    Paperclip::AttachmentRegistry.clear\n  end\n\n  after do\n    File.unlink(Paperclip.registered_attachments_styles_path) rescue nil\n  end\n\n  it \"enables to get and set path to registered styles file\" do\n    assert_equal ROOT.join('tmp/public/system/paperclip_attachments.yml').to_s, Paperclip.registered_attachments_styles_path\n    Paperclip.registered_attachments_styles_path = '/tmp/config/paperclip_attachments.yml'\n    assert_equal '/tmp/config/paperclip_attachments.yml', Paperclip.registered_attachments_styles_path\n    Paperclip.registered_attachments_styles_path = nil\n    assert_equal ROOT.join('tmp/public/system/paperclip_attachments.yml').to_s, Paperclip.registered_attachments_styles_path\n  end\n\n  it \"is able to get current attachment styles\" do\n    assert_equal Hash.new, Paperclip.send(:current_attachments_styles)\n    rebuild_model styles: {croppable: '600x600>', big: '1000x1000>'}\n    expected_hash = { Dummy: {avatar: [:big, :croppable]}}\n    assert_equal expected_hash, Paperclip.send(:current_attachments_styles)\n  end\n\n  it \"is able to save current attachment styles for further comparison\" do\n    rebuild_model styles: {croppable: '600x600>', big: '1000x1000>'}\n    Paperclip.save_current_attachments_styles!\n    expected_hash = { Dummy: {avatar: [:big, :croppable]}}\n    assert_equal expected_hash, YAML.load_file(Paperclip.registered_attachments_styles_path)\n  end\n\n  it \"is able to read registered attachment styles from file\" do\n    rebuild_model styles: {croppable: '600x600>', big: '1000x1000>'}\n    Paperclip.save_current_attachments_styles!\n    expected_hash = { Dummy: {avatar: [:big, :croppable]}}\n    assert_equal expected_hash, Paperclip.send(:get_registered_attachments_styles)\n  end\n\n  it \"is able to calculate differences between registered styles and current styles\" do\n    rebuild_model styles: {croppable: '600x600>', big: '1000x1000>'}\n    Paperclip.save_current_attachments_styles!\n    rebuild_model styles: {thumb: 'x100', export: 'x400>', croppable: '600x600>', big: '1000x1000>'}\n    expected_hash = { Dummy: {avatar: [:export, :thumb]} }\n    assert_equal expected_hash, Paperclip.missing_attachments_styles\n\n    ActiveRecord::Base.connection.create_table :books, force: true\n    class ::Book < ActiveRecord::Base\n      has_attached_file :cover, styles: {small: 'x100', large: '1000x1000>'}\n      has_attached_file :sample, styles: {thumb: 'x100'}\n    end\n\n    expected_hash = {\n      Dummy: {avatar: [:export, :thumb]},\n      Book: {sample: [:thumb], cover: [:large, :small]}\n    }\n    assert_equal expected_hash, Paperclip.missing_attachments_styles\n    Paperclip.save_current_attachments_styles!\n    assert_equal Hash.new, Paperclip.missing_attachments_styles\n  end\n\n  it \"is able to calculate differences when a new attachment is added to a model\" do\n    rebuild_model styles: {croppable: '600x600>', big: '1000x1000>'}\n    Paperclip.save_current_attachments_styles!\n\n    class ::Dummy\n      has_attached_file :photo, styles: {small: 'x100', large: '1000x1000>'}\n    end\n\n    expected_hash = {\n      Dummy: {photo: [:large, :small]}\n    }\n    assert_equal expected_hash, Paperclip.missing_attachments_styles\n    Paperclip.save_current_attachments_styles!\n    assert_equal Hash.new, Paperclip.missing_attachments_styles\n  end\n\n  # It's impossible to build styles hash without loading from database whole bunch of records\n  it \"skips lambda-styles\" do\n    rebuild_model styles: lambda{ |attachment| attachment.instance.other == 'a' ? {thumb: \"50x50#\"} : {large: \"400x400\"} }\n    assert_equal Hash.new, Paperclip.send(:current_attachments_styles)\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/paperclip_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip do\n  context \".run\" do\n    before do\n      Paperclip.options[:log_command] = false\n      Terrapin::CommandLine.expects(:new).with(\"convert\", \"stuff\", {}).returns(stub(:run))\n      @original_command_line_path = Terrapin::CommandLine.path\n    end\n\n    after do\n      Paperclip.options[:log_command] = true\n      Terrapin::CommandLine.path = @original_command_line_path\n    end\n\n    it \"runs the command with Terrapin\" do\n      Paperclip.run(\"convert\", \"stuff\")\n    end\n\n    it \"saves Terrapin::CommandLine.path that set before\" do\n      Terrapin::CommandLine.path = \"/opt/my_app/bin\"\n      Paperclip.run(\"convert\", \"stuff\")\n      expect(Terrapin::CommandLine.path).to match(\"/opt/my_app/bin\")\n    end\n\n    it \"does not duplicate Terrapin::CommandLine.path on multiple runs\" do\n      Terrapin::CommandLine.expects(:new).with(\"convert\", \"more_stuff\", {}).returns(stub(:run))\n      Terrapin::CommandLine.path = nil\n      Paperclip.options[:command_path] = \"/opt/my_app/bin\"\n      Paperclip.run(\"convert\", \"stuff\")\n      Paperclip.run(\"convert\", \"more_stuff\")\n\n      cmd_path = Paperclip.options[:command_path]\n      assert_equal 1, Terrapin::CommandLine.path.scan(cmd_path).count\n    end\n  end\n\n  it 'does not raise errors when doing a lot of running' do\n    Paperclip.options[:command_path] = [\"/usr/local/bin\"] * 1024\n    Terrapin::CommandLine.path = \"/something/else\"\n    100.times do |x|\n      Paperclip.run(\"echo\", x.to_s)\n    end\n  end\n\n  context \"Calling Paperclip.log without options[:logger] set\" do\n    before do\n      Paperclip.logger = nil\n      Paperclip.options[:logger] = nil\n    end\n\n    after do\n      Paperclip.options[:logger] = ActiveRecord::Base.logger\n      Paperclip.logger = ActiveRecord::Base.logger\n    end\n\n    it \"does not raise an error when log is called\" do\n      silence_stream(STDOUT) do\n        Paperclip.log('something')\n      end\n    end\n  end\n  context \"Calling Paperclip.run with a logger\" do\n    it \"passes the defined logger if :log_command is set\" do\n      Paperclip.options[:log_command] = true\n      Terrapin::CommandLine.expects(:new).with(\"convert\", \"stuff\", logger: Paperclip.logger).returns(stub(:run))\n      Paperclip.run(\"convert\", \"stuff\")\n    end\n  end\n\n  context \"Paperclip.each_instance_with_attachment\" do\n    before do\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n      d1 = Dummy.create(avatar: @file)\n      d2 = Dummy.create\n      d3 = Dummy.create(avatar: @file)\n      @expected = [d1, d3]\n    end\n\n    after { @file.close }\n\n    it \"yields every instance of a model that has an attachment\" do\n      actual = []\n      Paperclip.each_instance_with_attachment(\"Dummy\", \"avatar\") do |instance|\n        actual << instance\n      end\n      expect(actual).to match_array @expected\n    end\n  end\n\n  it \"raises when sent #processor and the name of a class that doesn't exist\" do\n    assert_raises(LoadError){ Paperclip.processor(:boogey_man) }\n  end\n\n  it \"returns a class when sent #processor and the name of a class under Paperclip\" do\n    assert_equal ::Paperclip::Thumbnail, Paperclip.processor(:thumbnail)\n  end\n\n  it \"gets a class from a namespaced class name\" do\n    class ::One; class Two; end; end\n    assert_equal ::One::Two, Paperclip.class_for(\"One::Two\")\n  end\n\n  it \"raises when class doesn't exist in specified namespace\" do\n    class ::Three; end\n    class ::Four; end\n    assert_raises NameError do\n      Paperclip.class_for(\"Three::Four\")\n    end\n  end\n\n  context \"An ActiveRecord model with an 'avatar' attachment\" do\n    before do\n      rebuild_model path: \"tmp/:class/omg/:style.:extension\"\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n    end\n\n    after { @file.close }\n\n    it \"does not error when trying to also create a 'blah' attachment\" do\n      assert_nothing_raised do\n        Dummy.class_eval do\n          has_attached_file :blah\n        end\n      end\n    end\n\n    context \"with a subclass\" do\n      before do\n        class ::SubDummy < Dummy; end\n      end\n\n      it \"is able to use the attachment from the subclass\" do\n        assert_nothing_raised do\n          @subdummy = SubDummy.create(avatar: @file)\n        end\n      end\n\n      after do\n        SubDummy.delete_all\n        Object.send(:remove_const, \"SubDummy\") rescue nil\n      end\n    end\n\n    it \"has an avatar getter method\" do\n      assert Dummy.new.respond_to?(:avatar)\n    end\n\n    it \"has an avatar setter method\" do\n      assert Dummy.new.respond_to?(:avatar=)\n    end\n\n    context \"that is valid\" do\n      before do\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      it \"is valid\" do\n        assert @dummy.valid?\n      end\n    end\n\n    it \"does not have Attachment in the ActiveRecord::Base namespace\" do\n      assert_raises(NameError) do\n        ActiveRecord::Base::Attachment\n      end\n    end\n  end\n\n  context \"configuring a custom processor\" do\n    before do\n      @freedom_processor = Class.new do\n        def make(file, options = {}, attachment = nil)\n          file\n        end\n      end.new\n\n      Paperclip.configure do |config|\n        config.register_processor(:freedom, @freedom_processor)\n      end\n    end\n\n    it \"is able to find the custom processor\" do\n      assert_equal @freedom_processor, Paperclip.processor(:freedom)\n    end\n\n    after do\n      Paperclip.clear_processors!\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/plural_cache_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe 'Plural cache' do\n  it 'caches pluralizations' do\n    cache = Paperclip::Interpolations::PluralCache.new\n    symbol = :box\n\n    first = cache.pluralize_symbol(symbol)\n    second = cache.pluralize_symbol(symbol)\n    expect(first).to equal(second)\n  end\n\n  it 'caches pluralizations and underscores' do\n    class BigBox ; end\n    cache = Paperclip::Interpolations::PluralCache.new\n    klass = BigBox\n\n    first = cache.underscore_and_pluralize_class(klass)\n    second = cache.underscore_and_pluralize_class(klass)\n    expect(first).to equal(second)\n  end\n\n  it 'pluralizes words' do\n    cache = Paperclip::Interpolations::PluralCache.new\n    symbol = :box\n\n    expect(cache.pluralize_symbol(symbol)).to eq(\"boxes\")\n  end\n\n  it 'pluralizes and underscore class names' do\n    class BigBox ; end\n    cache = Paperclip::Interpolations::PluralCache.new\n    klass = BigBox\n\n    expect(cache.underscore_and_pluralize_class(klass)).to eq(\"big_boxes\")\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/processor_helpers_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::ProcessorHelpers do\n  describe '.load_processor' do\n    context 'when the file exists in lib/paperclip' do\n      it 'loads it correctly' do\n        pathname = Pathname.new('my_app')\n        main_path = 'main_path'\n        alternate_path = 'alternate_path'\n\n        Rails.stubs(:root).returns(pathname)\n        File.expects(:expand_path).with(pathname.join('lib/paperclip', 'custom.rb')).returns(main_path)\n        File.expects(:expand_path).with(pathname.join('lib/paperclip_processors', 'custom.rb')).returns(alternate_path)\n        File.expects(:exist?).with(main_path).returns(true)\n        File.expects(:exist?).with(alternate_path).returns(false)\n\n        Paperclip.expects(:require).with(main_path)\n\n        Paperclip.load_processor(:custom)\n      end\n    end\n\n    context 'when the file exists in lib/paperclip_processors' do\n      it 'loads it correctly' do\n        pathname = Pathname.new('my_app')\n        main_path = 'main_path'\n        alternate_path = 'alternate_path'\n\n        Rails.stubs(:root).returns(pathname)\n        File.expects(:expand_path).with(pathname.join('lib/paperclip', 'custom.rb')).returns(main_path)\n        File.expects(:expand_path).with(pathname.join('lib/paperclip_processors', 'custom.rb')).returns(alternate_path)\n        File.expects(:exist?).with(main_path).returns(false)\n        File.expects(:exist?).with(alternate_path).returns(true)\n\n        Paperclip.expects(:require).with(alternate_path)\n\n        Paperclip.load_processor(:custom)\n      end\n    end\n\n    context 'when the file does not exist in lib/paperclip_processors' do\n      it 'raises an error' do\n        pathname = Pathname.new('my_app')\n        main_path = 'main_path'\n        alternate_path = 'alternate_path'\n\n        Rails.stubs(:root).returns(pathname)\n        File.stubs(:expand_path).with(pathname.join('lib/paperclip', 'custom.rb')).returns(main_path)\n        File.stubs(:expand_path).with(pathname.join('lib/paperclip_processors', 'custom.rb')).returns(alternate_path)\n        File.stubs(:exist?).with(main_path).returns(false)\n        File.stubs(:exist?).with(alternate_path).returns(false)\n\n        assert_raises(LoadError) { Paperclip.processor(:custom) }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/processor_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Processor do\n  it \"instantiates and call #make when sent #make to the class\" do\n    processor = mock\n    processor.expects(:make).with()\n    Paperclip::Processor.expects(:new).with(:one, :two, :three).returns(processor)\n    Paperclip::Processor.make(:one, :two, :three)\n  end\n\n  context \"Calling #convert\" do\n    it \"runs the convert command with Terrapin\" do\n      Paperclip.options[:log_command] = false\n      Terrapin::CommandLine.expects(:new).with(\"convert\", \"stuff\", {}).returns(stub(:run))\n      Paperclip::Processor.new('filename').convert(\"stuff\")\n    end\n  end\n\n  context \"Calling #identify\" do\n    it \"runs the identify command with Terrapin\" do\n      Paperclip.options[:log_command] = false\n      Terrapin::CommandLine.expects(:new).with(\"identify\", \"stuff\", {}).returns(stub(:run))\n      Paperclip::Processor.new('filename').identify(\"stuff\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/rails_environment_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::RailsEnvironment do\n\n  it \"returns nil when Rails isn't defined\" do\n    resetting_rails_to(nil) do\n      expect(Paperclip::RailsEnvironment.get).to be_nil\n    end\n  end\n\n  it \"returns nil when Rails.env isn't defined\" do\n    resetting_rails_to({}) do\n      expect(Paperclip::RailsEnvironment.get).to be_nil\n    end\n  end\n\n  it \"returns the value of Rails.env if it is set\" do\n    resetting_rails_to(OpenStruct.new(env: \"foo\")) do\n      expect(Paperclip::RailsEnvironment.get).to eq \"foo\"\n    end\n  end\n\n  def resetting_rails_to(new_value)\n    begin\n      previous_rails = Object.send(:remove_const, \"Rails\")\n      Object.const_set(\"Rails\", new_value) unless new_value.nil?\n      yield\n    ensure\n      Object.send(:remove_const, \"Rails\") if Object.const_defined?(\"Rails\")\n      Object.const_set(\"Rails\", previous_rails)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/rake_spec.rb",
    "content": "require 'spec_helper'\nrequire 'rake'\nload './lib/tasks/paperclip.rake'\n\ndescribe Rake do\n  context \"calling `rake paperclip:refresh:thumbnails`\" do\n    before do\n      rebuild_model\n      Paperclip::Task.stubs(:obtain_class).returns('Dummy')\n      @bogus_instance = Dummy.new\n      @bogus_instance.id = 'some_id'\n      @bogus_instance.avatar.stubs(:reprocess!)\n      @valid_instance = Dummy.new\n      @valid_instance.avatar.stubs(:reprocess!)\n      Paperclip::Task.stubs(:log_error)\n      Paperclip.stubs(:each_instance_with_attachment).multiple_yields @bogus_instance, @valid_instance\n    end\n    context \"when there is an exception in reprocess!\" do\n      before do\n        @bogus_instance.avatar.stubs(:reprocess!).raises\n      end\n\n      it \"catches the exception\" do\n        assert_nothing_raised do\n          ::Rake::Task['paperclip:refresh:thumbnails'].execute\n        end\n      end\n\n      it \"continues to the next instance\" do\n        @valid_instance.avatar.expects(:reprocess!)\n        ::Rake::Task['paperclip:refresh:thumbnails'].execute\n      end\n\n      it \"prints the exception\" do\n        exception_msg = 'Some Exception'\n        @bogus_instance.avatar.stubs(:reprocess!).raises(exception_msg)\n        Paperclip::Task.expects(:log_error).with do |str|\n          str.match exception_msg\n        end\n        ::Rake::Task['paperclip:refresh:thumbnails'].execute\n      end\n\n      it \"prints the class name\" do\n        Paperclip::Task.expects(:log_error).with do |str|\n          str.match 'Dummy'\n        end\n        ::Rake::Task['paperclip:refresh:thumbnails'].execute\n      end\n\n      it \"prints the instance ID\" do\n        Paperclip::Task.expects(:log_error).with do |str|\n          str.match \"ID #{@bogus_instance.id}\"\n        end\n        ::Rake::Task['paperclip:refresh:thumbnails'].execute\n      end\n    end\n\n    context \"when there is an error in reprocess!\" do\n      before do\n        @errors = mock('errors')\n        @errors.stubs(:full_messages).returns([''])\n        @errors.stubs(:blank?).returns(false)\n        @bogus_instance.stubs(:errors).returns(@errors)\n      end\n\n      it \"continues to the next instance\" do\n        @valid_instance.avatar.expects(:reprocess!)\n        ::Rake::Task['paperclip:refresh:thumbnails'].execute\n      end\n\n      it \"prints the error\" do\n        error_msg = 'Some Error'\n        @errors.stubs(:full_messages).returns([error_msg])\n        Paperclip::Task.expects(:log_error).with do |str|\n          str.match error_msg\n        end\n        ::Rake::Task['paperclip:refresh:thumbnails'].execute\n      end\n\n      it \"prints the class name\" do\n        Paperclip::Task.expects(:log_error).with do |str|\n          str.match 'Dummy'\n        end\n        ::Rake::Task['paperclip:refresh:thumbnails'].execute\n      end\n\n      it \"prints the instance ID\" do\n        Paperclip::Task.expects(:log_error).with do |str|\n          str.match \"ID #{@bogus_instance.id}\"\n        end\n        ::Rake::Task['paperclip:refresh:thumbnails'].execute\n      end\n    end\n  end\n\n  context \"Paperclip::Task.log_error method\" do\n    it \"prints its argument to STDERR\" do\n      msg = 'Some Message'\n      $stderr.expects(:puts).with(msg)\n      Paperclip::Task.log_error(msg)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/schema_spec.rb",
    "content": "require 'spec_helper'\nrequire 'paperclip/schema'\nrequire 'active_support/testing/deprecation'\n\ndescribe Paperclip::Schema do\n  include ActiveSupport::Testing::Deprecation\n\n  before do\n    rebuild_class\n  end\n\n  after do\n    Dummy.connection.drop_table :dummies rescue nil\n  end\n\n  context \"within table definition\" do\n    context \"using #has_attached_file\" do\n      before do\n        ActiveSupport::Deprecation.silenced = false\n      end\n      it \"creates attachment columns\" do\n        Dummy.connection.create_table :dummies, force: true do |t|\n          ActiveSupport::Deprecation.silence do\n            t.has_attached_file :avatar\n          end\n        end\n\n        columns = Dummy.columns.map{ |column| [column.name, column.sql_type] }\n\n        expect(columns).to include(['avatar_file_name', \"varchar\"])\n        expect(columns).to include(['avatar_content_type', \"varchar\"])\n        expect(columns).to include(['avatar_file_size', \"bigint\"])\n        expect(columns).to include(['avatar_updated_at', \"datetime\"])\n      end\n\n      it \"displays deprecation warning\" do\n        Dummy.connection.create_table :dummies, force: true do |t|\n          assert_deprecated do\n            t.has_attached_file :avatar\n          end\n        end\n      end\n    end\n\n    context \"using #attachment\" do\n      before do\n        Dummy.connection.create_table :dummies, force: true do |t|\n          t.attachment :avatar\n        end\n      end\n\n      it \"creates attachment columns\" do\n        columns = Dummy.columns.map{ |column| [column.name, column.sql_type] }\n\n        expect(columns).to include(['avatar_file_name', \"varchar\"])\n        expect(columns).to include(['avatar_content_type', \"varchar\"])\n        expect(columns).to include(['avatar_file_size', \"bigint\"])\n        expect(columns).to include(['avatar_updated_at', \"datetime\"])\n      end\n    end\n\n    context \"using #attachment with options\" do\n      before do\n        Dummy.connection.create_table :dummies, force: true do |t|\n          t.attachment :avatar, default: 1, file_name: { default: 'default' }\n        end\n      end\n\n      it \"sets defaults on columns\" do\n        defaults_columns = [\"avatar_file_name\", \"avatar_content_type\", \"avatar_file_size\"]\n        columns = Dummy.columns.select { |e| defaults_columns.include? e.name }\n\n        expect(columns).to have_column(\"avatar_file_name\").with_default(\"default\")\n        expect(columns).to have_column(\"avatar_content_type\").with_default(\"1\")\n        expect(columns).to have_column(\"avatar_file_size\").with_default(1)\n      end\n    end\n  end\n\n  context \"within schema statement\" do\n    before do\n      Dummy.connection.create_table :dummies, force: true\n    end\n\n    context \"migrating up\" do\n      context \"with single attachment\" do\n        before do\n          Dummy.connection.add_attachment :dummies, :avatar\n        end\n\n        it \"creates attachment columns\" do\n          columns = Dummy.columns.map{ |column| [column.name, column.sql_type] }\n\n          expect(columns).to include(['avatar_file_name', \"varchar\"])\n          expect(columns).to include(['avatar_content_type', \"varchar\"])\n          expect(columns).to include(['avatar_file_size', \"bigint\"])\n          expect(columns).to include(['avatar_updated_at', \"datetime\"])\n        end\n      end\n\n      context \"with single attachment and options\" do\n        before do\n          Dummy.connection.add_attachment :dummies, :avatar, default: '1', file_name: { default: 'default' }\n        end\n\n        it \"sets defaults on columns\" do\n          defaults_columns = [\"avatar_file_name\", \"avatar_content_type\", \"avatar_file_size\"]\n          columns = Dummy.columns.select { |e| defaults_columns.include? e.name }\n\n          expect(columns).to have_column(\"avatar_file_name\").with_default(\"default\")\n          expect(columns).to have_column(\"avatar_content_type\").with_default(\"1\")\n          expect(columns).to have_column(\"avatar_file_size\").with_default(1)\n        end\n      end\n\n      context \"with multiple attachments\" do\n        before do\n          Dummy.connection.add_attachment :dummies, :avatar, :photo\n        end\n\n        it \"creates attachment columns\" do\n          columns = Dummy.columns.map{ |column| [column.name, column.sql_type] }\n\n          expect(columns).to include(['avatar_file_name', \"varchar\"])\n          expect(columns).to include(['avatar_content_type', \"varchar\"])\n          expect(columns).to include(['avatar_file_size', \"bigint\"])\n          expect(columns).to include(['avatar_updated_at', \"datetime\"])\n          expect(columns).to include(['photo_file_name', \"varchar\"])\n          expect(columns).to include(['photo_content_type', \"varchar\"])\n          expect(columns).to include(['photo_file_size', \"bigint\"])\n          expect(columns).to include(['photo_updated_at', \"datetime\"])\n        end\n      end\n\n      context \"with multiple attachments and options\" do\n        before do\n          Dummy.connection.add_attachment :dummies, :avatar, :photo, default: '1', file_name: { default: 'default' }\n        end\n\n        it \"sets defaults on columns\" do\n          defaults_columns = [\"avatar_file_name\", \"avatar_content_type\", \"avatar_file_size\", \"photo_file_name\", \"photo_content_type\", \"photo_file_size\"]\n          columns = Dummy.columns.select { |e| defaults_columns.include? e.name }\n\n          expect(columns).to have_column(\"avatar_file_name\").with_default(\"default\")\n          expect(columns).to have_column(\"avatar_content_type\").with_default(\"1\")\n          expect(columns).to have_column(\"avatar_file_size\").with_default(1)\n          expect(columns).to have_column(\"photo_file_name\").with_default(\"default\")\n          expect(columns).to have_column(\"photo_content_type\").with_default(\"1\")\n          expect(columns).to have_column(\"photo_file_size\").with_default(1)\n        end\n      end\n\n      context \"with no attachment\" do\n        it \"raises an error\" do\n          assert_raises ArgumentError do\n            Dummy.connection.add_attachment :dummies\n          end\n        end\n      end\n    end\n\n    context \"migrating down\" do\n      before do\n        Dummy.connection.change_table :dummies do |t|\n          t.column :avatar_file_name, :string\n          t.column :avatar_content_type, :string\n          t.column :avatar_file_size, :bigint\n          t.column :avatar_updated_at, :datetime\n        end\n      end\n\n      context \"using #drop_attached_file\" do\n        before do\n          ActiveSupport::Deprecation.silenced = false\n        end\n        it \"removes the attachment columns\" do\n          ActiveSupport::Deprecation.silence do\n            Dummy.connection.drop_attached_file :dummies, :avatar\n          end\n\n          columns = Dummy.columns.map{ |column| [column.name, column.sql_type] }\n\n          expect(columns).to_not include(['avatar_file_name', \"varchar\"])\n          expect(columns).to_not include(['avatar_content_type', \"varchar\"])\n          expect(columns).to_not include(['avatar_file_size', \"bigint\"])\n          expect(columns).to_not include(['avatar_updated_at', \"datetime\"])\n        end\n\n        it \"displays a deprecation warning\" do\n          assert_deprecated do\n            Dummy.connection.drop_attached_file :dummies, :avatar\n          end\n        end\n      end\n\n      context \"using #remove_attachment\" do\n        context \"with single attachment\" do\n          before do\n            Dummy.connection.remove_attachment :dummies, :avatar\n          end\n\n          it \"removes the attachment columns\" do\n            columns = Dummy.columns.map{ |column| [column.name, column.sql_type] }\n\n            expect(columns).to_not include(['avatar_file_name', \"varchar\"])\n            expect(columns).to_not include(['avatar_content_type', \"varchar\"])\n            expect(columns).to_not include(['avatar_file_size', \"bigint\"])\n            expect(columns).to_not include(['avatar_updated_at', \"datetime\"])\n          end\n        end\n\n        context \"with multiple attachments\" do\n          before do\n            Dummy.connection.change_table :dummies do |t|\n              t.column :photo_file_name, :string\n              t.column :photo_content_type, :string\n              t.column :photo_file_size, :bigint\n              t.column :photo_updated_at, :datetime\n            end\n\n            Dummy.connection.remove_attachment :dummies, :avatar, :photo\n          end\n\n          it \"removes the attachment columns\" do\n            columns = Dummy.columns.map{ |column| [column.name, column.sql_type] }\n\n            expect(columns).to_not include(['avatar_file_name', \"varchar\"])\n            expect(columns).to_not include(['avatar_content_type', \"varchar\"])\n            expect(columns).to_not include(['avatar_file_size', \"bigint\"])\n            expect(columns).to_not include(['avatar_updated_at', \"datetime\"])\n            expect(columns).to_not include(['photo_file_name', \"varchar\"])\n            expect(columns).to_not include(['photo_content_type', \"varchar\"])\n            expect(columns).to_not include(['photo_file_size', \"bigint\"])\n            expect(columns).to_not include(['photo_updated_at', \"datetime\"])\n          end\n        end\n\n        context \"with no attachment\" do\n          it \"raises an error\" do\n            assert_raises ArgumentError do\n              Dummy.connection.remove_attachment :dummies\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/storage/filesystem_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Storage::Filesystem do\n  context \"Filesystem\" do\n    context \"normal file\" do\n      before do\n        rebuild_model styles: { thumbnail: \"25x25#\" }\n        @dummy = Dummy.create!\n\n        @file = File.open(fixture_file('5k.png'))\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      it \"allows file assignment\" do\n        assert @dummy.save\n      end\n\n      it \"stores the original\" do\n        @dummy.save\n        assert_file_exists(@dummy.avatar.path)\n      end\n\n      it \"stores the thumbnail\" do\n        @dummy.save\n        assert_file_exists(@dummy.avatar.path(:thumbnail))\n      end\n\n      it \"is rewinded after flush_writes\" do\n        @dummy.avatar.instance_eval \"def after_flush_writes; end\"\n\n        files = @dummy.avatar.queued_for_write.values\n        @dummy.save\n        assert files.none?(&:eof?), \"Expect all the files to be rewinded.\"\n      end\n\n      it \"is removed after after_flush_writes\" do\n        paths = @dummy.avatar.queued_for_write.values.map(&:path)\n        @dummy.save\n        assert paths.none?{ |path| File.exist?(path) },\n          \"Expect all the files to be deleted.\"\n      end\n\n      it 'copies the file to a known location with copy_to_local_file' do\n        tempfile = Tempfile.new(\"known_location\")\n        @dummy.avatar.copy_to_local_file(:original, tempfile.path)\n        tempfile.rewind\n        assert_equal @file.read, tempfile.read\n        tempfile.close\n      end\n    end\n\n    context \"with file that has space in file name\" do\n      before do\n        rebuild_model styles: { thumbnail: \"25x25#\" }\n        @dummy = Dummy.create!\n\n        @file = File.open(fixture_file('spaced file.png'))\n        @dummy.avatar = @file\n        @dummy.save\n      end\n\n      after { @file.close }\n\n      it \"stores the file\" do\n        assert_file_exists(@dummy.avatar.path)\n      end\n\n      it \"returns a replaced version for path\" do\n        assert_match /.+\\/spaced_file\\.png/, @dummy.avatar.path\n      end\n\n      it \"returns a replaced version for url\" do\n        assert_match /.+\\/spaced_file\\.png/, @dummy.avatar.url\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/storage/fog_spec.rb",
    "content": "require 'spec_helper'\nrequire 'fog/aws'\nrequire 'fog/local'\nrequire 'timecop'\n\ndescribe Paperclip::Storage::Fog do\n  context \"\" do\n    before { Fog.mock! }\n\n    context \"with credentials provided in a path string\" do\n      before do\n        rebuild_model styles: { medium: \"300x300>\", thumb: \"100x100>\" },\n                      storage: :fog,\n                      url: '/:attachment/:filename',\n                      fog_directory: \"paperclip\",\n                      fog_credentials: fixture_file('fog.yml')\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      it \"has the proper information loading credentials from a file\" do\n        assert_equal @dummy.avatar.fog_credentials[:provider], 'AWS'\n      end\n    end\n\n    context \"with credentials provided in a File object\" do\n      before do\n        rebuild_model styles: { medium: \"300x300>\", thumb: \"100x100>\" },\n                      storage: :fog,\n                      url: '/:attachment/:filename',\n                      fog_directory: \"paperclip\",\n                      fog_credentials: File.open(fixture_file('fog.yml'))\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      it \"has the proper information loading credentials from a file\" do\n        assert_equal @dummy.avatar.fog_credentials[:provider], 'AWS'\n      end\n    end\n\n    context \"with default values for path and url\" do\n      before do\n        rebuild_model styles: { medium: \"300x300>\", thumb: \"100x100>\" },\n                      storage: :fog,\n                      url: '/:attachment/:filename',\n                      fog_directory: \"paperclip\",\n                      fog_credentials: {\n                        provider: 'AWS',\n                        aws_access_key_id: 'AWS_ID',\n                        aws_secret_access_key: 'AWS_SECRET'\n                      }\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      it \"is able to interpolate the path without blowing up\" do\n        assert_equal File.expand_path(File.join(File.dirname(__FILE__), \"../../../tmp/public/avatars/5k.png\")),\n          @dummy.avatar.path\n      end\n    end\n\n    context \"with no path or url given and using defaults\" do\n      before do\n        rebuild_model styles: { medium: \"300x300>\", thumb: \"100x100>\" },\n                      storage: :fog,\n                      fog_directory: \"paperclip\",\n                      fog_credentials: {\n                        provider: 'AWS',\n                        aws_access_key_id: 'AWS_ID',\n                        aws_secret_access_key: 'AWS_SECRET'\n                      }\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.id = 1\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      it \"has correct path and url from interpolated defaults\" do\n        assert_equal \"dummies/avatars/000/000/001/original/5k.png\", @dummy.avatar.path\n      end\n    end\n\n    context \"with file params provided as lambda\" do\n      before do\n        fog_file = lambda{ |a| { custom_header: a.instance.custom_method }}\n        klass = rebuild_model storage: :fog,\n                              fog_file: fog_file\n\n        klass.class_eval do\n          def custom_method\n            'foobar'\n          end\n        end\n\n\n        @dummy = Dummy.new\n      end\n\n      it \"is able to evaluate correct values for file headers\" do\n        assert_equal @dummy.avatar.send(:fog_file), { custom_header: 'foobar' }\n      end\n    end\n\n    before do\n      @fog_directory = 'papercliptests'\n\n      @credentials = {\n        provider: 'AWS',\n        aws_access_key_id: 'ID',\n        aws_secret_access_key: 'SECRET'\n      }\n\n      @connection = Fog::Storage.new(@credentials)\n      @connection.directories.create(\n        key: @fog_directory\n      )\n\n      @options = {\n        fog_directory: @fog_directory,\n        fog_credentials: @credentials,\n        fog_host: nil,\n        fog_file: {cache_control: 1234},\n        path: \":attachment/:basename:dotextension\",\n        storage: :fog\n      }\n\n      rebuild_model(@options)\n    end\n\n    it \"is extended by the Fog module\" do\n      assert Dummy.new.avatar.is_a?(Paperclip::Storage::Fog)\n    end\n\n    context \"when assigned\" do\n      before do\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after do\n        @file.close\n        directory = @connection.directories.new(key: @fog_directory)\n        directory.files.each {|file| file.destroy}\n        directory.destroy\n      end\n\n      it \"is rewound after flush_writes\" do\n        @dummy.avatar.instance_eval \"def after_flush_writes; end\"\n\n        files = @dummy.avatar.queued_for_write.values\n        @dummy.save\n        assert files.none?(&:eof?), \"Expect all the files to be rewinded.\"\n      end\n\n      it \"is removed after after_flush_writes\" do\n        paths = @dummy.avatar.queued_for_write.values.map(&:path)\n        @dummy.save\n        assert paths.none?{ |path| File.exist?(path) },\n          \"Expect all the files to be deleted.\"\n      end\n\n      it 'is able to be copied to a local file' do\n        @dummy.save\n        tempfile = Tempfile.new(\"known_location\")\n        tempfile.binmode\n        @dummy.avatar.copy_to_local_file(:original, tempfile.path)\n        tempfile.rewind\n        assert_equal @connection.directories.get(@fog_directory).files.get(@dummy.avatar.path).body,\n                     tempfile.read\n        tempfile.close\n      end\n\n      it 'is able to be handled when missing while copying to a local file' do\n        tempfile = Tempfile.new(\"known_location\")\n        tempfile.binmode\n        assert_equal false, @dummy.avatar.copy_to_local_file(:original, tempfile.path)\n        tempfile.close\n      end\n\n      it \"passes the content type to the Fog::Storage::AWS::Files instance\" do\n        Fog::Storage::AWS::Files.any_instance.expects(:create).with do |hash|\n          hash[:content_type]\n        end\n        @dummy.save\n      end\n\n      context \"without a bucket\" do\n        before do\n          @connection.directories.get(@fog_directory).destroy\n        end\n\n        it \"creates the bucket\" do\n          assert @dummy.save\n          assert @connection.directories.get(@fog_directory)\n        end\n\n        it \"sucessfully rewinds the file during bucket creation\" do\n          assert @dummy.save\n          expect(Paperclip.io_adapters.for(@dummy.avatar).read.length).to be > 0\n        end\n      end\n\n      context \"with a bucket\" do\n        it \"succeeds\" do\n          assert @dummy.save\n        end\n      end\n\n      context \"without a fog_host\" do\n        before do\n          rebuild_model(@options.merge(fog_host: nil))\n          @dummy = Dummy.new\n          @dummy.avatar = StringIO.new('.')\n          @dummy.save\n        end\n\n        it \"provides a public url\" do\n          assert !@dummy.avatar.url.nil?\n        end\n      end\n\n      context \"with a fog_host\" do\n        before do\n          rebuild_model(@options.merge(fog_host: 'http://example.com'))\n          @dummy = Dummy.new\n          @dummy.avatar = StringIO.new(\".\\n\")\n          @dummy.save\n        end\n\n        it \"provides a public url\" do\n          expect(@dummy.avatar.url).to match(/^http:\\/\\/example\\.com\\/avatars\\/data\\?\\d*$/)\n        end\n      end\n\n      context \"with a fog_host that includes a wildcard placeholder\" do\n        before do\n          rebuild_model(\n            fog_directory: @fog_directory,\n            fog_credentials: @credentials,\n            fog_host: 'http://img%d.example.com',\n            path: \":attachment/:basename:dotextension\",\n            storage: :fog\n          )\n          @dummy = Dummy.new\n          @dummy.avatar = StringIO.new(\".\\n\")\n          @dummy.save\n        end\n\n        it \"provides a public url\" do\n          expect(@dummy.avatar.url).to match(/^http:\\/\\/img[0123]\\.example\\.com\\/avatars\\/data\\?\\d*$/)\n        end\n      end\n\n      context \"with fog_public set to false\" do\n        before do\n          rebuild_model(@options.merge(fog_public: false))\n          @dummy = Dummy.new\n          @dummy.avatar = StringIO.new('.')\n          @dummy.save\n        end\n\n        it 'sets the @fog_public instance variable to false' do\n          assert_equal false, @dummy.avatar.instance_variable_get('@options')[:fog_public]\n          assert_equal false, @dummy.avatar.fog_public\n        end\n      end\n\n      context \"with fog_public as a proc\" do\n        let(:proc) { ->(attachment) { !attachment } }\n\n        before do\n          rebuild_model(@options.merge(fog_public: proc))\n          @dummy = Dummy.new\n          @dummy.avatar = StringIO.new(\".\")\n          @dummy.save\n        end\n\n        it \"sets the @fog_public instance variable to false\" do\n          assert_equal proc, @dummy.avatar.instance_variable_get(\"@options\")[:fog_public]\n          assert_equal false, @dummy.avatar.fog_public\n        end\n      end\n\n      context \"with styles set and fog_public set to false\" do\n        before do\n          rebuild_model(@options.merge(fog_public: false, styles: { medium: \"300x300>\", thumb: \"100x100>\" }))\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it 'sets the @fog_public for a particular style to false' do\n          assert_equal false, @dummy.avatar.instance_variable_get('@options')[:fog_public]\n          assert_equal false, @dummy.avatar.fog_public(:thumb)\n        end\n      end\n\n      context \"with styles set and fog_public set per-style\" do\n        before do\n          rebuild_model(@options.merge(fog_public: { medium: false, thumb: true}, styles: { medium: \"300x300>\", thumb: \"100x100>\" }))\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it 'sets the fog_public for a particular style to correct value' do\n          assert_equal false, @dummy.avatar.fog_public(:medium)\n          assert_equal true, @dummy.avatar.fog_public(:thumb)\n        end\n      end\n\n      context \"with fog_public not set\" do\n        before do\n          rebuild_model(@options)\n          @dummy = Dummy.new\n          @dummy.avatar = StringIO.new('.')\n          @dummy.save\n        end\n\n        it \"defaults fog_public to true\" do\n          assert_equal true, @dummy.avatar.fog_public\n        end\n      end\n\n      context \"with scheme set\" do\n        before do\n          rebuild_model(@options.merge(:fog_credentials => @credentials.merge(:scheme => 'http')))\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it \"honors the scheme in public url\" do\n          assert_match(/^http:\\/\\//, @dummy.avatar.url)\n        end\n        it \"honors the scheme in expiring url\" do\n          assert_match(/^http:\\/\\//, @dummy.avatar.expiring_url)\n        end\n      end\n\n      context \"with scheme not set\" do\n        before do\n          rebuild_model(@options)\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it \"provides HTTPS public url\" do\n          assert_match(/^https:\\/\\//, @dummy.avatar.url)\n        end\n        it \"provides HTTPS expiring url\" do\n          assert_match(/^https:\\/\\//, @dummy.avatar.expiring_url)\n        end\n      end\n\n      context \"with a valid bucket name for a subdomain\" do\n        before { @dummy.stubs(:new_record?).returns(false) }\n\n        it \"provides an url in subdomain style\" do\n          assert_match(/^https:\\/\\/papercliptests.s3.amazonaws.com\\/avatars\\/5k.png/, @dummy.avatar.url)\n        end\n\n        it \"provides an url that expires in subdomain style\" do\n          assert_match(/^https:\\/\\/papercliptests.s3.amazonaws.com\\/avatars\\/5k.png.+Expires=.+$/, @dummy.avatar.expiring_url)\n        end\n      end\n\n      context \"generating an expiring url\" do\n        it \"generates the same url when using Times and Integer offsets\" do\n          Timecop.freeze do\n            offset = 1234\n            rebuild_model(@options)\n            dummy = Dummy.new\n            dummy.avatar = StringIO.new('.')\n\n            assert_equal dummy.avatar.expiring_url(offset),\n              dummy.avatar.expiring_url(Time.now + offset )\n          end\n        end\n\n        it 'matches the default url if there is no assignment' do\n          dummy = Dummy.new\n          assert_equal dummy.avatar.url, dummy.avatar.expiring_url\n        end\n\n        it 'matches the default url when given a style if there is no assignment' do\n          dummy = Dummy.new\n          assert_equal dummy.avatar.url(:thumb), dummy.avatar.expiring_url(3600, :thumb)\n        end\n      end\n\n      context \"with an invalid bucket name for a subdomain\" do\n        before do\n          rebuild_model(@options.merge(fog_directory: \"this_is_invalid\"))\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it \"does not match the bucket-subdomain restrictions\" do\n          invalid_subdomains = %w(this_is_invalid in iamareallylongbucketnameiamareallylongbucketnameiamareallylongbu invalid- inval..id inval-.id inval.-id -invalid 192.168.10.2)\n          invalid_subdomains.each do |name|\n            assert_no_match Paperclip::Storage::Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX, name\n          end\n        end\n\n        it \"provides an url in folder style\" do\n          assert_match(/^https:\\/\\/s3.amazonaws.com\\/this_is_invalid\\/avatars\\/5k.png\\?\\d*$/, @dummy.avatar.url)\n        end\n\n        it \"provides a url that expires in folder style\" do\n          assert_match(/^https:\\/\\/s3.amazonaws.com\\/this_is_invalid\\/avatars\\/5k.png.+Expires=.+$/, @dummy.avatar.expiring_url)\n        end\n\n      end\n\n      context \"with a proc for a bucket name evaluating a model method\" do\n        before do\n          @dynamic_fog_directory = 'dynamicpaperclip'\n          rebuild_model(@options.merge(fog_directory: lambda { |attachment| attachment.instance.bucket_name }))\n          @dummy = Dummy.new\n          @dummy.stubs(:bucket_name).returns(@dynamic_fog_directory)\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it \"has created the bucket\" do\n          assert @connection.directories.get(@dynamic_fog_directory).inspect\n        end\n\n        it \"provides an url using dynamic bucket name\" do\n          assert_match(/^https:\\/\\/dynamicpaperclip.s3.amazonaws.com\\/avatars\\/5k.png\\?\\d*$/, @dummy.avatar.url)\n        end\n      end\n\n      context \"with a proc for the fog_host evaluating a model method\" do\n        before do\n          rebuild_model(@options.merge(fog_host: lambda { |attachment| attachment.instance.fog_host }))\n          @dummy = Dummy.new\n          @dummy.stubs(:fog_host).returns('http://dynamicfoghost.com')\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it \"provides a public url\" do\n          assert_match(/http:\\/\\/dynamicfoghost\\.com/, @dummy.avatar.url)\n        end\n\n      end\n\n      context \"with a custom fog_host\" do\n        before do\n          rebuild_model(@options.merge(fog_host: \"http://dynamicfoghost.com\"))\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it \"provides a public url\" do\n          assert_match(/http:\\/\\/dynamicfoghost\\.com/, @dummy.avatar.url)\n        end\n\n        it \"provides an expiring url\" do\n          assert_match(/http:\\/\\/dynamicfoghost\\.com/, @dummy.avatar.expiring_url)\n        end\n\n        context \"with an invalid bucket name for a subdomain\" do\n          before do\n            rebuild_model(@options.merge({fog_directory: \"this_is_invalid\", fog_host: \"http://dynamicfoghost.com\"}))\n            @dummy = Dummy.new\n            @dummy.avatar = @file\n            @dummy.save\n          end\n\n          it \"provides an expiring url\" do\n            assert_match(/http:\\/\\/dynamicfoghost\\.com/, @dummy.avatar.expiring_url)\n          end\n        end\n\n      end\n\n      context \"with a proc for the fog_credentials evaluating a model method\" do\n        before do\n          @dynamic_fog_credentials = {\n            provider: 'AWS',\n            aws_access_key_id: 'DYNAMIC_ID',\n            aws_secret_access_key: 'DYNAMIC_SECRET'\n          }\n          rebuild_model(@options.merge(fog_credentials: lambda { |attachment| attachment.instance.fog_credentials }))\n          @dummy = Dummy.new\n          @dummy.stubs(:fog_credentials).returns(@dynamic_fog_credentials)\n          @dummy.avatar = @file\n          @dummy.save\n        end\n\n        it \"provides a public url\" do\n          assert_equal @dummy.avatar.fog_credentials, @dynamic_fog_credentials\n        end\n      end\n\n      context \"with custom fog_options\" do\n        before do\n          rebuild_model(\n            @options.merge(fog_options: { multipart_chunk_size: 104857600 }),\n          )\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        it \"applies the options to the fog #create call\" do\n          files = stub\n          @dummy.avatar.stubs(:directory).returns stub(files: files)\n          files.expects(:create).with(\n            has_entries(multipart_chunk_size: 104857600),\n          )\n          @dummy.save\n        end\n      end\n    end\n\n  end\n\n  context \"when using local storage\" do\n    before do\n      Fog.unmock!\n      rebuild_model styles: { medium: \"300x300>\", thumb: \"100x100>\" },\n                    storage: :fog,\n                    url: '/:attachment/:filename',\n                    fog_directory: \"paperclip\",\n                    fog_credentials: { provider: :local, local_root: \".\" },\n                    fog_host: 'localhost'\n\n      @file = File.new(fixture_file('5k.png'), 'rb')\n      @dummy = Dummy.new\n      @dummy.avatar = @file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    after do\n      @file.close\n      Fog.mock!\n    end\n\n    it \"returns the public url in place of the expiring url\" do\n      assert_match @dummy.avatar.public_url, @dummy.avatar.expiring_url\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/storage/s3_live_spec.rb",
    "content": "require 'spec_helper'\n\nunless ENV[\"S3_BUCKET\"].blank?\n  describe Paperclip::Storage::S3, 'Live S3' do\n    context \"when assigning an S3 attachment directly to another model\" do\n      before do\n        rebuild_model styles: { thumb: \"100x100\", square: \"32x32#\" },\n                      storage: :s3,\n                      bucket: ENV[\"S3_BUCKET\"],\n                      path: \":class/:attachment/:id/:style.:extension\",\n                      s3_region: ENV[\"S3_REGION\"],\n                      s3_credentials: {\n                        access_key_id: ENV['AWS_ACCESS_KEY_ID'],\n                        secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']\n                      }\n\n        @file = File.new(fixture_file(\"5k.png\"))\n      end\n\n      it \"does not raise any error\" do\n        @attachment = Dummy.new.avatar\n        @attachment.assign(@file)\n        @attachment.save\n\n        @attachment2 = Dummy.new.avatar\n        @attachment2.assign(@file)\n        @attachment2.save\n      end\n\n      it \"allows assignment from another S3 object\" do\n        @attachment = Dummy.new.avatar\n        @attachment.assign(@file)\n        @attachment.save\n\n        @attachment2 = Dummy.new.avatar\n        @attachment2.assign(@attachment)\n        @attachment2.save\n      end\n\n      after { @file.close }\n    end\n\n    context \"Generating an expiring url on a nonexistant attachment\" do\n      before do\n        rebuild_model styles: { thumb: \"100x100\", square: \"32x32#\" },\n                      storage: :s3,\n                      bucket: ENV[\"S3_BUCKET\"],\n                      path: \":class/:attachment/:id/:style.:extension\",\n                      s3_region: ENV[\"S3_REGION\"],\n                      s3_credentials: {\n                        access_key_id: ENV['AWS_ACCESS_KEY_ID'],\n                        secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']\n                      }\n\n        @dummy = Dummy.new\n      end\n\n      it \"returns a missing url\" do\n        expect(@dummy.avatar.expiring_url).to eq @dummy.avatar.url\n      end\n    end\n\n    context \"Using S3 for real, an attachment with S3 storage\" do\n      before do\n        rebuild_model styles: { thumb: \"100x100\", square: \"32x32#\" },\n                      storage: :s3,\n                      bucket: ENV[\"S3_BUCKET\"],\n                      path: \":class/:attachment/:id/:style.:extension\",\n                      s3_region: ENV[\"S3_REGION\"],\n                      s3_credentials: {\n                        access_key_id: ENV['AWS_ACCESS_KEY_ID'],\n                        secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']\n                      }\n\n        Dummy.delete_all\n        @dummy = Dummy.new\n      end\n\n      it \"is extended by the S3 module\" do\n        assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3)\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy.avatar = @file\n        end\n\n        after do\n          @file.close\n          @dummy.destroy\n        end\n\n        context \"and saved\" do\n          before do\n            @dummy.save\n          end\n\n          it \"is on S3\" do\n            assert true\n          end\n        end\n      end\n    end\n\n    context \"An attachment that uses S3 for storage and has spaces in file name\" do\n      before do\n        rebuild_model styles: { thumb: \"100x100\", square: \"32x32#\" },\n          storage: :s3,\n          bucket: ENV[\"S3_BUCKET\"],\n          s3_region: ENV[\"S3_REGION\"],\n          url: \":s3_domain_url\",\n          path: \"/:class/:attachment/:id_partition/:style/:filename\",\n          s3_credentials: {\n            access_key_id: ENV['AWS_ACCESS_KEY_ID'],\n            secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']\n          }\n\n        Dummy.delete_all\n        @file = File.new(fixture_file('spaced file.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n        @dummy.save\n      end\n\n      it \"returns a replaced version for path\" do\n        assert_match /.+\\/spaced_file\\.png/, @dummy.avatar.path\n      end\n\n      it \"returns a replaced version for url\" do\n        assert_match /.+\\/spaced_file\\.png/, @dummy.avatar.url\n      end\n\n      it \"is accessible\" do\n        assert_success_response @dummy.avatar.url\n      end\n\n      it \"is reprocessable\" do\n        assert @dummy.avatar.reprocess!\n      end\n\n      it \"is destroyable\" do\n        url = @dummy.avatar.url\n        @dummy.destroy\n        assert_forbidden_response url\n      end\n    end\n\n    context \"An attachment that uses S3 for storage and uses AES256 encryption\" do\n      before do\n        rebuild_model styles: { thumb: \"100x100\", square: \"32x32#\" },\n                      storage: :s3,\n                      bucket: ENV[\"S3_BUCKET\"],\n                      path: \":class/:attachment/:id/:style.:extension\",\n                      s3_region: ENV[\"S3_REGION\"],\n                      s3_credentials: {\n                        access_key_id: ENV['AWS_ACCESS_KEY_ID'],\n                        secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']\n                      },\n                      s3_server_side_encryption: \"AES256\"\n        Dummy.delete_all\n        @dummy = Dummy.new\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy.avatar = @file\n        end\n\n        after do\n          @file.close\n          @dummy.destroy\n        end\n\n        context \"and saved\" do\n          before do\n            @dummy.save\n          end\n\n          it \"is encrypted on S3\" do\n            assert @dummy.avatar.s3_object.server_side_encryption == \"AES256\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/storage/s3_spec.rb",
    "content": "require \"spec_helper\"\nrequire \"aws-sdk-s3\"\n\ndescribe Paperclip::Storage::S3 do\n  before do\n    Aws.config[:stub_responses] = true\n  end\n\n  def aws2_add_region\n    { s3_region: 'us-east-1' }\n  end\n\n  context \"Parsing S3 credentials\" do\n    before do\n      @proxy_settings = {host: \"127.0.0.1\", port: 8888, user: \"foo\", password: \"bar\"}\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        http_proxy: @proxy_settings,\n        s3_credentials: {not: :important}\n      @dummy = Dummy.new\n      @avatar = @dummy.avatar\n    end\n\n    it \"gets the correct credentials when RAILS_ENV is production\" do\n      rails_env(\"production\") do\n        assert_equal({key: \"12345\"},\n                     @avatar.parse_credentials('production' => {key: '12345'},\n                                               development: {key: \"54321\"}))\n      end\n    end\n\n    it \"gets the correct credentials when RAILS_ENV is development\" do\n      rails_env(\"development\") do\n        assert_equal({key: \"54321\"},\n                     @avatar.parse_credentials('production' => {key: '12345'},\n                                               development: {key: \"54321\"}))\n      end\n    end\n\n    it \"returns the argument if the key does not exist\" do\n      rails_env(\"not really an env\") do\n        assert_equal({test: \"12345\"}, @avatar.parse_credentials(test: \"12345\"))\n      end\n    end\n\n    it \"supports HTTP proxy settings\" do\n      rails_env(\"development\") do\n        assert_equal(true, @avatar.using_http_proxy?)\n        assert_equal(@proxy_settings[:host], @avatar.http_proxy_host)\n        assert_equal(@proxy_settings[:port], @avatar.http_proxy_port)\n        assert_equal(@proxy_settings[:user], @avatar.http_proxy_user)\n        assert_equal(@proxy_settings[:password], @avatar.http_proxy_password)\n      end\n    end\n\n  end\n\n  context \":bucket option via :s3_credentials\" do\n\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {bucket: 'testing'}\n      @dummy = Dummy.new\n    end\n\n    it \"populates #bucket_name\" do\n      assert_equal @dummy.avatar.bucket_name, 'testing'\n    end\n\n  end\n\n  context \":bucket option\" do\n\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\", s3_credentials: {}\n      @dummy = Dummy.new\n    end\n\n    it \"populates #bucket_name\" do\n      assert_equal @dummy.avatar.bucket_name, 'testing'\n    end\n\n  end\n\n  context \"missing :bucket option\" do\n\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        http_proxy: @proxy_settings,\n        s3_credentials: {not: :important}\n\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n\n    end\n\n    it \"raises an argument error\" do\n      expect { @dummy.save }.to raise_error(ArgumentError, /missing required :bucket option/)\n    end\n\n  end\n\n  context \"\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {},\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\",\n        url: \":s3_path_url\"\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url based on an S3 path\" do\n      assert_match %r{^//s3.amazonaws.com/bucket/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n\n    it \"uses the correct bucket\" do\n      assert_equal \"bucket\", @dummy.avatar.s3_bucket.name\n    end\n\n    it \"uses the correct key\" do\n      assert_equal \"avatars/data\", @dummy.avatar.s3_object.key\n    end\n  end\n\n  context \"s3_protocol\" do\n    [\"http\", :http, \"\"].each do |protocol|\n      context \"as #{protocol.inspect}\" do\n        before do\n          rebuild_model (aws2_add_region).merge storage: :s3,\n            s3_protocol: protocol\n          @dummy = Dummy.new\n        end\n\n        it \"returns the s3_protocol in string\" do\n          assert_equal protocol.to_s, @dummy.avatar.s3_protocol\n        end\n      end\n    end\n  end\n\n  context \"s3_protocol: 'https'\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {},\n        s3_protocol: 'https',\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\"\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url based on an S3 path\" do\n      assert_match %r{^https://s3.amazonaws.com/bucket/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n  end\n\n  context \"s3_protocol: ''\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {},\n        s3_protocol: '',\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\"\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a protocol-relative URL\" do\n      assert_match %r{^//s3.amazonaws.com/bucket/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n  end\n\n  context \"s3_protocol: :https\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {},\n        s3_protocol: :https,\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\"\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url based on an S3 path\" do\n      assert_match %r{^https://s3.amazonaws.com/bucket/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n  end\n\n  context \"s3_protocol: ''\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {},\n        s3_protocol: '',\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\"\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url based on an S3 path\" do\n      assert_match %r{^//s3.amazonaws.com/bucket/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n  end\n\n  context \"An attachment that uses S3 for storage and has the style in the path\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        path: \":attachment/:style/:basename:dotextension\",\n        styles: {\n          thumb: \"80x80>\"\n        },\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        }\n\n        @dummy = Dummy.new\n        @dummy.avatar = stringy_file\n        @avatar = @dummy.avatar\n    end\n\n    it \"uses an S3 object based on the correct path for the default style\" do\n      assert_equal(\"avatars/original/data\", @dummy.avatar.s3_object.key)\n    end\n\n    it \"uses an S3 object based on the correct path for the custom style\" do\n      assert_equal(\"avatars/thumb/data\", @dummy.avatar.s3_object(:thumb).key)\n    end\n  end\n\n  # the s3_host_name will be defined by the s3_region\n  context \"s3_host_name\" do\n    before do\n      rebuild_model storage: :s3,\n        s3_credentials: {},\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\",\n        s3_host_name: \"s3-ap-northeast-1.amazonaws.com\",\n        s3_region: \"ap-northeast-1\"\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url based on an :s3_host_name path\" do\n      assert_match %r{^//s3-ap-northeast-1.amazonaws.com/bucket/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n\n    it \"uses the S3 bucket with the correct host name\" do\n      assert_equal \"s3.ap-northeast-1.amazonaws.com\",\n        @dummy.avatar.s3_bucket.client.config.endpoint.host\n    end\n  end\n\n  context \"dynamic s3_host_name\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {},\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\",\n        s3_host_name: lambda {|a| a.instance.value }\n      @dummy = Dummy.new\n      class << @dummy\n        attr_accessor :value\n      end\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"uses s3_host_name as a proc if available\" do\n      @dummy.value = \"s3.something.com\"\n      assert_equal \"//s3.something.com/bucket/avatars/data\", @dummy.avatar.url(:original, timestamp: false)\n    end\n  end\n\n  context \"use_accelerate_endpoint\" do\n    context \"defaults to false\" do\n      before do\n        rebuild_model(\n          storage: :s3,\n          s3_credentials: {},\n          bucket: \"bucket\",\n          path: \":attachment/:basename:dotextension\",\n          s3_host_name: \"s3-ap-northeast-1.amazonaws.com\",\n          s3_region: \"ap-northeast-1\",\n        )\n        @dummy = Dummy.new\n        @dummy.avatar = stringy_file\n        @dummy.stubs(:new_record?).returns(false)\n      end\n\n      it \"returns a url based on an :s3_host_name path\" do\n        assert_match %r{^//s3-ap-northeast-1.amazonaws.com/bucket/avatars/data[^\\.]},\n          @dummy.avatar.url\n      end\n\n      it \"uses the S3 client with the use_accelerate_endpoint config is false\" do\n        expect(@dummy.avatar.s3_bucket.client.config.use_accelerate_endpoint).to be(false)\n      end\n    end\n\n    context \"set to true\" do\n      before do\n        rebuild_model(\n          storage: :s3,\n          s3_credentials: {},\n          bucket: \"bucket\",\n          path: \":attachment/:basename:dotextension\",\n          s3_host_name: \"s3-accelerate.amazonaws.com\",\n          s3_region: \"ap-northeast-1\",\n          use_accelerate_endpoint: true,\n        )\n        @dummy = Dummy.new\n        @dummy.avatar = stringy_file\n        @dummy.stubs(:new_record?).returns(false)\n      end\n\n      it \"returns a url based on an :s3_host_name path\" do\n        assert_match %r{^//s3-accelerate.amazonaws.com/bucket/avatars/data[^\\.]},\n          @dummy.avatar.url\n      end\n\n      it \"uses the S3 client with the use_accelerate_endpoint config is true\" do\n        expect(@dummy.avatar.s3_bucket.client.config.use_accelerate_endpoint).to be(true)\n      end\n    end\n  end\n\n  context \"An attachment that uses S3 for storage and has styles that return different file types\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        styles: { large: ['500x500#', :jpg] },\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\",\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        }\n\n        File.open(fixture_file('5k.png'), 'rb') do |file|\n          @dummy = Dummy.new\n          @dummy.avatar = file\n          @dummy.stubs(:new_record?).returns(false)\n        end\n    end\n\n    it \"returns a url containing the correct original file mime type\" do\n      assert_match /.+\\/5k.png/, @dummy.avatar.url\n    end\n\n    it 'uses the correct key for the original file mime type' do\n      assert_match /.+\\/5k.png/, @dummy.avatar.s3_object.key\n    end\n\n    it \"returns a url containing the correct processed file mime type\" do\n      assert_match /.+\\/5k.jpg/, @dummy.avatar.url(:large)\n    end\n\n    it \"uses the correct key for the processed file mime type\" do\n      assert_match /.+\\/5k.jpg/, @dummy.avatar.s3_object(:large).key\n    end\n  end\n\n  context \"An attachment that uses S3 for storage and has a proc for styles\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        styles: lambda { |attachment| attachment.instance.counter\n          {thumbnail: { geometry: \"50x50#\",\n                        s3_headers: {'Cache-Control' => 'max-age=31557600'}} }},\n        bucket: \"bucket\",\n        path: \":attachment/:style/:basename:dotextension\",\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        }\n\n        @file = File.new(fixture_file('5k.png'), 'rb')\n\n        Dummy.class_eval do\n          def counter\n            @counter ||= 0\n            @counter += 1\n            @counter\n          end\n        end\n\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n\n        object = stub\n        @dummy.avatar.stubs(:s3_object).with(:original).returns(object)\n        @dummy.avatar.stubs(:s3_object).with(:thumbnail).returns(object)\n\n        object.expects(:upload_file)\n          .with(anything, content_type: 'image/png',\n                acl: :\"public-read\")\n        object.expects(:upload_file)\n          .with(anything, content_type: 'image/png',\n                acl: :\"public-read\",\n                cache_control: 'max-age=31557600')\n        @dummy.save\n    end\n\n    after { @file.close }\n\n    it \"succeeds\" do\n      assert_equal @dummy.counter, 7\n    end\n  end\n\n  context \"An attachment that uses S3 for storage and has styles\" do\n    before do\n      rebuild_model(\n        (aws2_add_region).merge(\n          storage: :s3,\n          styles: { thumb: [\"90x90#\", :jpg] },\n          bucket: \"bucket\",\n          s3_credentials: {\n            \"access_key_id\" => \"12345\",\n            \"secret_access_key\" => \"54321\" }\n        )\n      )\n\n      @file = File.new(fixture_file(\"5k.png\"), \"rb\")\n      @dummy = Dummy.new\n      @dummy.avatar = @file\n      @dummy.save\n    end\n\n    context \"reprocess\" do\n      before do\n        @object = stub\n        @dummy.avatar.stubs(:s3_object).with(:original).returns(@object)\n        @dummy.avatar.stubs(:s3_object).with(:thumb).returns(@object)\n        @object.stubs(:get).yields(@file.read)\n        @object.stubs(:exists?).returns(true)\n      end\n\n      it \"uploads original\" do\n        @object.expects(:upload_file).with(\n          anything,\n          content_type: \"image/png\",\n          acl: :\"public-read\").returns(true)\n        @object.expects(:upload_file).with(\n          anything,\n          content_type: \"image/jpeg\",\n          acl: :\"public-read\").returns(true)\n        @dummy.avatar.reprocess!\n      end\n\n      it \"doesn't upload original\" do\n        @object.expects(:upload_file).with(\n          anything,\n          content_type: \"image/jpeg\",\n          acl: :\"public-read\").returns(true)\n        @dummy.avatar.reprocess!(:thumb)\n      end\n    end\n\n    after { @file.close }\n  end\n\n  context \"An attachment that uses S3 for storage and has spaces in file name\" do\n    before do\n      rebuild_model(\n        (aws2_add_region).merge storage: :s3,\n        styles: { large: [\"500x500#\", :jpg] },\n        bucket: \"bucket\",\n        s3_credentials: { \"access_key_id\" => \"12345\",\n                          \"secret_access_key\" => \"54321\" }\n        )\n\n      File.open(fixture_file(\"spaced file.png\"), \"rb\") do |file|\n        @dummy = Dummy.new\n        @dummy.avatar = file\n        @dummy.stubs(:new_record?).returns(false)\n      end\n    end\n\n    it \"returns a replaced version for path\" do\n      assert_match /.+\\/spaced_file\\.png/, @dummy.avatar.path\n    end\n\n    it \"returns a replaced version for url\" do\n      assert_match /.+\\/spaced_file\\.png/, @dummy.avatar.url\n    end\n  end\n\n  context \"An attachment that uses S3 for storage and has a question mark in file name\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        styles: { large: ['500x500#', :jpg] },\n        bucket: \"bucket\",\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        }\n\n      stringio = stringy_file\n      class << stringio\n        def original_filename\n          \"question?mark.png\"\n        end\n      end\n      file = Paperclip.io_adapters.for(stringio, hash_digest: Digest::MD5)\n      @dummy = Dummy.new\n      @dummy.avatar = file\n      @dummy.save\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a replaced version for path\" do\n      assert_match /.+\\/question_mark\\.png/, @dummy.avatar.path\n    end\n\n    it \"returns a replaced version for url\" do\n      assert_match /.+\\/question_mark\\.png/, @dummy.avatar.url\n    end\n  end\n\n  context \"\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {},\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\",\n        url: \":s3_domain_url\"\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url based on an S3 subdomain\" do\n      assert_match %r{^//bucket.s3.amazonaws.com/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n  end\n\n  context \"\" do\n    before do\n      rebuild_model(\n        (aws2_add_region).merge storage: :s3,\n        s3_credentials: {\n          production: { bucket: \"prod_bucket\" },\n          development: { bucket: \"dev_bucket\" }\n        },\n        bucket: \"bucket\",\n        s3_host_alias: \"something.something.com\",\n        path: \":attachment/:basename:dotextension\",\n        url: \":s3_alias_url\"\n        )\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url based on the host_alias\" do\n      assert_match %r{^//something.something.com/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n  end\n\n  context \"generating a url with a prefixed host alias\" do\n    before do\n      rebuild_model(\n        aws2_add_region.merge(\n          storage: :s3,\n          s3_credentials: {\n            production: { bucket: \"prod_bucket\" },\n            development: { bucket: \"dev_bucket\" },\n          },\n          bucket: \"bucket\",\n          s3_host_alias: \"something.something.com\",\n          s3_prefixes_in_alias: 2,\n          path: \"prefix1/prefix2/:attachment/:basename:dotextension\",\n          url: \":s3_alias_url\",\n        )\n      )\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url with the prefixes removed\" do\n      assert_match %r{^//something.something.com/avatars/data[^\\.]},\n                   @dummy.avatar.url\n    end\n  end\n\n  context \"generating a url with a proc as the host alias\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: { bucket: \"prod_bucket\" },\n        s3_host_alias: Proc.new{|atch| \"cdn#{atch.instance.counter % 4}.example.com\"},\n        path: \":attachment/:basename:dotextension\",\n        url: \":s3_alias_url\"\n      Dummy.class_eval do\n        def counter\n          @counter ||= 0\n          @counter += 1\n          @counter\n        end\n      end\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a url based on the host_alias\" do\n      assert_match %r{^//cdn1.example.com/avatars/data[^\\.]}, @dummy.avatar.url\n      assert_match %r{^//cdn2.example.com/avatars/data[^\\.]}, @dummy.avatar.url\n    end\n\n    it \"still returns the bucket name\" do\n      assert_equal \"prod_bucket\", @dummy.avatar.bucket_name\n    end\n\n  end\n\n  context \"\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {},\n        bucket: \"bucket\",\n        path: \":attachment/:basename:dotextension\",\n        url: \":asset_host\"\n      @dummy = Dummy.new\n      @dummy.avatar = stringy_file\n      @dummy.stubs(:new_record?).returns(false)\n    end\n\n    it \"returns a relative URL for Rails to calculate assets host\" do\n      assert_match %r{^avatars/data[^\\.]}, @dummy.avatar.url\n    end\n\n  end\n\n  context \"Generating a secure url with an expiration\" do\n    before do\n      @build_model_with_options = lambda {|options|\n        base_options = {\n          storage: :s3,\n          s3_credentials: {\n            production: { bucket: \"prod_bucket\" },\n            development: { bucket: \"dev_bucket\" }\n          },\n          s3_host_alias: \"something.something.com\",\n          s3_permissions: \"private\",\n          path: \":attachment/:basename:dotextension\",\n          url: \":s3_alias_url\"\n        }\n\n        rebuild_model (aws2_add_region).merge base_options.merge(options)\n      }\n    end\n\n    it \"uses default options\" do\n      @build_model_with_options[{}]\n\n      rails_env(\"production\") do\n        @dummy = Dummy.new\n        @dummy.avatar = stringy_file\n\n        object = stub\n        @dummy.avatar.stubs(:s3_object).returns(object)\n\n        object.expects(:presigned_url).with(:get, expires_in: 3600)\n        @dummy.avatar.expiring_url\n      end\n    end\n\n    it \"allows overriding s3_url_options\" do\n      @build_model_with_options[s3_url_options: { response_content_disposition: \"inline\" }]\n\n      rails_env(\"production\") do\n        @dummy = Dummy.new\n        @dummy.avatar = stringy_file\n\n        object = stub\n        @dummy.avatar.stubs(:s3_object).returns(object)\n        object.expects(:presigned_url)\n          .with(:get, expires_in: 3600,\n                response_content_disposition: \"inline\")\n        @dummy.avatar.expiring_url\n      end\n    end\n\n    it \"allows overriding s3_object options with a proc\" do\n      @build_model_with_options[s3_url_options: lambda {|attachment| { response_content_type: attachment.avatar_content_type } }]\n\n      rails_env(\"production\") do\n        @dummy = Dummy.new\n\n        @file = stringy_file\n        @file.stubs(:original_filename).returns(\"5k.png\\n\\n\")\n        Paperclip.stubs(:run).returns('image/png')\n        @file.stubs(:content_type).returns(\"image/png\\n\\n\")\n        @file.stubs(:to_tempfile).returns(@file)\n\n        @dummy.avatar = @file\n\n        object = stub\n        @dummy.avatar.stubs(:s3_object).returns(object)\n        object.expects(:presigned_url)\n          .with(:get, expires_in: 3600, response_content_type: \"image/png\")\n        @dummy.avatar.expiring_url\n      end\n    end\n  end\n\n  context \"#expiring_url\" do\n    before { @dummy = Dummy.new }\n\n    context \"with no attachment\" do\n      before { assert(!@dummy.avatar.exists?) }\n\n      it \"returns the default URL\" do\n        assert_equal(@dummy.avatar.url, @dummy.avatar.expiring_url)\n      end\n\n      it 'generates a url for a style when a file does not exist' do\n        assert_equal(@dummy.avatar.url(:thumb), @dummy.avatar.expiring_url(3600, :thumb))\n      end\n    end\n\n    it \"generates the same url when using Times and Integer offsets\" do\n      assert_equal @dummy.avatar.expiring_url(1234), @dummy.avatar.expiring_url(Time.now + 1234)\n    end\n  end\n\n  context \"Generating a url with an expiration for each style\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {\n          production: { bucket: \"prod_bucket\" },\n          development: { bucket: \"dev_bucket\" }\n        },\n        s3_permissions: :private,\n        s3_host_alias: \"something.something.com\",\n        path: \":attachment/:style/:basename:dotextension\",\n        url: \":s3_alias_url\"\n\n      rails_env(\"production\") do\n        @dummy = Dummy.new\n        @dummy.avatar = stringy_file\n      end\n    end\n\n    it \"generates a url for the thumb\" do\n      object = stub\n      @dummy.avatar.stubs(:s3_object).with(:thumb).returns(object)\n      object.expects(:presigned_url).with(:get, expires_in: 1800)\n      @dummy.avatar.expiring_url(1800, :thumb)\n    end\n\n    it \"generates a url for the default style\" do\n      object = stub\n      @dummy.avatar.stubs(:s3_object).with(:original).returns(object)\n      object.expects(:presigned_url).with(:get, expires_in: 1800)\n      @dummy.avatar.expiring_url(1800)\n    end\n  end\n\n  context \"Parsing S3 credentials with a bucket in them\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        s3_credentials: {\n          production: { bucket: \"prod_bucket\" },\n          development: { bucket: \"dev_bucket\" }\n        }\n      @dummy = Dummy.new\n    end\n\n    it \"gets the right bucket in production\" do\n      rails_env(\"production\") do\n        assert_equal \"prod_bucket\", @dummy.avatar.bucket_name\n        assert_equal \"prod_bucket\", @dummy.avatar.s3_bucket.name\n      end\n    end\n\n    it \"gets the right bucket in development\" do\n      rails_env(\"development\") do\n        assert_equal \"dev_bucket\", @dummy.avatar.bucket_name\n        assert_equal \"dev_bucket\", @dummy.avatar.s3_bucket.name\n      end\n    end\n  end\n\n  # the bucket.name is determined by the :s3_region\n  context \"Parsing S3 credentials with a s3_host_name in them\" do\n    before do\n      rebuild_model storage: :s3,\n        bucket: 'testing',\n        s3_credentials: {\n        production: {\n          s3_region: \"world-end\",\n          s3_host_name: \"s3-world-end.amazonaws.com\" },\n        development: {\n          s3_region: \"ap-northeast-1\",\n          s3_host_name: \"s3-ap-northeast-1.amazonaws.com\" },\n        test: {\n          s3_region: \"\" }\n        }\n      @dummy = Dummy.new\n    end\n\n    it \"gets the right s3_host_name in production\" do\n      rails_env(\"production\") do\n        assert_match %r{^s3-world-end.amazonaws.com}, @dummy.avatar.s3_host_name\n        assert_match %r{^s3.world-end.amazonaws.com},\n          @dummy.avatar.s3_bucket.client.config.endpoint.host\n      end\n    end\n\n    it \"gets the right s3_host_name in development\" do\n      rails_env(\"development\") do\n        assert_match %r{^s3.ap-northeast-1.amazonaws.com},\n          @dummy.avatar.s3_host_name\n        assert_match %r{^s3.ap-northeast-1.amazonaws.com},\n          @dummy.avatar.s3_bucket.client.config.endpoint.host\n      end\n    end\n\n    it \"gets the right s3_host_name if the key does not exist\" do\n      rails_env(\"test\") do\n        assert_match %r{^s3.amazonaws.com}, @dummy.avatar.s3_host_name\n        assert_raises(Aws::Errors::MissingRegionError) do\n          @dummy.avatar.s3_bucket.client.config.endpoint.host\n        end\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        path: \":attachment/:style/:basename:dotextension\",\n        s3_credentials: {\n          access_key_id: \"12345\",\n          secret_access_key: \"54321\"\n        }\n    end\n\n    it \"is extended by the S3 module\" do\n      assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3)\n    end\n\n    it \"won't be extended by the Filesystem module\" do\n      assert ! Dummy.new.avatar.is_a?(Paperclip::Storage::Filesystem)\n    end\n\n    context \"when assigned\" do\n      before do\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n        @dummy.stubs(:new_record?).returns(false)\n      end\n\n      after { @file.close }\n\n      it \"does not get a bucket to get a URL\" do\n        @dummy.avatar.expects(:s3).never\n        @dummy.avatar.expects(:s3_bucket).never\n        assert_match %r{^//s3\\.amazonaws\\.com/testing/avatars/original/5k\\.png}, @dummy.avatar.url\n      end\n\n      it \"is rewound after flush_writes\" do\n        @dummy.avatar.instance_eval \"def after_flush_writes; end\"\n        @dummy.avatar.stubs(:s3_object).returns(stub(upload_file: true))\n        files = @dummy.avatar.queued_for_write.values.each(&:read)\n        @dummy.save\n        assert files.none?(&:eof?), \"Expect all the files to be rewound.\"\n      end\n\n      it \"is removed after after_flush_writes\" do\n        @dummy.avatar.stubs(:s3_object).returns(stub(upload_file: true))\n        paths = @dummy.avatar.queued_for_write.values.map(&:path)\n        @dummy.save\n        assert paths.none?{ |path| File.exist?(path) },\n          \"Expect all the files to be deleted.\"\n      end\n\n      it \"will retry to save again but back off on SlowDown\" do\n        @dummy.avatar.stubs(:sleep)\n        Aws::S3::Object.any_instance.stubs(:upload_file).\n          raises(Aws::S3::Errors::SlowDown.new(stub,\n                                               stub(status: 503, body: \"\")))\n        expect {@dummy.save}.to raise_error(Aws::S3::Errors::SlowDown)\n        expect(@dummy.avatar).to have_received(:sleep).with(1)\n        expect(@dummy.avatar).to have_received(:sleep).with(2)\n        expect(@dummy.avatar).to have_received(:sleep).with(4)\n        expect(@dummy.avatar).to have_received(:sleep).with(8)\n        expect(@dummy.avatar).to have_received(:sleep).with(16)\n      end\n\n      context \"and saved\" do\n        before do\n          object = stub\n          @dummy.avatar.stubs(:s3_object).returns(object)\n          object.expects(:upload_file)\n            .with(anything, content_type: 'image/png', acl: :\"public-read\")\n          @dummy.save\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n\n      context \"and saved without a bucket\" do\n        before do\n          Aws::S3::Bucket.any_instance.expects(:create)\n          Aws::S3::Object.any_instance.stubs(:upload_file).\n            raises(Aws::S3::Errors::NoSuchBucket\n                    .new(stub,\n                         stub(status: 404, body: \"<foo/>\"))).then.returns(nil)\n          @dummy.save\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n\n      context \"and remove\" do\n        before do\n          Aws::S3::Object.any_instance.stubs(:exists?).returns(true)\n          Aws::S3::Object.any_instance.stubs(:delete)\n          @dummy.destroy\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n\n      context 'that the file were missing' do\n        before do\n          Aws::S3::Object.any_instance.stubs(:exists?)\n            .raises(Aws::S3::Errors::ServiceError.new(\"rspec stub raises\",\n                                                      \"object exists?\"))\n        end\n\n        it 'returns false on exists?' do\n          assert !@dummy.avatar.exists?\n        end\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage and bucket defined as a Proc\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: lambda { |attachment| \"bucket_#{attachment.instance.other}\" },\n        s3_credentials: {not: :important}\n    end\n\n    it \"gets the right bucket name\" do\n      assert \"bucket_a\", Dummy.new(other: 'a').avatar.bucket_name\n      assert \"bucket_a\", Dummy.new(other: 'a').avatar.s3_bucket.name\n      assert \"bucket_b\", Dummy.new(other: 'b').avatar.bucket_name\n      assert \"bucket_b\", Dummy.new(other: 'b').avatar.s3_bucket.name\n    end\n  end\n\n  context \"An attachment with S3 storage and S3 credentials defined as a Proc\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: {not: :important},\n        s3_credentials: lambda { |attachment|\n          Hash['access_key_id' => \"access#{attachment.instance.other}\", 'secret_access_key' => \"secret#{attachment.instance.other}\"]\n        }\n    end\n\n    it \"gets the right credentials\" do\n      assert \"access1234\", Dummy.new(other: '1234').avatar.s3_credentials[:access_key_id]\n      assert \"secret1234\", Dummy.new(other: '1234').avatar.s3_credentials[:secret_access_key]\n    end\n  end\n\n  context \"An attachment with S3 storage and S3 credentials with a :credential_provider\" do\n    before do\n      class DummyCredentialProvider; end\n\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        s3_credentials: {\n          credentials: DummyCredentialProvider.new\n        }\n      @dummy = Dummy.new\n    end\n\n    it \"sets the credential-provider\" do\n      expect(@dummy.avatar.s3_bucket.client.config.credentials).to be_a DummyCredentialProvider\n    end\n  end\n\n  context \"An attachment with S3 storage and S3 credentials in an unsupported manor\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\", s3_credentials: [\"unsupported\"]\n      @dummy = Dummy.new\n    end\n\n    it \"does not accept the credentials\" do\n      assert_raises(ArgumentError) do\n        @dummy.avatar.s3_credentials\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage and S3 credentials not supplied\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3, bucket: \"testing\"\n      @dummy = Dummy.new\n    end\n\n    it \"does not parse any credentials\" do\n      assert_equal({}, @dummy.avatar.s3_credentials)\n    end\n  end\n\n  context \"An attachment with S3 storage and specific s3 headers set\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        path: \":attachment/:style/:basename:dotextension\",\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        },\n        s3_headers: {'Cache-Control' => 'max-age=31557600'}\n    end\n\n    context \"when assigned\" do\n      before do\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      context \"and saved\" do\n        before do\n          object = stub\n          @dummy.avatar.stubs(:s3_object).returns(object)\n\n          object.expects(:upload_file)\n            .with(anything,\n                  content_type: 'image/png',\n                  acl: :\"public-read\",\n                  cache_control: 'max-age=31557600')\n          @dummy.save\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage and metadata set using header names\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        path: \":attachment/:style/:basename:dotextension\",\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        },\n        s3_headers: {'x-amz-meta-color' => 'red'}\n    end\n\n    context \"when assigned\" do\n      before do\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      context \"and saved\" do\n        before do\n          object = stub\n          @dummy.avatar.stubs(:s3_object).returns(object)\n\n          object.expects(:upload_file)\n            .with(anything,\n                  content_type: 'image/png',\n                  acl: :\"public-read\",\n                  metadata: { \"color\" => \"red\" })\n          @dummy.save\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage and metadata set using the :s3_metadata option\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        path: \":attachment/:style/:basename:dotextension\",\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        },\n        s3_metadata: { \"color\" => \"red\" }\n    end\n\n    context \"when assigned\" do\n      before do\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      context \"and saved\" do\n        before do\n          object = stub\n          @dummy.avatar.stubs(:s3_object).returns(object)\n\n          object.expects(:upload_file)\n            .with(anything,\n                  content_type: 'image/png',\n                  acl: :\"public-read\",\n                  metadata: { \"color\" => \"red\" })\n          @dummy.save\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage and storage class set\" do\n    context \"using the header name\" do\n      before do\n        rebuild_model (aws2_add_region).merge storage: :s3,\n          bucket: \"testing\",\n          path: \":attachment/:style/:basename:dotextension\",\n          s3_credentials: {\n            'access_key_id' => \"12345\",\n            'secret_access_key' => \"54321\"\n          },\n          s3_headers: { \"x-amz-storage-class\" => \"reduced_redundancy\" }\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        after { @file.close }\n\n        context \"and saved\" do\n          before do\n            object = stub\n            @dummy.avatar.stubs(:s3_object).returns(object)\n\n            object.expects(:upload_file)\n              .with(anything,\n                    content_type: 'image/png',\n                    acl: :\"public-read\",\n                    storage_class: \"reduced_redundancy\")\n            @dummy.save\n          end\n\n          it \"succeeds\" do\n            assert true\n          end\n        end\n      end\n    end\n\n    context \"using per style hash\" do\n      before do\n        rebuild_model (aws2_add_region).merge :storage => :s3,\n          :bucket => \"testing\",\n          :path => \":attachment/:style/:basename.:extension\",\n          :styles => {\n            :thumb => \"80x80>\"\n          },\n          :s3_credentials => {\n            'access_key_id' => \"12345\",\n            'secret_access_key' => \"54321\"\n          },\n          :s3_storage_class => {\n            :thumb => :reduced_redundancy\n          }\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        after { @file.close }\n\n        context \"and saved\" do\n          before do\n            object = stub\n            [:thumb, :original].each do |style|\n              @dummy.avatar.stubs(:s3_object).with(style).returns(object)\n\n              expected_options = {\n                :content_type => \"image/png\",\n                acl: :\"public-read\"\n              }\n              expected_options.merge!(:storage_class => :reduced_redundancy) if style == :thumb\n\n              object.expects(:upload_file)\n                .with(anything, expected_options)\n            end\n            @dummy.save\n          end\n\n          it \"succeeds\" do\n            assert true\n          end\n        end\n      end\n    end\n\n    context \"using global hash option\" do\n      before do\n        rebuild_model (aws2_add_region).merge :storage => :s3,\n          :bucket => \"testing\",\n          :path => \":attachment/:style/:basename.:extension\",\n          :styles => {\n            :thumb => \"80x80>\"\n          },\n          :s3_credentials => {\n            'access_key_id' => \"12345\",\n            'secret_access_key' => \"54321\"\n          },\n          :s3_storage_class => :reduced_redundancy\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        after { @file.close }\n\n        context \"and saved\" do\n          before do\n            object = stub\n            [:thumb, :original].each do |style|\n              @dummy.avatar.stubs(:s3_object).with(style).returns(object)\n\n              object.expects(:upload_file)\n                .with(anything, :content_type => \"image/png\",\n                      acl: :\"public-read\",\n                      :storage_class => :reduced_redundancy)\n            end\n            @dummy.save\n          end\n\n          it \"succeeds\" do\n            assert true\n          end\n        end\n      end\n    end\n  end\n\n  context \"Can disable AES256 encryption multiple ways\" do\n    [nil, false, ''].each do |tech|\n      before do\n        rebuild_model(\n          (aws2_add_region).merge storage: :s3,\n          bucket: \"testing\",\n          path: \":attachment/:style/:basename:dotextension\",\n          s3_credentials: {\n            'access_key_id'          => \"12345\",\n            'secret_access_key'      => \"54321\"},\n            s3_server_side_encryption: tech)\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        after { @file.close }\n\n        context \"and saved\" do\n          before do\n            object = stub\n            @dummy.avatar.stubs(:s3_object).returns(object)\n\n            object.expects(:upload_file)\n              .with(anything, :content_type => \"image/png\", acl: :\"public-read\")\n            @dummy.save\n          end\n\n          it \"succeeds\" do\n            assert true\n          end\n        end\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage and using AES256 encryption\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        path: \":attachment/:style/:basename:dotextension\",\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        },\n        s3_server_side_encryption: \"AES256\"\n    end\n\n    context \"when assigned\" do\n      before do\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      context \"and saved\" do\n        before do\n          object = stub\n          @dummy.avatar.stubs(:s3_object).returns(object)\n\n          object.expects(:upload_file)\n            .with(anything, content_type: \"image/png\",\n                  acl: :\"public-read\",\n                  server_side_encryption: \"AES256\")\n          @dummy.save\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage and storage class set using the :storage_class option\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        path: \":attachment/:style/:basename:dotextension\",\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        },\n        s3_storage_class: :reduced_redundancy\n    end\n\n    context \"when assigned\" do\n      before do\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      context \"and saved\" do\n        before do\n          object = stub\n          @dummy.avatar.stubs(:s3_object).returns(object)\n\n          object.expects(:upload_file)\n            .with(anything,\n                  content_type: \"image/png\",\n                  acl: :\"public-read\",\n                  storage_class: :reduced_redundancy)\n          @dummy.save\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n    end\n  end\n\n  context \"with S3 credentials supplied as Pathname\" do\n    before do\n      ENV['S3_KEY']    = 'pathname_key'\n      ENV['S3_BUCKET'] = 'pathname_bucket'\n      ENV['S3_SECRET'] = 'pathname_secret'\n\n      rails_env('test') do\n        rebuild_model (aws2_add_region).merge storage: :s3,\n          s3_credentials: Pathname.new(fixture_file('s3.yml'))\n\n        Dummy.delete_all\n        @dummy = Dummy.new\n      end\n    end\n\n    it \"parses the credentials\" do\n      assert_equal 'pathname_bucket', @dummy.avatar.bucket_name\n\n      assert_equal 'pathname_key',\n         @dummy.avatar.s3_bucket.client.config.access_key_id\n\n      assert_equal 'pathname_secret',\n         @dummy.avatar.s3_bucket.client.config.secret_access_key\n    end\n  end\n\n  context \"with S3 credentials in a YAML file\" do\n    before do\n      ENV['S3_KEY']    = 'env_key'\n      ENV['S3_BUCKET'] = 'env_bucket'\n      ENV['S3_SECRET'] = 'env_secret'\n\n      rails_env('test') do\n        rebuild_model (aws2_add_region).merge storage: :s3,\n          s3_credentials: File.new(fixture_file('s3.yml'))\n\n        Dummy.delete_all\n\n        @dummy = Dummy.new\n      end\n    end\n\n    it \"runs the file through ERB\" do\n      assert_equal 'env_bucket', @dummy.avatar.bucket_name\n\n      assert_equal 'env_key',\n          @dummy.avatar.s3_bucket.client.config.access_key_id\n\n      assert_equal 'env_secret',\n          @dummy.avatar.s3_bucket.client.config.secret_access_key\n    end\n  end\n\n  context \"S3 Permissions\" do\n    context \"defaults to :public_read\" do\n      before do\n        rebuild_model (aws2_add_region).merge storage: :s3,\n          bucket: \"testing\",\n          path: \":attachment/:style/:basename:dotextension\",\n          s3_credentials: {\n            'access_key_id' => \"12345\",\n            'secret_access_key' => \"54321\"\n          }\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        after { @file.close }\n\n        context \"and saved\" do\n          before do\n            object = stub\n            @dummy.avatar.stubs(:s3_object).returns(object)\n\n            object.expects(:upload_file)\n              .with(anything, content_type: \"image/png\", acl: :\"public-read\")\n            @dummy.save\n          end\n\n          it \"succeeds\" do\n            assert true\n          end\n        end\n      end\n    end\n\n    context \"string permissions set\" do\n      before do\n        rebuild_model (aws2_add_region).merge storage: :s3,\n          bucket: \"testing\",\n          path: \":attachment/:style/:basename:dotextension\",\n          s3_credentials: {\n            'access_key_id' => \"12345\",\n            'secret_access_key' => \"54321\"\n          },\n          s3_permissions: :private\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        after { @file.close }\n\n        context \"and saved\" do\n          before do\n            object = stub\n            @dummy.avatar.stubs(:s3_object).returns(object)\n\n            object.expects(:upload_file)\n              .with(anything, content_type: \"image/png\", acl: :private)\n            @dummy.save\n          end\n\n          it \"succeeds\" do\n            assert true\n          end\n        end\n      end\n    end\n\n    context \"hash permissions set\" do\n      before do\n        rebuild_model (aws2_add_region).merge storage: :s3,\n          bucket: \"testing\",\n          path: \":attachment/:style/:basename:dotextension\",\n          styles: {\n            thumb: \"80x80>\"\n          },\n          s3_credentials: {\n            'access_key_id' => \"12345\",\n            'secret_access_key' => \"54321\"\n          },\n          s3_permissions: {\n            original: :private,\n            thumb: :public_read\n          }\n      end\n\n      context \"when assigned\" do\n        before do\n          @file = File.new(fixture_file('5k.png'), 'rb')\n          @dummy = Dummy.new\n          @dummy.avatar = @file\n        end\n\n        after { @file.close }\n\n        context \"and saved\" do\n          before do\n            [:thumb, :original].each do |style|\n              object = stub\n              @dummy.avatar.stubs(:s3_object).with(style).returns(object)\n\n              object.expects(:upload_file)\n                .with(anything,\n                      content_type: \"image/png\",\n                      acl: style == :thumb ? :public_read : :private)\n            end\n            @dummy.save\n          end\n\n          it \"succeeds\" do\n            assert true\n          end\n        end\n      end\n    end\n\n    context \"proc permission set\" do\n      before do\n        rebuild_model(\n          (aws2_add_region).merge storage: :s3,\n          bucket: \"testing\",\n          path: \":attachment/:style/:basename:dotextension\",\n          styles: {\n            thumb: \"80x80>\"\n          },\n          s3_credentials: {\n            'access_key_id' => \"12345\",\n            'secret_access_key' => \"54321\"\n          },\n          s3_permissions: lambda {|attachment, style|\n            attachment.instance.private_attachment? && style.to_sym != :thumb ? :private : :\"public-read\"\n          }\n        )\n      end\n    end\n  end\n\n  context \"An attachment with S3 storage and metadata set using a proc as headers\" do\n    before do\n      rebuild_model(\n        (aws2_add_region).merge storage: :s3,\n        bucket: \"testing\",\n        path: \":attachment/:style/:basename:dotextension\",\n        styles: {\n          thumb: \"80x80>\"\n        },\n        s3_credentials: {\n          'access_key_id' => \"12345\",\n          'secret_access_key' => \"54321\"\n        },\n        s3_headers: lambda {|attachment|\n          {'Content-Disposition' => \"attachment; filename=\\\"#{attachment.name}\\\"\"}\n        }\n      )\n    end\n\n    context \"when assigned\" do\n      before do\n        @file = File.new(fixture_file('5k.png'), 'rb')\n        @dummy = Dummy.new\n        @dummy.stubs(name: 'Custom Avatar Name.png')\n        @dummy.avatar = @file\n      end\n\n      after { @file.close }\n\n      context \"and saved\" do\n        before do\n          [:thumb, :original].each do |style|\n            object = stub\n            @dummy.avatar.stubs(:s3_object).with(style).returns(object)\n\n            object.expects(:upload_file)\n              .with(anything,\n                    content_type: \"image/png\",\n                    acl: :\"public-read\",\n                    content_disposition: 'attachment; filename=\"Custom Avatar Name.png\"')\n          end\n          @dummy.save\n        end\n\n        it \"succeeds\" do\n          assert true\n        end\n      end\n    end\n  end\n\n  context \"path is a proc\" do\n    before do\n      rebuild_model (aws2_add_region).merge storage: :s3,\n        path: ->(attachment) { attachment.instance.attachment_path }\n\n      @dummy = Dummy.new\n      @dummy.class_eval do\n        def attachment_path\n          '/some/dynamic/path'\n        end\n      end\n      @dummy.avatar = stringy_file\n    end\n\n    it \"returns a correct path\" do\n      assert_match '/some/dynamic/path', @dummy.avatar.path\n    end\n  end\n\n  private\n\n  def rails_env(env)\n    stored_env, Rails.env = Rails.env, env\n    begin\n      yield\n    ensure\n      Rails.env = stored_env\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/style_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Style do\n  context \"A style rule\" do\n    before do\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: { foo: {geometry: \"100x100#\", format: :png} },\n                               whiny: true\n      @style = @attachment.styles[:foo]\n    end\n\n    it \"is held as a Style object\" do\n      expect(@style).to be_a Paperclip::Style\n    end\n\n    it \"gets processors from the attachment definition\" do\n      assert_equal [:thumbnail], @style.processors\n    end\n\n    it \"has the right geometry\" do\n      assert_equal \"100x100#\", @style.geometry\n    end\n\n    it \"is whiny if the attachment is\" do\n      assert @style.whiny?\n    end\n\n    it \"responds to hash notation\" do\n      assert_equal [:thumbnail], @style[:processors]\n      assert_equal \"100x100#\", @style[:geometry]\n    end\n\n    it \"returns the name of the style in processor options\" do\n      assert_equal :foo, @style.processor_options[:style]\n    end\n  end\n\n  context \"A style rule with properties supplied as procs\" do\n    before do\n      @attachment = attachment path: \":basename.:extension\",\n                               whiny_thumbnails: true,\n                               processors: lambda {|a| [:test]},\n                               styles: {\n                                 foo: lambda{|a| \"300x300#\"},\n                                 bar: {\n                                   geometry: lambda{|a| \"300x300#\"},\n                                   convert_options: lambda{|a| \"-do_stuff\"},\n                                   source_file_options: lambda{|a| \"-do_extra_stuff\"}\n                                 }\n                               }\n    end\n\n    it \"calls procs when they are needed\" do\n      assert_equal \"300x300#\", @attachment.styles[:foo].geometry\n      assert_equal \"300x300#\", @attachment.styles[:bar].geometry\n      assert_equal [:test], @attachment.styles[:foo].processors\n      assert_equal [:test], @attachment.styles[:bar].processors\n      assert_equal \"-do_stuff\", @attachment.styles[:bar].convert_options\n      assert_equal \"-do_extra_stuff\", @attachment.styles[:bar].source_file_options\n    end\n  end\n\n  context \"An attachment with style rules in various forms\" do\n    before do\n      styles = {}\n      styles[:aslist] = [\"100x100\", :png]\n      styles[:ashash] = {geometry: \"100x100\", format: :png}\n      styles[:asstring] = \"100x100\"\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: styles\n    end\n\n    it \"has the right number of styles\" do\n      expect(@attachment.styles).to be_a Hash\n      assert_equal 3, @attachment.styles.size\n    end\n\n    it \"has styles as Style objects\" do\n      [:aslist, :ashash, :aslist].each do |s|\n        expect(@attachment.styles[s]).to be_a Paperclip::Style\n      end\n    end\n\n    it \"has the right geometries\" do\n      [:aslist, :ashash, :aslist].each do |s|\n        assert_equal @attachment.styles[s].geometry, \"100x100\"\n      end\n    end\n\n    it \"has the right formats\" do\n      assert_equal @attachment.styles[:aslist].format, :png\n      assert_equal @attachment.styles[:ashash].format, :png\n      assert_nil @attachment.styles[:asstring].format\n    end\n\n    it \"retains order\" do\n      assert_equal [:aslist, :ashash, :asstring], @attachment.styles.keys\n    end\n  end\n\n  context \"An attachment with :convert_options\" do\n    it \"does not have called extra_options_for(:thumb/:large) on initialization\" do\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: {thumb: \"100x100\", large: \"400x400\"},\n                               convert_options: {all: \"-do_stuff\", thumb: \"-thumbnailize\"}\n      @attachment.expects(:extra_options_for).never\n      @style = @attachment.styles[:thumb]\n    end\n\n    it \"calls extra_options_for(:thumb/:large) when convert options are requested\" do\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: {thumb: \"100x100\", large: \"400x400\"},\n                               convert_options: {all: \"-do_stuff\", thumb: \"-thumbnailize\"}\n      @style = @attachment.styles[:thumb]\n      @file = StringIO.new(\"...\")\n      @file.stubs(:original_filename).returns(\"file.jpg\")\n\n      @attachment.expects(:extra_options_for).with(:thumb)\n      @attachment.styles[:thumb].convert_options\n    end\n  end\n\n  context \"An attachment with :source_file_options\" do\n    it \"does not have called extra_source_file_options_for(:thumb/:large) on initialization\" do\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: {thumb: \"100x100\", large: \"400x400\"},\n                               source_file_options: {all: \"-density 400\", thumb: \"-depth 8\"}\n      @attachment.expects(:extra_source_file_options_for).never\n      @style = @attachment.styles[:thumb]\n    end\n\n    it \"calls extra_options_for(:thumb/:large) when convert options are requested\" do\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: {thumb: \"100x100\", large: \"400x400\"},\n                               source_file_options: {all: \"-density 400\", thumb: \"-depth 8\"}\n      @style = @attachment.styles[:thumb]\n      @file = StringIO.new(\"...\")\n      @file.stubs(:original_filename).returns(\"file.jpg\")\n\n      @attachment.expects(:extra_source_file_options_for).with(:thumb)\n      @attachment.styles[:thumb].source_file_options\n    end\n  end\n\n  context \"A style rule with its own :processors\" do\n    before do\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: {\n                                 foo: {\n                                   geometry: \"100x100#\",\n                                   format: :png,\n                                   processors: [:test]\n                                  }\n                                },\n                               processors: [:thumbnail]\n      @style = @attachment.styles[:foo]\n    end\n\n    it \"does not get processors from the attachment\" do\n      @attachment.expects(:processors).never\n      assert_not_equal [:thumbnail], @style.processors\n    end\n\n    it \"reports its own processors\" do\n      assert_equal [:test], @style.processors\n    end\n\n  end\n\n  context \"A style rule with :processors supplied as procs\" do\n    before do\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: {\n                                 foo: {\n                                   geometry: \"100x100#\",\n                                   format: :png,\n                                   processors: lambda{|a| [:test]}\n                                  }\n                                },\n                               processors: [:thumbnail]\n    end\n\n    it \"defers processing of procs until they are needed\" do\n      expect(@attachment.styles[:foo].instance_variable_get(\"@processors\")).to be_a Proc\n    end\n\n    it \"calls procs when they are needed\" do\n      assert_equal [:test], @attachment.styles[:foo].processors\n    end\n  end\n\n  context \"An attachment with :convert_options and :source_file_options in :styles\" do\n    before do\n      @attachment = attachment path: \":basename.:extension\",\n                               styles: {\n                                 thumb: \"100x100\",\n                                 large: {geometry: \"400x400\",\n                                            convert_options: \"-do_stuff\",\n                                            source_file_options: \"-do_extra_stuff\"\n                                 }\n                               }\n      @file = StringIO.new(\"...\")\n      @file.stubs(:original_filename).returns(\"file.jpg\")\n    end\n\n    it \"has empty options for :thumb style\" do\n      assert_equal \"\", @attachment.styles[:thumb].processor_options[:convert_options]\n      assert_equal \"\", @attachment.styles[:thumb].processor_options[:source_file_options]\n    end\n\n    it \"has the right options for :large style\" do\n      assert_equal \"-do_stuff\", @attachment.styles[:large].processor_options[:convert_options]\n      assert_equal \"-do_extra_stuff\", @attachment.styles[:large].processor_options[:source_file_options]\n    end\n  end\n\n  context \"A style rule supplied with default format\" do\n     before do\n       @attachment = attachment default_format: :png,\n                                styles: {\n                                  asstring: \"300x300#\",\n                                  aslist: [\"300x300#\", :jpg],\n                                  ashash: {\n                                    geometry: \"300x300#\",\n                                    convert_options: \"-do_stuff\"\n                                  }\n                                }\n     end\n\n     it \"has the right number of styles\" do\n       expect(@attachment.styles).to be_a Hash\n       assert_equal 3, @attachment.styles.size\n     end\n\n     it \"has styles as Style objects\" do\n       [:aslist, :ashash, :aslist].each do |s|\n         expect(@attachment.styles[s]).to be_a Paperclip::Style\n       end\n     end\n\n     it \"has the right geometries\" do\n       [:aslist, :ashash, :aslist].each do |s|\n         assert_equal @attachment.styles[s].geometry, \"300x300#\"\n       end\n     end\n\n     it \"has the right formats\" do\n       assert_equal @attachment.styles[:aslist].format,    :jpg\n       assert_equal @attachment.styles[:ashash].format,    :png\n       assert_equal @attachment.styles[:asstring].format,  :png\n     end\n\n   end\nend\n"
  },
  {
    "path": "spec/paperclip/tempfile_factory_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::TempfileFactory do\n  it \"is able to generate a tempfile with the right name\" do\n    file = subject.generate(\"omg.png\")\n    assert File.extname(file.path), \"png\"\n  end\n\n  it \"is able to generate a tempfile with the right name with a tilde at the beginning\" do\n    file = subject.generate(\"~omg.png\")\n    assert File.extname(file.path), \"png\"\n  end\n\n  it \"is able to generate a tempfile with the right name with a tilde at the end\" do\n    file = subject.generate(\"omg.png~\")\n    assert File.extname(file.path), \"png\"\n  end\n\n  it \"is able to generate a tempfile from a file with a really long name\" do\n    filename = \"#{\"longfilename\" * 100}.png\"\n    file = subject.generate(filename)\n    assert File.extname(file.path), \"png\"\n  end\n\n  it 'is able to take nothing as a parameter and not error' do\n   file = subject.generate\n   assert File.exist?(file.path)\n  end\n\n  it \"does not throw Errno::ENAMETOOLONG when it has a really long name\" do\n    expect { subject.generate(\"o\" * 255) }.to_not raise_error\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/tempfile_spec.rb",
    "content": "require \"spec_helper\"\n\ndescribe Paperclip::Tempfile do\n  context \"A Paperclip Tempfile\" do\n    before do\n      @tempfile = described_class.new([\"file\", \".jpg\"])\n    end\n\n    after { @tempfile.close }\n\n    it \"has its path contain a real extension\" do\n      assert_equal \".jpg\", File.extname(@tempfile.path)\n    end\n\n    it \"is a real Tempfile\" do\n      assert @tempfile.is_a?(::Tempfile)\n    end\n  end\n\n  context \"Another Paperclip Tempfile\" do\n    before do\n      @tempfile = described_class.new(\"file\")\n    end\n\n    after { @tempfile.close }\n\n    it \"does not have an extension if not given one\" do\n      assert_equal \"\", File.extname(@tempfile.path)\n    end\n\n    it \"is a real Tempfile\" do\n      assert @tempfile.is_a?(::Tempfile)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/thumbnail_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Thumbnail do\n  context \"An image\" do\n    before do\n      @file = File.new(fixture_file(\"5k.png\"), 'rb')\n    end\n\n    after { @file.close }\n\n    [[\"600x600>\", \"434x66\"],\n     [\"400x400>\", \"400x61\"],\n     [\"32x32<\", \"434x66\"],\n     [nil, \"434x66\"]\n    ].each do |args|\n      context \"being thumbnailed with a geometry of #{args[0]}\" do\n        before do\n          @thumb = Paperclip::Thumbnail.new(@file, geometry: args[0])\n        end\n\n        it \"starts with dimensions of 434x66\" do\n          cmd = %Q[identify -format \"%wx%h\" \"#{@file.path}\"]\n          assert_equal \"434x66\", `#{cmd}`.chomp\n        end\n\n        it \"reports the correct target geometry\" do\n          assert_equal args[0].to_s, @thumb.target_geometry.to_s\n        end\n\n        context \"when made\" do\n          before do\n            @thumb_result = @thumb.make\n          end\n\n          it \"is the size we expect it to be\" do\n            cmd = %Q[identify -format \"%wx%h\" \"#{@thumb_result.path}\"]\n            assert_equal args[1], `#{cmd}`.chomp\n          end\n        end\n      end\n    end\n\n    context \"being thumbnailed at 100x50 with cropping\" do\n      before do\n        @thumb = Paperclip::Thumbnail.new(@file, geometry: \"100x50#\")\n      end\n\n      it \"lets us know when a command isn't found versus a processing error\" do\n        old_path = ENV['PATH']\n        begin\n          Terrapin::CommandLine.path = ''\n          Paperclip.options[:command_path] = ''\n          ENV['PATH'] = ''\n          assert_raises(Paperclip::Errors::CommandNotFoundError) do\n            silence_stream(STDERR) do\n              @thumb.make\n            end\n          end\n        ensure\n          ENV['PATH'] = old_path\n        end\n      end\n\n      it \"reports its correct current and target geometries\" do\n        assert_equal \"100x50#\", @thumb.target_geometry.to_s\n        assert_equal \"434x66\", @thumb.current_geometry.to_s\n      end\n\n      it \"reports its correct format\" do\n        assert_nil @thumb.format\n      end\n\n      it \"has whiny turned on by default\" do\n        assert @thumb.whiny\n      end\n\n      it \"has convert_options set to nil by default\" do\n        assert_equal nil, @thumb.convert_options\n      end\n\n      it \"has source_file_options set to nil by default\" do\n        assert_equal nil, @thumb.source_file_options\n      end\n\n      it \"sends the right command to convert when sent #make\" do\n        @thumb.expects(:convert).with do |*arg|\n          arg[0] == ':source -auto-orient -resize \"x50\" -crop \"100x50+114+0\" +repage :dest' &&\n          arg[1][:source] == \"#{File.expand_path(@thumb.file.path)}[0]\"\n        end\n        @thumb.make\n      end\n\n      it \"creates the thumbnail when sent #make\" do\n        dst = @thumb.make\n        assert_match /100x50/, `identify \"#{dst.path}\"`\n      end\n    end\n\n    it 'crops a EXIF-rotated image properly' do\n      file = File.new(fixture_file('rotated.jpg'))\n      thumb = Paperclip::Thumbnail.new(file, geometry: \"50x50#\")\n\n      output_file = thumb.make\n\n      command = Terrapin::CommandLine.new(\"identify\", \"-format %wx%h :file\")\n      assert_equal \"50x50\", command.run(file: output_file.path).strip\n    end\n\n    context \"being thumbnailed with source file options set\" do\n      before do\n        @thumb = Paperclip::Thumbnail.new(@file,\n                                          geometry: \"100x50#\",\n                                          source_file_options: \"-strip\")\n      end\n\n      it \"has source_file_options value set\" do\n        assert_equal [\"-strip\"], @thumb.source_file_options\n      end\n\n      it \"sends the right command to convert when sent #make\" do\n        @thumb.expects(:convert).with do |*arg|\n          arg[0] == '-strip :source -auto-orient -resize \"x50\" -crop \"100x50+114+0\" +repage :dest' &&\n          arg[1][:source] == \"#{File.expand_path(@thumb.file.path)}[0]\"\n        end\n        @thumb.make\n      end\n\n      it \"creates the thumbnail when sent #make\" do\n        dst = @thumb.make\n        assert_match /100x50/, `identify \"#{dst.path}\"`\n      end\n\n      context \"redefined to have bad source_file_options setting\" do\n        before do\n          @thumb = Paperclip::Thumbnail.new(@file,\n                                            geometry: \"100x50#\",\n                                            source_file_options: \"-this-aint-no-option\")\n        end\n\n        it \"errors when trying to create the thumbnail\" do\n          assert_raises(Paperclip::Error) do\n            silence_stream(STDERR) do\n              @thumb.make\n            end\n          end\n        end\n      end\n    end\n\n    context \"being thumbnailed with convert options set\" do\n      before do\n        @thumb = Paperclip::Thumbnail.new(@file,\n                                          geometry: \"100x50#\",\n                                          convert_options: \"-strip -depth 8\")\n      end\n\n      it \"has convert_options value set\" do\n        assert_equal %w\"-strip -depth 8\", @thumb.convert_options\n      end\n\n      it \"sends the right command to convert when sent #make\" do\n        @thumb.expects(:convert).with do |*arg|\n          arg[0] == ':source -auto-orient -resize \"x50\" -crop \"100x50+114+0\" +repage -strip -depth 8 :dest' &&\n          arg[1][:source] == \"#{File.expand_path(@thumb.file.path)}[0]\"\n        end\n        @thumb.make\n      end\n\n      it \"creates the thumbnail when sent #make\" do\n        dst = @thumb.make\n        assert_match /100x50/, `identify \"#{dst.path}\"`\n      end\n\n      context \"redefined to have bad convert_options setting\" do\n        before do\n          @thumb = Paperclip::Thumbnail.new(@file,\n                                            geometry: \"100x50#\",\n                                            convert_options: \"-this-aint-no-option\")\n        end\n\n        it \"errors when trying to create the thumbnail\" do\n          silence_stream(STDERR) do\n            expect {\n              @thumb.make\n            }.to raise_error(\n              Paperclip::Error, /unrecognized option `-this-aint-no-option'/\n            )\n          end\n        end\n\n        it \"lets us know when a command isn't found versus a processing error\" do\n          old_path = ENV['PATH']\n          begin\n            Terrapin::CommandLine.path = ''\n            Paperclip.options[:command_path] = ''\n            ENV['PATH'] = ''\n            assert_raises(Paperclip::Errors::CommandNotFoundError) do\n              silence_stream(STDERR) do\n                @thumb.make\n              end\n            end\n          ensure\n            ENV['PATH'] = old_path\n          end\n        end\n      end\n    end\n\n    context \"being thumbnailed with a blank geometry string\" do\n      before do\n        @thumb = Paperclip::Thumbnail.new(@file,\n                                          geometry: \"\",\n                                          convert_options: \"-gravity center -crop \\\"300x300+0-0\\\"\")\n      end\n\n      it \"does not get resized by default\" do\n        assert !@thumb.transformation_command.include?(\"-resize\")\n      end\n    end\n\n    context \"being thumbnailed with default animated option (true)\" do\n      it \"calls identify to check for animated images when sent #make\" do\n        thumb = Paperclip::Thumbnail.new(@file, geometry: \"100x50#\")\n        thumb.expects(:identify).at_least_once.with do |*arg|\n          arg[0] == '-format %m :file' &&\n          arg[1][:file] == \"#{File.expand_path(thumb.file.path)}[0]\"\n        end\n        thumb.make\n      end\n    end\n\n    context \"passing a custom file geometry parser\" do\n      after do\n        Object.send(:remove_const, :GeoParser) if Object.const_defined?(:GeoParser)\n      end\n\n      it \"produces the appropriate transformation_command\" do\n        GeoParser = Class.new do\n          def self.from_file(file)\n            new\n          end\n\n          def transformation_to(target, should_crop)\n            [\"SCALE\", \"CROP\"]\n          end\n        end\n\n        thumb = Paperclip::Thumbnail.new(@file, geometry: '50x50', file_geometry_parser: ::GeoParser)\n\n        transformation_command = thumb.transformation_command\n\n        assert transformation_command.include?('-crop'),\n          %{expected #{transformation_command.inspect} to include '-crop'}\n        assert transformation_command.include?('\"CROP\"'),\n          %{expected #{transformation_command.inspect} to include '\"CROP\"'}\n        assert transformation_command.include?('-resize'),\n          %{expected #{transformation_command.inspect} to include '-resize'}\n        assert transformation_command.include?('\"SCALE\"'),\n          %{expected #{transformation_command.inspect} to include '\"SCALE\"'}\n      end\n    end\n\n    context \"passing a custom geometry string parser\" do\n      after do\n        Object.send(:remove_const, :GeoParser) if Object.const_defined?(:GeoParser)\n      end\n\n      it \"produces the appropriate transformation_command\" do\n        GeoParser = Class.new do\n          def self.parse(s)\n            new\n          end\n\n          def to_s\n            \"151x167\"\n          end\n        end\n\n        thumb = Paperclip::Thumbnail.new(@file, geometry: '50x50', string_geometry_parser: ::GeoParser)\n\n        transformation_command = thumb.transformation_command\n\n        assert transformation_command.include?('\"151x167\"'),\n          %{expected #{transformation_command.inspect} to include '151x167'}\n      end\n    end\n  end\n\n  context \"A multipage PDF\" do\n    before do\n      @file = File.new(fixture_file(\"twopage.pdf\"), 'rb')\n    end\n\n    after { @file.close }\n\n    it \"starts with two pages with dimensions 612x792\" do\n      cmd = %Q[identify -format \"%wx%h\" \"#{@file.path}\"]\n      assert_equal \"612x792\"*2, `#{cmd}`.chomp\n    end\n\n    context \"being thumbnailed at 100x100 with cropping\" do\n      before do\n        @thumb = Paperclip::Thumbnail.new(@file, geometry: \"100x100#\", format: :png)\n      end\n\n      it \"reports its correct current and target geometries\" do\n        assert_equal \"100x100#\", @thumb.target_geometry.to_s\n        assert_equal \"612x792\", @thumb.current_geometry.to_s\n      end\n\n      it \"reports its correct format\" do\n        assert_equal :png, @thumb.format\n      end\n\n      it \"creates the thumbnail when sent #make\" do\n        dst = @thumb.make\n        assert_match /100x100/, `identify \"#{dst.path}\"`\n      end\n    end\n  end\n\n  context \"An animated gif\" do\n    before do\n      @file = File.new(fixture_file(\"animated.gif\"), 'rb')\n    end\n\n    after { @file.close }\n\n    it \"starts with 12 frames with size 100x100\" do\n      cmd = %Q[identify -format \"%wx%h\" \"#{@file.path}\"]\n      assert_equal \"100x100\"*12, `#{cmd}`.chomp\n    end\n\n    context \"with static output\" do\n      before do\n       @thumb = Paperclip::Thumbnail.new(@file, geometry: \"50x50\", format: :jpg)\n      end\n\n      it \"creates the single frame thumbnail when sent #make\" do\n        dst = @thumb.make\n        cmd = %Q[identify -format \"%wx%h\" \"#{dst.path}\"]\n        assert_equal \"50x50\", `#{cmd}`.chomp\n      end\n    end\n\n    context \"with animated output format\" do\n      before do\n       @thumb = Paperclip::Thumbnail.new(@file, geometry: \"50x50\", format: :gif)\n      end\n\n      it \"creates the 12 frames thumbnail when sent #make\" do\n        dst = @thumb.make\n        cmd = %Q[identify -format \"%wx%h,\" \"#{dst.path}\"]\n        frames = `#{cmd}`.chomp.split(',')\n        assert_equal 12, frames.size\n        assert_frame_dimensions (45..50), frames\n      end\n\n      it \"uses the -coalesce option\" do\n        assert_equal @thumb.transformation_command.first, \"-coalesce\"\n      end\n\n      it \"uses the -layers 'optimize' option\" do\n        assert_equal @thumb.transformation_command.last, '-layers \"optimize\"'\n      end\n    end\n\n    context \"with omitted output format\" do\n      before do\n       @thumb = Paperclip::Thumbnail.new(@file, geometry: \"50x50\")\n      end\n\n      it \"creates the 12 frames thumbnail when sent #make\" do\n        dst = @thumb.make\n        cmd = %Q[identify -format \"%wx%h,\" \"#{dst.path}\"]\n        frames = `#{cmd}`.chomp.split(',')\n        assert_equal 12, frames.size\n        assert_frame_dimensions (45..50), frames\n      end\n\n      it \"uses the -coalesce option\" do\n        assert_equal @thumb.transformation_command.first, \"-coalesce\"\n      end\n\n      it \"uses the -layers 'optimize' option\" do\n        assert_equal @thumb.transformation_command.last, '-layers \"optimize\"'\n      end\n    end\n\n    context \"with unidentified source format\" do\n      before do\n        @unidentified_file = File.new(fixture_file(\"animated.unknown\"), 'rb')\n        @thumb = Paperclip::Thumbnail.new(@file, geometry: \"60x60\")\n      end\n\n      it \"creates the 12 frames thumbnail when sent #make\" do\n        dst = @thumb.make\n        cmd = %Q[identify -format \"%wx%h,\" \"#{dst.path}\"]\n        frames = `#{cmd}`.chomp.split(',')\n        assert_equal 12, frames.size\n        assert_frame_dimensions (55..60), frames\n      end\n\n      it \"uses the -coalesce option\" do\n        assert_equal @thumb.transformation_command.first, \"-coalesce\"\n      end\n\n      it \"uses the -layers 'optimize' option\" do\n        assert_equal @thumb.transformation_command.last, '-layers \"optimize\"'\n      end\n    end\n\n    context \"with no source format\" do\n      before do\n        @unidentified_file = File.new(fixture_file(\"animated\"), 'rb')\n        @thumb = Paperclip::Thumbnail.new(@file, geometry: \"70x70\")\n      end\n\n      it \"creates the 12 frames thumbnail when sent #make\" do\n        dst = @thumb.make\n        cmd = %Q[identify -format \"%wx%h,\" \"#{dst.path}\"]\n        frames = `#{cmd}`.chomp.split(',')\n        assert_equal 12, frames.size\n        assert_frame_dimensions (60..70), frames\n      end\n\n      it \"uses the -coalesce option\" do\n        assert_equal @thumb.transformation_command.first, \"-coalesce\"\n      end\n\n      it \"uses the -layers 'optimize' option\" do\n        assert_equal @thumb.transformation_command.last, '-layers \"optimize\"'\n      end\n    end\n\n    context \"with animated option set to false\" do\n      before do\n       @thumb = Paperclip::Thumbnail.new(@file, geometry: \"50x50\", animated: false)\n      end\n\n      it \"outputs the gif format\" do\n        dst = @thumb.make\n        cmd = %Q[identify \"#{dst.path}\"]\n        assert_match /GIF/, `#{cmd}`.chomp\n      end\n\n      it \"creates the single frame thumbnail when sent #make\" do\n        dst = @thumb.make\n        cmd = %Q[identify -format \"%wx%h\" \"#{dst.path}\"]\n        assert_equal \"50x50\", `#{cmd}`.chomp\n      end\n    end\n\n    context \"with a specified frame_index\" do\n      before do\n        @thumb = Paperclip::Thumbnail.new(\n          @file,\n          geometry: \"50x50\",\n          frame_index: 5,\n          format: :jpg,\n        )\n      end\n\n      it \"creates the thumbnail from the frame index when sent #make\" do\n        @thumb.make\n        assert_equal 5, @thumb.frame_index\n      end\n    end\n\n    context \"with a specified frame_index out of bounds\" do\n      before do\n        @thumb = Paperclip::Thumbnail.new(\n          @file,\n          geometry: \"50x50\",\n          frame_index: 20,\n          format: :jpg,\n        )\n      end\n\n      it \"errors when trying to create the thumbnail\" do\n        assert_raises(Paperclip::Error) do\n          silence_stream(STDERR) do\n            @thumb.make\n          end\n        end\n      end\n    end\n  end\n\n  context \"with a really long file name\" do\n    before do\n      tempfile = Tempfile.new(\"f\")\n      tempfile_additional_chars = tempfile.path.split(\"/\")[-1].length + 15\n      image_file = File.new(fixture_file(\"5k.png\"), \"rb\")\n      @file = Tempfile.new(\"f\" * (255 - tempfile_additional_chars))\n      @file.write(image_file.read)\n      @file.rewind\n    end\n\n    it \"does not throw Errno::ENAMETOOLONG\" do\n      thumb = Paperclip::Thumbnail.new(@file, geometry: \"50x50\", format: :gif)\n      expect { thumb.make }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/url_generator_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::UrlGenerator do\n  it \"uses the given interpolator\" do\n    expected = \"the expected result\"\n    mock_interpolator = MockInterpolator.new(result: expected)\n    mock_attachment = MockAttachment.new(interpolator: mock_interpolator)\n\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n    result = url_generator.for(:style_name, {})\n\n    assert_equal expected, result\n    assert mock_interpolator.has_interpolated_attachment?(mock_attachment)\n    assert mock_interpolator.has_interpolated_style_name?(:style_name)\n  end\n\n  it \"uses the default URL when no file is assigned\" do\n    mock_interpolator = MockInterpolator.new\n    default_url = \"the default url\"\n    options = { interpolator: mock_interpolator, default_url: default_url }\n    mock_attachment = MockAttachment.new(options)\n\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n    url_generator.for(:style_name, {})\n\n    assert mock_interpolator.has_interpolated_pattern?(default_url),\n      \"expected the interpolator to be passed #{default_url.inspect} but it wasn't\"\n  end\n\n  it \"executes the default URL lambda when no file is assigned\" do\n    mock_interpolator = MockInterpolator.new\n    default_url = lambda {|attachment| \"the #{attachment.class.name} default url\" }\n    options = { interpolator: mock_interpolator, default_url: default_url}\n    mock_attachment = MockAttachment.new(options)\n\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n    url_generator.for(:style_name, {})\n\n    assert mock_interpolator.has_interpolated_pattern?(\"the MockAttachment default url\"),\n      %{expected the interpolator to be passed \"the MockAttachment default url\", but it wasn't}\n  end\n\n  it \"executes the method named by the symbol as the default URL when no file is assigned\" do\n    mock_model = FakeModel.new\n    default_url = :to_s\n    mock_interpolator = MockInterpolator.new\n    options = {\n      interpolator: mock_interpolator,\n      default_url: default_url,\n      model: mock_model,\n    }\n    mock_attachment = MockAttachment.new(options)\n\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n    url_generator.for(:style_name, {})\n\n    assert mock_interpolator.has_interpolated_pattern?(mock_model.to_s),\n      %{expected the interpolator to be passed #{mock_model.to_s}, but it wasn't}\n  end\n\n  it \"URL-escapes spaces if asked to\" do\n    expected = \"the expected result\"\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = { interpolator: mock_interpolator }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {escape: true})\n\n    assert_equal \"the%20expected%20result\", result\n  end\n\n  it \"escapes the result of the interpolator using a method on the object, if asked to escape\" do\n    expected = Class.new do\n      def escape\n        \"the escaped result\"\n      end\n    end.new\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = { interpolator: mock_interpolator }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {escape: true})\n\n    assert_equal \"the escaped result\", result\n  end\n\n  it \"leaves spaces unescaped as asked to\" do\n    expected = \"the expected result\"\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = { interpolator: mock_interpolator }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {escape: false})\n\n    assert_equal \"the expected result\", result\n  end\n\n  it \"defaults to leaving spaces unescaped\" do\n    expected = \"the expected result\"\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = { interpolator: mock_interpolator }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {})\n\n    assert_equal \"the expected result\", result\n  end\n\n  it \"produces URLs without the updated_at value when the object does not respond to updated_at\" do\n    expected = \"the expected result\"\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = { interpolator: mock_interpolator, responds_to_updated_at: false }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {timestamp: true})\n\n    assert_equal expected, result\n  end\n\n  it \"produces URLs without the updated_at value when the updated_at value is nil\" do\n    expected = \"the expected result\"\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = {\n      responds_to_updated_at: true,\n      updated_at: nil,\n      interpolator: mock_interpolator,\n    }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {timestamp: true})\n\n    assert_equal expected, result\n  end\n\n  it \"produces URLs with the updated_at when it exists\" do\n    expected = \"the expected result\"\n    updated_at = 1231231234\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = { interpolator: mock_interpolator, updated_at: updated_at }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {timestamp: true})\n\n    assert_equal \"#{expected}?#{updated_at}\", result\n  end\n\n  it \"produces URLs with the updated_at when it exists, separated with a & if a ? follow by = already exists\" do\n    expected = \"the?expected=result\"\n    updated_at = 1231231234\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = { interpolator: mock_interpolator, updated_at: updated_at }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {timestamp: true})\n\n    assert_equal \"#{expected}&#{updated_at}\", result\n  end\n\n  it \"produces URLs without the updated_at when told to do as much\" do\n    expected = \"the expected result\"\n    updated_at = 1231231234\n    mock_interpolator = MockInterpolator.new(result: expected)\n    options = { interpolator: mock_interpolator, updated_at: updated_at }\n    mock_attachment = MockAttachment.new(options)\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n\n    result = url_generator.for(:style_name, {timestamp: false})\n\n    assert_equal expected, result\n  end\n\n  it \"produces the correct URL when the instance has a file name\" do\n    expected = \"the expected result\"\n    mock_interpolator = MockInterpolator.new\n    options = {\n      interpolator: mock_interpolator,\n      url: expected,\n      original_filename: \"exists\",\n    }\n    mock_attachment = MockAttachment.new(options)\n\n    url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n    url_generator.for(:style_name, {})\n\n    assert mock_interpolator.has_interpolated_pattern?(expected),\n      \"expected the interpolator to be passed #{expected.inspect} but it wasn't\"\n  end\n\n  describe \"should be able to escape (, ), [, and ].\" do\n    def generate(expected, updated_at=nil)\n      mock_interpolator = MockInterpolator.new(result: expected)\n      options = { interpolator: mock_interpolator, updated_at: updated_at }\n      mock_attachment = MockAttachment.new(options)\n      url_generator = Paperclip::UrlGenerator.new(mock_attachment)\n      def url_generator.respond_to(params)\n        false if params == :escape\n      end\n      url_generator.for(:style_name, {escape: true, timestamp: !!updated_at})\n    end\n\n    it \"not timestamp\" do\n      expected = \"the(expected)result[]\"\n      assert_equal \"the%28expected%29result%5B%5D\", generate(expected)\n    end\n\n    it \"timestamp\" do\n      expected = \"the(expected)result[]\"\n      updated_at = 1231231234\n      assert_equal \"the%28expected%29result%5B%5D?#{updated_at}\",\n        generate(expected, updated_at)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/validators/attachment_content_type_validator_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Validators::AttachmentContentTypeValidator do\n  before do\n    rebuild_model\n    @dummy = Dummy.new\n  end\n\n  def build_validator(options)\n    @validator = Paperclip::Validators::AttachmentContentTypeValidator.new(options.merge(\n      attributes: :avatar\n    ))\n  end\n\n  context \"with a nil content type\" do\n    before do\n      build_validator content_type: \"image/jpg\"\n      @dummy.stubs(avatar_content_type: nil)\n      @validator.validate(@dummy)\n    end\n\n    it \"does not set an error message\" do\n      assert @dummy.errors[:avatar_content_type].blank?\n    end\n  end\n\n  context \"with :allow_nil option\" do\n    context \"as true\" do\n      before do\n        build_validator content_type: \"image/png\", allow_nil: true\n        @dummy.stubs(avatar_content_type: nil)\n        @validator.validate(@dummy)\n      end\n\n      it \"allows avatar_content_type as nil\" do\n        assert @dummy.errors[:avatar_content_type].blank?\n      end\n    end\n\n    context \"as false\" do\n      before do\n        build_validator content_type: \"image/png\", allow_nil: false\n        @dummy.stubs(avatar_content_type: nil)\n        @validator.validate(@dummy)\n      end\n\n      it \"does not allow avatar_content_type as nil\" do\n        assert @dummy.errors[:avatar_content_type].present?\n      end\n    end\n  end\n\n  context \"with a failing validation\" do\n    before do\n      build_validator content_type: \"image/png\", allow_nil: false\n      @dummy.stubs(avatar_content_type: nil)\n      @validator.validate(@dummy)\n    end\n\n    it \"adds error to the base object\" do\n      assert @dummy.errors[:avatar].present?,\n        \"Error not added to base attribute\"\n    end\n\n    it \"adds error to base object as a string\" do\n      expect(@dummy.errors[:avatar].first).to be_a String\n    end\n  end\n\n  context \"with a successful validation\" do\n    before do\n      build_validator content_type: \"image/png\", allow_nil: false\n      @dummy.stubs(avatar_content_type: \"image/png\")\n      @validator.validate(@dummy)\n    end\n\n    it \"does not add error to the base object\" do\n      assert @dummy.errors[:avatar].blank?,\n        \"Error was added to base attribute\"\n    end\n  end\n\n  context \"with :allow_blank option\" do\n    context \"as true\" do\n      before do\n        build_validator content_type: \"image/png\", allow_blank: true\n        @dummy.stubs(avatar_content_type: \"\")\n        @validator.validate(@dummy)\n      end\n\n      it \"allows avatar_content_type as blank\" do\n        assert @dummy.errors[:avatar_content_type].blank?\n      end\n    end\n\n    context \"as false\" do\n      before do\n        build_validator content_type: \"image/png\", allow_blank: false\n        @dummy.stubs(avatar_content_type: \"\")\n        @validator.validate(@dummy)\n      end\n\n      it \"does not allow avatar_content_type as blank\" do\n        assert @dummy.errors[:avatar_content_type].present?\n      end\n    end\n  end\n\n  context \"whitelist format\" do\n    context \"with an allowed type\" do\n      context \"as a string\" do\n        before do\n          build_validator content_type: \"image/jpg\"\n          @dummy.stubs(avatar_content_type: \"image/jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_content_type].blank?\n        end\n      end\n\n      context \"as an regexp\" do\n        before do\n          build_validator content_type: /^image\\/.*/\n          @dummy.stubs(avatar_content_type: \"image/jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_content_type].blank?\n        end\n      end\n\n      context \"as a list\" do\n        before do\n          build_validator content_type: [\"image/png\", \"image/jpg\", \"image/jpeg\"]\n          @dummy.stubs(avatar_content_type: \"image/jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_content_type].blank?\n        end\n      end\n    end\n\n    context \"with a disallowed type\" do\n      context \"as a string\" do\n        before do\n          build_validator content_type: \"image/png\"\n          @dummy.stubs(avatar_content_type: \"image/jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"sets a correct default error message\" do\n          assert @dummy.errors[:avatar_content_type].present?\n          expect(@dummy.errors[:avatar_content_type]).to include \"is invalid\"\n        end\n      end\n\n      context \"as a regexp\" do\n        before do\n          build_validator content_type: /^text\\/.*/\n          @dummy.stubs(avatar_content_type: \"image/jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"sets a correct default error message\" do\n          assert @dummy.errors[:avatar_content_type].present?\n          expect(@dummy.errors[:avatar_content_type]).to include \"is invalid\"\n        end\n      end\n\n      context \"with :message option\" do\n        context \"without interpolation\" do\n          before do\n            build_validator content_type: \"image/png\", message: \"should be a PNG image\"\n            @dummy.stubs(avatar_content_type: \"image/jpg\")\n            @validator.validate(@dummy)\n          end\n\n          it \"sets a correct error message\" do\n            expect(@dummy.errors[:avatar_content_type]).to include \"should be a PNG image\"\n          end\n        end\n\n        context \"with interpolation\" do\n          before do\n            build_validator content_type: \"image/png\", message: \"should have content type %{types}\"\n            @dummy.stubs(avatar_content_type: \"image/jpg\")\n            @validator.validate(@dummy)\n          end\n\n          it \"sets a correct error message\" do\n            expect(@dummy.errors[:avatar_content_type]).to include \"should have content type image/png\"\n          end\n        end\n      end\n    end\n  end\n\n  context \"blacklist format\" do\n    context \"with an allowed type\" do\n      context \"as a string\" do\n        before do\n          build_validator not: \"image/gif\"\n          @dummy.stubs(avatar_content_type: \"image/jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_content_type].blank?\n        end\n      end\n\n      context \"as an regexp\" do\n        before do\n          build_validator not: /^text\\/.*/\n          @dummy.stubs(avatar_content_type: \"image/jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_content_type].blank?\n        end\n      end\n\n      context \"as a list\" do\n        before do\n          build_validator not: [\"image/png\", \"image/jpg\", \"image/jpeg\"]\n          @dummy.stubs(avatar_content_type: \"image/gif\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_content_type].blank?\n        end\n      end\n    end\n\n    context \"with a disallowed type\" do\n      context \"as a string\" do\n        before do\n          build_validator not: \"image/png\"\n          @dummy.stubs(avatar_content_type: \"image/png\")\n          @validator.validate(@dummy)\n        end\n\n        it \"sets a correct default error message\" do\n          assert @dummy.errors[:avatar_content_type].present?\n          expect(@dummy.errors[:avatar_content_type]).to include \"is invalid\"\n        end\n      end\n\n      context \"as a regexp\" do\n        before do\n          build_validator not: /^text\\/.*/\n          @dummy.stubs(avatar_content_type: \"text/plain\")\n          @validator.validate(@dummy)\n        end\n\n        it \"sets a correct default error message\" do\n          assert @dummy.errors[:avatar_content_type].present?\n          expect(@dummy.errors[:avatar_content_type]).to include \"is invalid\"\n        end\n      end\n\n      context \"with :message option\" do\n        context \"without interpolation\" do\n          before do\n            build_validator not: \"image/png\", message: \"should not be a PNG image\"\n            @dummy.stubs(avatar_content_type: \"image/png\")\n            @validator.validate(@dummy)\n          end\n\n          it \"sets a correct error message\" do\n            expect(@dummy.errors[:avatar_content_type]).to include \"should not be a PNG image\"\n          end\n        end\n\n        context \"with interpolation\" do\n          before do\n            build_validator not: \"image/png\", message: \"should not have content type %{types}\"\n            @dummy.stubs(avatar_content_type: \"image/png\")\n            @validator.validate(@dummy)\n          end\n\n          it \"sets a correct error message\" do\n            expect(@dummy.errors[:avatar_content_type]).to include \"should not have content type image/png\"\n          end\n        end\n      end\n    end\n  end\n\n  context \"using the helper\" do\n    before do\n      Dummy.validates_attachment_content_type :avatar, content_type: \"image/jpg\"\n    end\n\n    it \"adds the validator to the class\" do\n      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_content_type }\n    end\n  end\n\n  context \"given options\" do\n    it \"raises argument error if no required argument was given\" do\n      assert_raises(ArgumentError) do\n        build_validator message: \"Some message\"\n      end\n    end\n\n    it \"does not raise argument error if :content_type was given\" do\n      build_validator content_type: \"image/jpg\"\n    end\n\n    it \"does not raise argument error if :not was given\" do\n      build_validator not: \"image/jpg\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/validators/attachment_file_name_validator_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Validators::AttachmentFileNameValidator do\n  before do\n    rebuild_model\n    @dummy = Dummy.new\n  end\n\n  def build_validator(options)\n    @validator = Paperclip::Validators::AttachmentFileNameValidator.new(options.merge(\n      attributes: :avatar\n    ))\n  end\n\n  context \"with a failing validation\" do\n    before do\n      build_validator matches: /.*\\.png$/, allow_nil: false\n      @dummy.stubs(avatar_file_name: \"data.txt\")\n      @validator.validate(@dummy)\n    end\n\n    it \"adds error to the base object\" do\n      assert @dummy.errors[:avatar].present?,\n        \"Error not added to base attribute\"\n    end\n\n    it \"adds error to base object as a string\" do\n      expect(@dummy.errors[:avatar].first).to be_a String\n    end\n  end\n\n  it \"does not add error to the base object with a successful validation\" do\n    build_validator matches: /.*\\.png$/, allow_nil: false\n    @dummy.stubs(avatar_file_name: \"image.png\")\n    @validator.validate(@dummy)\n\n    assert @dummy.errors[:avatar].blank?, \"Error was added to base attribute\"\n  end\n\n  context \"whitelist format\" do\n    context \"with an allowed type\" do\n      context \"as a single regexp\" do\n        before do\n          build_validator matches: /.*\\.jpg$/\n          @dummy.stubs(avatar_file_name: \"image.jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_file_name].blank?\n        end\n      end\n\n      context \"as a list\" do\n        before do\n          build_validator matches: [/.*\\.png$/, /.*\\.jpe?g$/]\n          @dummy.stubs(avatar_file_name: \"image.jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_file_name].blank?\n        end\n      end\n    end\n\n    context \"with a disallowed type\" do\n      it \"sets a correct default error message\" do\n        build_validator matches: /^text\\/.*/\n        @dummy.stubs(avatar_file_name: \"image.jpg\")\n        @validator.validate(@dummy)\n\n        assert @dummy.errors[:avatar_file_name].present?\n        expect(@dummy.errors[:avatar_file_name]).to include \"is invalid\"\n      end\n\n      it \"sets a correct custom error message\" do\n        build_validator matches: /.*\\.png$/, message: \"should be a PNG image\"\n        @dummy.stubs(avatar_file_name: \"image.jpg\")\n        @validator.validate(@dummy)\n\n        expect(@dummy.errors[:avatar_file_name]).to include \"should be a PNG image\"\n      end\n    end\n  end\n\n  context \"blacklist format\" do\n    context \"with an allowed type\" do\n      context \"as a single regexp\" do\n        before do\n          build_validator not: /^text\\/.*/\n          @dummy.stubs(avatar_file_name: \"image.jpg\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_file_name].blank?\n        end\n      end\n\n      context \"as a list\" do\n        before do\n          build_validator not: [/.*\\.png$/, /.*\\.jpe?g$/]\n          @dummy.stubs(avatar_file_name: \"image.gif\")\n          @validator.validate(@dummy)\n        end\n\n        it \"does not set an error message\" do\n          assert @dummy.errors[:avatar_file_name].blank?\n        end\n      end\n    end\n\n    context \"with a disallowed type\" do\n      it \"sets a correct default error message\" do\n        build_validator not: /data.*/\n        @dummy.stubs(avatar_file_name: \"data.txt\")\n        @validator.validate(@dummy)\n\n        assert @dummy.errors[:avatar_file_name].present?\n        expect(@dummy.errors[:avatar_file_name]).to include \"is invalid\"\n      end\n\n      it \"sets a correct custom error message\" do\n        build_validator not: /.*\\.png$/, message: \"should not be a PNG image\"\n        @dummy.stubs(avatar_file_name: \"image.png\")\n        @validator.validate(@dummy)\n\n        expect(@dummy.errors[:avatar_file_name]).to include \"should not be a PNG image\"\n      end\n    end\n  end\n\n  context \"using the helper\" do\n    before do\n      Dummy.validates_attachment_file_name :avatar, matches: /.*\\.jpg$/\n    end\n\n    it \"adds the validator to the class\" do\n      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_file_name }\n    end\n  end\n\n  context \"given options\" do\n    it \"raises argument error if no required argument was given\" do\n      assert_raises(ArgumentError) do\n        build_validator message: \"Some message\"\n      end\n    end\n\n    it \"does not raise argument error if :matches was given\" do\n      build_validator matches: /.*\\.jpg$/\n    end\n\n    it \"does not raise argument error if :not was given\" do\n      build_validator not: /.*\\.jpg$/\n    end\n  end\nend\n\n"
  },
  {
    "path": "spec/paperclip/validators/attachment_presence_validator_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Validators::AttachmentPresenceValidator do\n  before do\n    rebuild_model\n    @dummy = Dummy.new\n  end\n\n  def build_validator(options={})\n    @validator = Paperclip::Validators::AttachmentPresenceValidator.new(options.merge(\n      attributes: :avatar\n    ))\n  end\n\n  context \"nil attachment\" do\n    before do\n      @dummy.avatar = nil\n    end\n\n    context \"with default options\" do\n      before do\n        build_validator\n        @validator.validate(@dummy)\n      end\n\n      it \"adds error on the attachment\" do\n        assert @dummy.errors[:avatar].present?\n      end\n\n      it \"does not add an error on the file_name attribute\" do\n        assert @dummy.errors[:avatar_file_name].blank?\n      end\n    end\n\n    context \"with :if option\" do\n      context \"returning true\" do\n        before do\n          build_validator if: true\n          @validator.validate(@dummy)\n        end\n\n        it \"performs a validation\" do\n          assert @dummy.errors[:avatar].present?\n        end\n      end\n\n      context \"returning false\" do\n        before do\n          build_validator if: false\n          @validator.validate(@dummy)\n        end\n\n        it \"performs a validation\" do\n          assert @dummy.errors[:avatar].present?\n        end\n      end\n    end\n  end\n\n  context \"with attachment\" do\n    before do\n      build_validator\n      @dummy.avatar = StringIO.new('.\\n')\n      @validator.validate(@dummy)\n    end\n\n    it \"does not add error on the attachment\" do\n      assert @dummy.errors[:avatar].blank?\n    end\n\n    it \"does not add an error on the file_name attribute\" do\n      assert @dummy.errors[:avatar_file_name].blank?\n    end\n  end\n\n  context \"using the helper\" do\n    before do\n      Dummy.validates_attachment_presence :avatar\n    end\n\n    it \"adds the validator to the class\" do\n      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_presence }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/validators/attachment_size_validator_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Validators::AttachmentSizeValidator do\n  before do\n    rebuild_model\n    @dummy = Dummy.new\n  end\n\n  def build_validator(options)\n    @validator = Paperclip::Validators::AttachmentSizeValidator.new(options.merge(\n      attributes: :avatar\n    ))\n  end\n\n  def self.should_allow_attachment_file_size(size)\n    context \"when the attachment size is #{size}\" do\n      it \"adds error to dummy object\" do\n        @dummy.stubs(:avatar_file_size).returns(size)\n        @validator.validate(@dummy)\n        assert @dummy.errors[:avatar_file_size].blank?,\n          \"Expect an error message on :avatar_file_size, got none.\"\n      end\n\n      it \"does not add error to the base dummy object\" do\n        assert @dummy.errors[:avatar].blank?,\n          \"Error added to base attribute\"\n      end\n    end\n  end\n\n  def self.should_not_allow_attachment_file_size(size, options = {})\n    context \"when the attachment size is #{size}\" do\n      before do\n        @dummy.stubs(:avatar_file_size).returns(size)\n        @validator.validate(@dummy)\n      end\n\n      it \"adds error to dummy object\" do\n        assert @dummy.errors[:avatar_file_size].present?,\n          \"Unexpected error message on :avatar_file_size\"\n      end\n\n      it \"adds error to the base dummy object\" do\n        assert @dummy.errors[:avatar].present?,\n          \"Error not added to base attribute\"\n      end\n\n      it \"adds error to base object as a string\" do\n        expect(@dummy.errors[:avatar].first).to be_a String\n      end\n\n      if options[:message]\n        it \"returns a correct error message\" do\n          expect(@dummy.errors[:avatar_file_size]).to include options[:message]\n        end\n      end\n    end\n  end\n\n  context \"with :in option\" do\n    context \"as a range\" do\n      before do\n        build_validator in: (5.kilobytes..10.kilobytes)\n      end\n\n      should_allow_attachment_file_size(7.kilobytes)\n      should_not_allow_attachment_file_size(4.kilobytes)\n      should_not_allow_attachment_file_size(11.kilobytes)\n    end\n\n    context \"as a proc\" do\n      before do\n        build_validator in: lambda { |avatar| (5.kilobytes..10.kilobytes) }\n      end\n\n      should_allow_attachment_file_size(7.kilobytes)\n      should_not_allow_attachment_file_size(4.kilobytes)\n      should_not_allow_attachment_file_size(11.kilobytes)\n    end\n  end\n\n  context \"with :greater_than option\" do\n    context \"as number\" do\n      before do\n        build_validator greater_than: 10.kilobytes\n      end\n\n      should_allow_attachment_file_size 11.kilobytes\n      should_not_allow_attachment_file_size 10.kilobytes\n    end\n\n    context \"as a proc\" do\n      before do\n        build_validator greater_than: lambda { |avatar| 10.kilobytes }\n      end\n\n      should_allow_attachment_file_size 11.kilobytes\n      should_not_allow_attachment_file_size 10.kilobytes\n    end\n  end\n\n  context \"with :less_than option\" do\n    context \"as number\" do\n      before do\n        build_validator less_than: 10.kilobytes\n      end\n\n      should_allow_attachment_file_size 9.kilobytes\n      should_not_allow_attachment_file_size 10.kilobytes\n    end\n\n    context \"as a proc\" do\n      before do\n        build_validator less_than: lambda { |avatar| 10.kilobytes }\n      end\n\n      should_allow_attachment_file_size 9.kilobytes\n      should_not_allow_attachment_file_size 10.kilobytes\n    end\n  end\n\n  context \"with :greater_than and :less_than option\" do\n    context \"as numbers\" do\n      before do\n        build_validator greater_than: 5.kilobytes,\n          less_than: 10.kilobytes\n      end\n\n      should_allow_attachment_file_size 7.kilobytes\n      should_not_allow_attachment_file_size 5.kilobytes\n      should_not_allow_attachment_file_size 10.kilobytes\n    end\n\n    context \"as a proc\" do\n      before do\n        build_validator greater_than: lambda { |avatar| 5.kilobytes },\n          less_than: lambda { |avatar| 10.kilobytes }\n      end\n\n      should_allow_attachment_file_size 7.kilobytes\n      should_not_allow_attachment_file_size 5.kilobytes\n      should_not_allow_attachment_file_size 10.kilobytes\n    end\n  end\n\n  context \"with :message option\" do\n    context \"given a range\" do\n      before do\n        build_validator in: (5.kilobytes..10.kilobytes),\n          message: \"is invalid. (Between %{min} and %{max} please.)\"\n      end\n\n      should_not_allow_attachment_file_size(\n        11.kilobytes,\n        message: \"is invalid. (Between 5 KB and 10 KB please.)\"\n      )\n    end\n\n    context \"given :less_than and :greater_than\" do\n      before do\n        build_validator less_than: 10.kilobytes,\n          greater_than: 5.kilobytes,\n          message: \"is invalid. (Between %{min} and %{max} please.)\"\n      end\n\n      should_not_allow_attachment_file_size(\n        11.kilobytes,\n        message: \"is invalid. (Between 5 KB and 10 KB please.)\"\n      )\n    end\n  end\n\n  context \"default error messages\" do\n    context \"given :less_than and :greater_than\" do\n      before do\n        build_validator greater_than: 5.kilobytes,\n          less_than: 10.kilobytes\n      end\n\n      should_not_allow_attachment_file_size(\n        11.kilobytes,\n        message: \"must be less than 10 KB\"\n      )\n\n      should_not_allow_attachment_file_size(\n        4.kilobytes,\n        message: \"must be greater than 5 KB\"\n      )\n    end\n\n    context \"given a size range\" do\n      before do\n        build_validator in: (5.kilobytes..10.kilobytes)\n      end\n\n      should_not_allow_attachment_file_size(\n        11.kilobytes,\n        message: \"must be in between 5 KB and 10 KB\"\n      )\n\n      should_not_allow_attachment_file_size(\n        4.kilobytes,\n        message: \"must be in between 5 KB and 10 KB\"\n      )\n    end\n  end\n\n  context \"using the helper\" do\n    before do\n      Dummy.validates_attachment_size :avatar, in: (5.kilobytes..10.kilobytes)\n    end\n\n    it \"adds the validator to the class\" do\n      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_size }\n    end\n  end\n\n  context \"given options\" do\n    it \"raises argument error if no required argument was given\" do\n      assert_raises(ArgumentError) do\n        build_validator message: \"Some message\"\n      end\n    end\n\n    (Paperclip::Validators::AttachmentSizeValidator::AVAILABLE_CHECKS).each do |argument|\n      it \"does not raise arguemnt error if #{argument} was given\" do\n        build_validator argument => 5.kilobytes\n      end\n    end\n\n    it \"does not raise argument error if :in was given\" do\n      build_validator in: (5.kilobytes..10.kilobytes)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Validators::MediaTypeSpoofDetectionValidator do\n  before do\n    rebuild_model\n    @dummy = Dummy.new\n  end\n\n  def build_validator(options = {})\n    @validator = Paperclip::Validators::MediaTypeSpoofDetectionValidator.new(options.merge(\n      attributes: :avatar\n    ))\n  end\n\n  it \"is on the attachment without being explicitly added\" do\n    assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :media_type_spoof_detection }\n  end\n\n  it \"is not on the attachment when explicitly rejected\" do\n    rebuild_model validate_media_type: false\n    assert Dummy.validators_on(:avatar).none?{ |validator| validator.kind == :media_type_spoof_detection }\n  end\n\n  it \"returns default error message for spoofed media type\" do\n    build_validator\n    file = File.new(fixture_file(\"5k.png\"), \"rb\")\n    @dummy.avatar.assign(file)\n\n    detector = mock(\"detector\", :spoofed? => true)\n    Paperclip::MediaTypeSpoofDetector.stubs(:using).returns(detector)\n    @validator.validate(@dummy)\n\n    assert_equal I18n.t(\"errors.messages.spoofed_media_type\"), @dummy.errors[:avatar].first\n  end\n\n  it \"runs when attachment is dirty\" do\n    build_validator\n    file = File.new(fixture_file(\"5k.png\"), \"rb\")\n    @dummy.avatar.assign(file)\n    Paperclip::MediaTypeSpoofDetector.stubs(:using).returns(stub(:spoofed? => false))\n\n    @dummy.valid?\n\n    assert_received(Paperclip::MediaTypeSpoofDetector, :using){|e| e.once }\n  end\n\n  it \"does not run when attachment is not dirty\" do\n    Paperclip::MediaTypeSpoofDetector.stubs(:using).never\n    @dummy.valid?\n    assert_received(Paperclip::MediaTypeSpoofDetector, :using){|e| e.never }\n  end\nend\n"
  },
  {
    "path": "spec/paperclip/validators_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Paperclip::Validators do\n  context \"using the helper\" do\n    before do\n      rebuild_class\n      Dummy.validates_attachment :avatar, presence: true, content_type: { content_type: \"image/jpeg\" }, size: { in: 0..10240 }\n    end\n\n    it \"adds the attachment_presence validator to the class\" do\n      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_presence }\n    end\n\n    it \"adds the attachment_content_type validator to the class\" do\n      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_content_type }\n    end\n\n    it \"adds the attachment_size validator to the class\" do\n      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_size }\n    end\n\n    it 'prevents you from attaching a file that violates that validation' do\n      Dummy.class_eval{ validate(:name) { raise \"DO NOT RUN THIS\" } }\n      dummy = Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      expect(dummy.errors.keys).to match_array [:avatar_content_type, :avatar, :avatar_file_size]\n      assert_raises(RuntimeError){ dummy.valid? }\n    end\n  end\n\n  context 'using the helper with array of validations' do\n    before do\n      rebuild_class\n      Dummy.validates_attachment :avatar, file_type_ignorance: true, file_name: [\n          { matches: /\\A.*\\.jpe?g\\z/i, message: :invalid_extension },\n          { matches: /\\A.{,8}\\..+\\z/i, message: [:too_long, count: 8] },\n      ]\n    end\n\n    it 'adds the attachment_file_name validator to the class' do\n      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_file_name }\n    end\n\n    it 'adds the attachment_file_name validator with two validations' do\n      assert_equal 2, Dummy.validators_on(:avatar).select{ |validator| validator.kind == :attachment_file_name }.size\n    end\n\n    it 'prevents you from attaching a file that violates all of these validations' do\n      Dummy.class_eval{ validate(:name) { raise 'DO NOT RUN THIS' } }\n      dummy = Dummy.new(avatar: File.new(fixture_file('spaced file.png')))\n      expect(dummy.errors.keys).to match_array [:avatar, :avatar_file_name]\n      assert_raises(RuntimeError){ dummy.valid? }\n    end\n\n    it 'prevents you from attaching a file that violates only first of these validations' do\n      Dummy.class_eval{ validate(:name) { raise 'DO NOT RUN THIS' } }\n      dummy = Dummy.new(avatar: File.new(fixture_file('5k.png')))\n      expect(dummy.errors.keys).to match_array [:avatar, :avatar_file_name]\n      assert_raises(RuntimeError){ dummy.valid? }\n    end\n\n    it 'prevents you from attaching a file that violates only second of these validations' do\n      Dummy.class_eval{ validate(:name) { raise 'DO NOT RUN THIS' } }\n      dummy = Dummy.new(avatar: File.new(fixture_file('spaced file.jpg')))\n      expect(dummy.errors.keys).to match_array [:avatar, :avatar_file_name]\n      assert_raises(RuntimeError){ dummy.valid? }\n    end\n\n    it 'allows you to attach a file that does not violate these validations' do\n      dummy = Dummy.new(avatar: File.new(fixture_file('rotated.jpg')))\n      expect(dummy.errors.full_messages).to be_empty\n      assert dummy.valid?\n    end\n  end\n\n  context \"using the helper with a conditional\" do\n    before do\n      rebuild_class\n      Dummy.validates_attachment :avatar, presence: true,\n        content_type: { content_type: \"image/jpeg\" },\n        size: { in: 0..10240 },\n        if: :title_present?\n    end\n\n    it \"validates the attachment if title is present\" do\n      Dummy.class_eval do\n        def title_present?\n          true\n        end\n      end\n      dummy = Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      expect(dummy.errors.keys).to match_array [:avatar_content_type, :avatar, :avatar_file_size]\n    end\n\n    it \"does not validate attachment if title is not present\" do\n      Dummy.class_eval do\n        def title_present?\n          false\n        end\n      end\n      dummy = Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      assert_equal [], dummy.errors.keys\n    end\n  end\n\n  context 'with no other validations on the Dummy#avatar attachment' do\n    before do\n      reset_class(\"Dummy\")\n      Dummy.has_attached_file :avatar\n      Paperclip.reset_duplicate_clash_check!\n    end\n\n    it 'raises an error when no content_type validation exists' do\n      assert_raises(Paperclip::Errors::MissingRequiredValidatorError) do\n        Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      end\n    end\n\n    it 'does not raise an error when a content_type validation exists' do\n      Dummy.validates_attachment :avatar, content_type: { content_type: \"image/jpeg\" }\n\n      assert_nothing_raised do\n        Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      end\n    end\n\n    it 'does not raise an error when a content_type validation exists using validates_with' do\n      Dummy.validates_with Paperclip::Validators::AttachmentContentTypeValidator, attributes: :attachment, content_type: 'images/jpeg'\n\n      assert_nothing_raised do\n        Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      end\n    end\n\n    it 'does not raise an error when an inherited validator is used' do\n      class MyValidator < Paperclip::Validators::AttachmentContentTypeValidator\n        def initialize(options)\n          options[:content_type] = \"images/jpeg\" unless options.has_key?(:content_type)\n          super\n        end\n      end\n      Dummy.validates_with MyValidator, attributes: :attachment\n\n      assert_nothing_raised do\n        Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      end\n    end\n\n    it 'does not raise an error when a file_name validation exists' do\n      Dummy.validates_attachment :avatar, file_name: { matches: /png$/ }\n\n      assert_nothing_raised do\n        Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      end\n    end\n\n    it 'does not raise an error when a the validation has been explicitly rejected' do\n      Dummy.validates_attachment :avatar, file_type_ignorance: true\n\n      assert_nothing_raised do\n        Dummy.new(avatar: File.new(fixture_file(\"12k.png\")))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "require 'rubygems'\nrequire 'rspec'\nrequire 'active_record'\nrequire 'active_record/version'\nrequire 'active_support'\nrequire 'active_support/core_ext'\nrequire 'mocha/api'\nrequire 'bourne'\nrequire 'ostruct'\nrequire 'pathname'\nrequire 'activerecord-import'\n\nROOT = Pathname(File.expand_path(File.join(File.dirname(__FILE__), '..')))\n\nputs \"Testing against version #{ActiveRecord::VERSION::STRING}\"\n\n$LOAD_PATH << File.join(ROOT, 'lib')\n$LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')\nrequire File.join(ROOT, 'lib', 'paperclip.rb')\n\nFIXTURES_DIR = File.join(File.dirname(__FILE__), \"fixtures\")\nconfig = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))\nActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + \"/debug.log\")\nActiveRecord::Base.establish_connection(config['test'])\nif ActiveRecord::VERSION::STRING >= \"4.2\" &&\n    ActiveRecord::VERSION::STRING < \"5.0\"\n  ActiveRecord::Base.raise_in_transactional_callbacks = true\nend\nPaperclip.options[:logger] = ActiveRecord::Base.logger\n\nDir[File.join(ROOT, 'spec', 'support', '**', '*.rb')].each{|f| require f }\n\nRails = FakeRails.new('test', Pathname.new(ROOT).join('tmp'))\nActiveSupport::Deprecation.silenced = true\n\nRSpec.configure do |config|\n  config.include Assertions\n  config.include ModelReconstruction\n  config.include TestData\n  config.include Reporting\n  config.extend VersionHelper\n  config.mock_framework = :mocha\n  config.before(:all) do\n    rebuild_model\n  end\nend\n"
  },
  {
    "path": "spec/support/assertions.rb",
    "content": "module Assertions\n  def assert(truthy, message = nil)\n    expect(!!truthy).to(eq(true), message)\n  end\n\n  def assert_equal(expected, actual, message = nil)\n    expect(actual).to(eq(expected), message)\n  end\n\n  def assert_not_equal(expected, actual, message = nil)\n    expect(actual).to_not(eq(expected), message)\n  end\n\n  def assert_raises(exception_class, message = nil, &block)\n    expect(&block).to raise_error(exception_class, message)\n  end\n\n  def assert_nothing_raised(&block)\n    expect(&block).to_not raise_error\n  end\n\n  def assert_nil(thing)\n    expect(thing).to be_nil\n  end\n\n  def assert_contains(haystack, needle)\n    expect(haystack).to include(needle)\n  end\n\n  def assert_match(pattern, value)\n    expect(value).to match(pattern)\n  end\n\n  def assert_no_match(pattern, value)\n    expect(value).to_not match(pattern)\n  end\n\n  def assert_file_exists(path_to_file)\n    expect(path_to_file).to exist\n  end\n\n  def assert_file_not_exists(path_to_file)\n    expect(path_to_file).to_not exist\n  end\n\n  def assert_empty(object)\n    expect(object).to be_empty\n  end\n\n  def assert_success_response(url)\n    url = \"http:#{url}\" unless url =~ /http/\n    Net::HTTP.get_response(URI.parse(url)) do |response|\n      assert_equal \"200\",\n        response.code,\n        \"Expected HTTP response code 200, got #{response.code}\"\n    end\n  end\n\n  def assert_not_found_response(url)\n    url = \"http:#{url}\" unless url =~ /http/\n    Net::HTTP.get_response(URI.parse(url)) do |response|\n      assert_equal \"404\", response.code,\n        \"Expected HTTP response code 404, got #{response.code}\"\n    end\n  end\n\n  def assert_forbidden_response(url)\n    url = \"http:#{url}\" unless url =~ /http/\n    Net::HTTP.get_response(URI.parse(url)) do |response|\n      assert_equal \"403\", response.code,\n        \"Expected HTTP response code 403, got #{response.code}\"\n    end\n  end\n\n  def assert_frame_dimensions(range, frames)\n    frames.each_with_index do |frame, frame_index|\n      frame.split('x').each_with_index do |dimension, dimension_index |\n        assert range.include?(dimension.to_i), \"Frame #{frame_index}[#{dimension_index}] should have been within #{range.inspect}, but was #{dimension}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fake_model.rb",
    "content": "class FakeModel\n  attr_accessor(\n    :avatar_file_name,\n    :avatar_file_size,\n    :avatar_updated_at,\n    :avatar_content_type,\n    :avatar_fingerprint,\n    :id\n  )\n\n  def errors\n    @errors ||= []\n  end\n\n  def run_paperclip_callbacks name, *args\n  end\n\n  def valid?\n    errors.empty?\n  end\n\n  def new_record?\n    false\n  end\nend\n"
  },
  {
    "path": "spec/support/fake_rails.rb",
    "content": "class FakeRails\n  def initialize(env, root)\n    @env = env\n    @root = root\n  end\n\n  attr_accessor :env, :root\n\n  def const_defined?(const)\n    false\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/empty.html",
    "content": "<html></html>\n"
  },
  {
    "path": "spec/support/fixtures/fog.yml",
    "content": "development:\n    provider: AWS\n    aws_access_key_id: AWS_ID\n    aws_secret_access_key: AWS_SECRET\ntest:\n    provider: AWS\n    aws_access_key_id: AWS_ID\n    aws_secret_access_key: AWS_SECRET\n"
  },
  {
    "path": "spec/support/fixtures/s3.yml",
    "content": "development:\n  key: 54321\nproduction:\n  key: 12345\ntest:\n  bucket: <%= ENV['S3_BUCKET'] %>\n  access_key_id: <%= ENV['S3_KEY'] %>\n  secret_access_key: <%= ENV['S3_SECRET'] %>\n"
  },
  {
    "path": "spec/support/fixtures/text.txt",
    "content": "paperclip!\n"
  },
  {
    "path": "spec/support/matchers/accept.rb",
    "content": "RSpec::Matchers.define :accept do |expected|\n  match do |actual|\n    actual.matches?(expected)\n  end\nend\n"
  },
  {
    "path": "spec/support/matchers/exist.rb",
    "content": "RSpec::Matchers.define :exist do |expected|\n  match do |actual|\n    File.exist?(actual)\n  end\nend\n"
  },
  {
    "path": "spec/support/matchers/have_column.rb",
    "content": "RSpec::Matchers.define :have_column do |column_name|\n  chain :with_default do |default|\n    @default = default\n  end\n\n  match do |columns|\n    column = columns.detect{|column| column.name == column_name }\n    column && column.default.to_s == @default.to_s\n  end\n\n  failure_message_method =\n    if RSpec::Version::STRING.to_i >= 3\n      :failure_message\n    else\n      :failure_message_for_should\n    end\n\n  send(failure_message_method) do |columns|\n    \"expected to find '#{column_name}', \" +\n      \"default '#{@default}' \" +\n      \"in #{columns.map { |column| [column.name, column.default] }}\"\n  end\nend\n"
  },
  {
    "path": "spec/support/mock_attachment.rb",
    "content": "class MockAttachment\n  attr_accessor :updated_at, :original_filename\n  attr_reader :options\n\n  def initialize(options = {})\n    @options = options\n    @model = options[:model]\n    @responds_to_updated_at = options[:responds_to_updated_at]\n    @updated_at = options[:updated_at]\n    @original_filename = options[:original_filename]\n  end\n\n  def instance\n    @model\n  end\n\n  def respond_to?(meth)\n    if meth.to_s == \"updated_at\"\n      @responds_to_updated_at || @updated_at\n    else\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/mock_interpolator.rb",
    "content": "class MockInterpolator\n  def initialize(options = {})\n    @options = options\n  end\n\n  def interpolate(pattern, attachment, style_name)\n    @interpolated_pattern = pattern\n    @interpolated_attachment = attachment\n    @interpolated_style_name = style_name\n    @options[:result]\n  end\n\n  def has_interpolated_pattern?(pattern)\n    @interpolated_pattern == pattern\n  end\n\n  def has_interpolated_style_name?(style_name)\n    @interpolated_style_name == style_name\n  end\n\n  def has_interpolated_attachment?(attachment)\n    @interpolated_attachment == attachment\n  end\nend\n"
  },
  {
    "path": "spec/support/mock_url_generator_builder.rb",
    "content": "class MockUrlGeneratorBuilder\n  def initializer\n  end\n\n  def new(attachment)\n    @attachment = attachment\n    @attachment_options = @attachment.options\n    self\n  end\n\n  def for(style_name, options)\n    @generated_url_with_style_name = style_name\n    @generated_url_with_options = options\n    \"hello\"\n  end\n\n  def has_generated_url_with_options?(options)\n    # options.is_a_subhash_of(@generated_url_with_options)\n    options.inject(true) do |acc,(k,v)|\n      acc && @generated_url_with_options[k] == v\n    end\n  end\n\n  def has_generated_url_with_style_name?(style_name)\n    @generated_url_with_style_name == style_name\n  end\nend\n"
  },
  {
    "path": "spec/support/model_reconstruction.rb",
    "content": "module ModelReconstruction\n  def reset_class class_name\n    ActiveRecord::Base.send(:include, Paperclip::Glue)\n    Object.send(:remove_const, class_name) rescue nil\n    klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))\n\n    klass.class_eval do\n      include Paperclip::Glue\n    end\n\n    klass.reset_column_information\n    klass.connection_pool.clear_table_cache!(klass.table_name) if klass.connection_pool.respond_to?(:clear_table_cache!)\n\n    if klass.connection.respond_to?(:schema_cache)\n      if ActiveRecord::VERSION::STRING >= \"5.0\"\n        klass.connection.schema_cache.clear_data_source_cache!(klass.table_name)\n      else\n        klass.connection.schema_cache.clear_table_cache!(klass.table_name)\n      end\n    end\n\n    klass\n  end\n\n  def reset_table table_name, &block\n    block ||= lambda { |table| true }\n    ActiveRecord::Base.connection.create_table :dummies, {force: true}, &block\n  end\n\n  def modify_table &block\n    ActiveRecord::Base.connection.change_table :dummies, &block\n  end\n\n  def rebuild_model options = {}\n    ActiveRecord::Base.connection.create_table :dummies, force: true do |table|\n      table.column :title, :string\n      table.column :other, :string\n      table.column :avatar_file_name, :string\n      table.column :avatar_content_type, :string\n      table.column :avatar_file_size, :bigint\n      table.column :avatar_updated_at, :datetime\n      table.column :avatar_fingerprint, :string\n    end\n    rebuild_class options\n  end\n\n  def rebuild_class options = {}\n    reset_class(\"Dummy\").tap do |klass|\n      klass.has_attached_file :avatar, options\n      klass.do_not_validate_attachment_file_type :avatar\n      Paperclip.reset_duplicate_clash_check!\n    end\n  end\n\n  def rebuild_meta_class_of obj, options = {}\n    meta_class_of(obj).tap do |metaklass|\n      metaklass.has_attached_file :avatar, options\n      metaklass.do_not_validate_attachment_file_type :avatar\n      Paperclip.reset_duplicate_clash_check!\n    end\n  end\n\n  def meta_class_of(obj)\n    class << obj\n      self\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/reporting.rb",
    "content": "module Reporting\n  def silence_stream(stream)\n    old_stream = stream.dup\n    stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')\n    stream.sync = true\n    yield\n  ensure\n    stream.reopen(old_stream)\n    old_stream.close\n  end\nend\n"
  },
  {
    "path": "spec/support/test_data.rb",
    "content": "module TestData\n  def attachment(options={})\n    Paperclip::Attachment.new(:avatar, FakeModel.new, options)\n  end\n\n  def stringy_file\n    StringIO.new('.\\n')\n  end\n\n  def fixture_file(filename)\n    File.join(File.dirname(__FILE__), 'fixtures', filename)\n  end\nend\n"
  },
  {
    "path": "spec/support/version_helper.rb",
    "content": "module VersionHelper\n  def active_support_version\n    ActiveSupport::VERSION::STRING\n  end\n\n  def ruby_version\n    RUBY_VERSION\n  end\nend\n"
  }
]