{"id":13427892,"url":"https://github.com/refile/refile","last_synced_at":"2026-03-27T02:44:06.709Z","repository":{"id":1016526,"uuid":"26870928","full_name":"refile/refile","owner":"refile","description":"Ruby file uploads, take 3","archived":false,"fork":false,"pushed_at":"2024-07-01T17:43:36.000Z","size":958,"stargazers_count":2435,"open_issues_count":28,"forks_count":299,"subscribers_count":43,"default_branch":"master","last_synced_at":"2026-03-26T07:13:32.768Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/refile.png","metadata":{"files":{"readme":"README.md","changelog":"History.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-11-19T16:41:22.000Z","updated_at":"2026-03-24T09:38:30.000Z","dependencies_parsed_at":"2024-01-31T05:07:52.963Z","dependency_job_id":"36b3be88-b306-4552-a713-8ae267cee5e3","html_url":"https://github.com/refile/refile","commit_stats":{"total_commits":474,"total_committers":80,"mean_commits":5.925,"dds":0.4620253164556962,"last_synced_commit":"c4ac577c6fdad92bc079a62a0e82888319daedc8"},"previous_names":["elabs/refile"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/refile/refile","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refile%2Frefile","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refile%2Frefile/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refile%2Frefile/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refile%2Frefile/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/refile","download_url":"https://codeload.github.com/refile/refile/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refile%2Frefile/sbom","scorecard":{"id":768610,"data":{"date":"2025-08-11","repo":{"name":"github.com/refile/refile","commit":"c4ac577c6fdad92bc079a62a0e82888319daedc8"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":5,"reason":"Found 7/13 approved changesets -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 27 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T01:35:48.946Z","repository_id":1016526,"created_at":"2025-08-23T01:35:48.946Z","updated_at":"2025-08-23T01:35:48.946Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30961816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-26T11:00:55.194Z","status":"ssl_error","status_checked_at":"2026-03-26T11:00:48.266Z","response_time":114,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T01:00:42.052Z","updated_at":"2026-03-27T02:44:06.665Z","avatar_url":"https://github.com/refile.png","language":"Ruby","readme":"# Refile\n\n[![Gem Version](https://badge.fury.io/rb/refile.svg)](http://badge.fury.io/rb/refile)\n[![Build Status](https://travis-ci.org/refile/refile.svg?branch=master)](https://travis-ci.org/refile/refile)\n[![Inline docs](http://inch-ci.org/github/refile/refile.svg?branch=master)](http://inch-ci.org/github/refile/refile)\n\nRefile is a modern file upload library for Ruby applications. It is simple, yet\npowerful.\n\nLinks:\n\n- [API documentation](http://www.rubydoc.info/gems/refile)\n- [Source Code](https://github.com/refile/refile)\n- [Contributing](https://github.com/refile/refile/blob/master/CONTRIBUTING.md)\n- [Code of Conduct](https://github.com/refile/refile/blob/master/CODE_OF_CONDUCT.md)\n- [Example Application](https://github.com/refile/refile_example_app)\n\n\nFeatures:\n\n- Configurable backends, file system, S3, etc...\n- Convenient integration with ORMs\n- On the fly manipulation of images and other files\n- Streaming IO for fast and memory friendly uploads\n- Works across form redisplays, i.e. when validations fail, even on S3\n- Effortless direct uploads, even to S3\n- Support for multiple file uploads\n- Support for single file upload\n\nSponsored by:\n\n[\u003cimg src=\"http://d3cv91luii1z1d.cloudfront.net/logo-gh.png\" alt=\"Elabs\" height=\"50px\"/\u003e](http://elabs.se)\n\n## Quick start, Rails\n\nAdd the gem:\n\n``` ruby\ngem \"refile\", require: \"refile/rails\"\ngem \"refile-mini_magick\"\n```\n\nWe're requiring both Refile's Rails integration and image processing via the\n[MiniMagick](https://github.com/minimagick/minimagick) gem, which requires\n[ImageMagick](http://imagemagick.org/) (or [GraphicsMagick](http://www.graphicsmagick.org/)) to be installed. To install it simply\nrun:\n\n``` sh\nbrew install imagemagick # OS X\nsudo apt-get install imagemagick # Ubuntu\n```\n\nUse the `attachment` method to use Refile in a model:\n\n``` ruby\nclass User \u003c ActiveRecord::Base\n  attachment :profile_image\nend\n```\n\nGenerate a migration:\n\n``` sh\nrails generate migration add_profile_image_to_users profile_image_id:string \u0026\u0026\nprofile_image_filename:string \u0026\u0026 profile_image_size:string \u0026\u0026\nprofile_image_content_type:string\n\nrake db:migrate\n```\n\nAdd an attachment field to your form:\n\n``` erb\n\u003c%= form_for @user do |form| %\u003e\n  \u003c%= form.attachment_field :profile_image %\u003e\n\u003c% end %\u003e\n```\n\nSet up strong parameters:\n\n``` ruby\ndef user_params\n  params.require(:user).permit(:profile_image)\nend\n```\n\nAnd start uploading! Finally show the file in your view:\n\n``` erb\n\u003c%= image_tag attachment_url(@user, :profile_image, :fill, 300, 300, format: \"jpg\") %\u003e\n```\n\n## How it works\n\nRefile consists of several parts:\n\n1. Backends: cache and persist files\n2. Model attachments: map files to model columns\n3. A Rack application: streams files and accepts uploads\n4. Rails helpers: conveniently generate markup in your views\n5. A JavaScript library: facilitates direct uploads\n\nLet's look at each of these in more detail!\n\n## 1. Backend\n\nFiles are uploaded to a backend. The backend assigns an ID to this file, which\nwill be unique for this file within the backend.\n\nLet's look at a simple example of using the backend:\n\n``` ruby\nbackend = Refile::Backend::FileSystem.new(\"tmp\")\n\nfile = backend.upload(StringIO.new(\"hello\"))\nfile.id # =\u003e \"b205bc...\"\nfile.read # =\u003e \"hello\"\n\nbackend.get(file.id).read # =\u003e \"hello\"\n```\n\nAs you may notice, backends are \"flat\". Files do not have directories, nor do\nthey have names or permissions, they are only identified by their ID.\n\nRefile has a global registry of backends, accessed through `Refile.backends`.\n\nThere are two \"special\" backends, which are only really special in that they\nare the default backends for attachments. They are `cache` and `store`.\n\nBy default files will be uploaded to `./tmp/uploads/store`. If you would like\nto persist them between deploys of your application, you can override the upload\nfolder by adding an initializer like this:\n\n```ruby\n# config/initializers/refile.rb\n\nRefile.backends['store'] = Refile::Backend::FileSystem.new('/etc/projectname-uploads/')\n```\n\nThe cache is intended to be transient. Files are added here before they are\nmeant to be permanently stored. Usually files are then moved to the store for\npermanent storage, but this isn't always the case.\n\nSuppose for example that a user uploads a file in a form and receives a\nvalidation error. In that case the file has been temporarily stored in the\ncache. The user might decide to fix the error and resubmit, at which point the\nfile will be promoted to the store. On the other hand, the user might simply\ngive up and leave, now the file is left in the cache for later cleanup.\n\nRefile has convenient accessors for setting the `cache` and `store`, so for\nexample if you add the refile-s3 gem to your Gemfile:\n\n``` ruby\ngem \"refile-s3\"\n```\n\nNow you can upload files to S3 easily by using these accessors:\n\n``` ruby\n# config/initializers/refile.rb\nrequire \"refile/s3\"\n\naws = {\n  access_key_id: \"xyz\",\n  secret_access_key: \"abc\",\n  region: \"sa-east-1\",\n  bucket: \"my-bucket\",\n}\nRefile.cache = Refile::S3.new(prefix: \"cache\", **aws)\nRefile.store = Refile::S3.new(prefix: \"store\", **aws)\n```\n\nTry this in the quick start example above and your files are now uploaded to\nS3.\n\nBackends also provide the option of restricting the size of files they accept.\nFor example:\n\n``` ruby\nRefile.cache = Refile::S3.new(max_size: 10.megabytes, ...)\n```\n\nThe Refile gem only ships with a\n[FileSystem](lib/refile/backend/file_system.rb) backend. Additional backends\nare provided by other gems.\n\n- [Amazon S3](https://github.com/refile/refile-s3)\n- [Fog](https://github.com/refile/refile-fog) provides support for a ton of\n  different cloud storage providers, including Google Storage and Rackspace\n  CloudFiles.\n- [Postgresql](https://github.com/krists/refile-postgres)\n- [Gridfs](https://github.com/Titinux/refile-gridfs)\n- [In Memory](https://github.com/refile/refile-memory)\n\n### Uploadable\n\nThe `upload` method on backends can be called with a variety of objects. It\nrequires that the object passed to it behaves similarly to Ruby IO objects, in\nparticular it must implement the methods `size`, `read(length = nil, buffer =\nnil)`, `eof?`, `rewind`, and `close`. All of `File`, `Tempfile`,\n`ActionDispatch::UploadedFile` and `StringIO` implement this interface, however\n`String` does not. If you want to upload a file from a `String` you must wrap\nit in a `StringIO` first.\n\n## 2. Attachments\n\nYou've already seen the `attachment` method:\n\n``` ruby\nclass User \u003c ActiveRecord::Base\n  attachment :profile_image\nend\n```\n\nCalling `attachment` generates a getter and setter with the given name. When\nyou assign a file to the setter, it is uploaded to the cache:\n\n``` ruby\nUser.new\n\n# with a ActionDispatch::UploadedFile\nuser.profile_image = params[:file]\n\n# with a regular File object\nFile.open(\"/some/path\", \"rb\") do |file|\n  user.profile_image = file\nend\n\n# or a StringIO\nuser.profile_image = StringIO.new(\"hello world\")\n\nuser.profile_image.id # =\u003e \"fec421...\"\nuser.profile_image.read # =\u003e \"hello world\"\n```\n\nWhen you call `save` on the record, the uploaded file is transferred from the\ncache to the store. Where possible, Refile does this move efficiently. For example\nif both `cache` and `store` are on the same S3 account, instead of downloading\nthe file and uploading it again, Refile will simply issue a copy command to S3.\n\n### Other ORMs\n\nRefile comes with [ActiveRecord\nintegration](lib/refile/attachment/active_record.rb) built-in, but is built to\nintegrate with any ORM, so building your own should not be too difficult. Some\nintegrations are already available via gems:\n\n* [refile-sequel](https://github.com/refile/refile-sequel)\n* [refile-mongoid](https://github.com/krettan/refile-mongoid)\n\n### Pure Ruby classes\n\nYou can also use attachments in pure Ruby classes like this:\n\n``` ruby\nclass User\n  extend Refile::Attachment\n\n  attr_accessor :profile_image_id\n\n  attachment :profile_image\nend\n```\n\n### Keeping uploaded files\n\nBy default Refile will delete a stored file when its model is destroyed. You can change this behaviour by passing in the `destroy` option.\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  attachment :profile_image, destroy: false\nend\n\n```\n\nNow Refile will not delete the `profile_image` file from the store if the user is destroyed.\n\n## 3. Rack Application\n\nRefile includes a Rack application (an endpoint, not a middleware), written in\nSinatra. This application streams files from backends and can even accept file\nuploads and upload them to backends.\n\n**Important:** Unlike other file upload solutions, Refile always streams your files through your\napplication. It cannot generate URLs to your files. This means that you should\n**always** put a CDN or other HTTP cache in front of your application. Serving\nfiles through your app takes a lot of resources and you want it to happen rarely.\n\nSetting this up is actually quite simple, you can use the same CDN you would use\nfor your application's static assets. [This blog post](http://www.happybearsoftware.com/use-cloudfront-and-the-rails-asset-pipeline-to-speed-up-your-app.html)\nexplains how to set this up (bonus: faster static assets!). Once you've set this\nup, simply configure Refile to use your CDN:\n\n``` ruby\nRefile.cdn_host = \"https://your-dist-url.cloudfront.net\"\n```\n\nUsing [the HTTPS protocol](https://www.eff.org/encrypt-the-web-report) for `Refile.cdn_host` is recommended. There aren't [any performance concerns](https://istlsfastyet.com/), and it is always safe to request HTTPS assets.\n\n### Mounting\n\nIf you are using Rails and have required [refile/rails.rb](lib/refile/rails.rb),\nthen the Rack application is mounted for you at `/attachments`. You should be able\nto see this when you run `rake routes`.\n\nYou can configure Refile to use a different `mount_point` than `/attachments`:\n\n``` ruby\nRefile.mount_point = \"/your-preferred-mount-point\"\n```\n\nYou could also run the application on its own, it doesn't need to be mounted to\nwork.\n\nIf you are using a catch-all route (such as required by Comfy CMS), you will need to turn off Automounting and add the refile route before your catch all route.\n\n(in initializers/refile.rb)\n``` ruby\nRefile.automount = false\n```\n\nin routes.rb\n``` ruby\n  mount Refile.app, at: Refile.mount_point, as: :refile_app\n\n  # Make sure this routeset is defined last\n  comfy_route :cms, :path =\u003e '/', :sitemap =\u003e true\n\n```\n\n\n\n### Retrieving files\n\nFiles can be retrieved from the application by calling:\n\n```\nGET /attachments/:token/:backend_name/:id/:filename\n```\n\nThe `:filename` serves no other purpose than generating a nice name when the user\ndownloads the file, it does not in any way affect the downloaded file. For caching\npurposes you should always use the same filename for the same file. The Rails helpers\ndefault this to the name of the column.\n\nThe `:token` is a generated digest of the request path when the\n`Refile.secret_key` is configured; otherwise, the application will raise an error.\nThe digest feature provides a security measure against unverified requests.\n\n**NOTICE:** If you don't set the `Refile.secret_key` we will use rails `secret_key_base`\nto generate the token. We suggest you not to change the `secret_key_base` after you\ngenerated and hardcoded some attachment URLs in your application (e.g. blog post images),\nbecause the token will change and you'll not be able to retrieve in this case, the images.\n\n### Processing\n\nRefile provides on the fly processing of files. You can trigger it by calling\na URL like this:\n\n```\nGET /attachments/:token/:backend_name/:processor_name/*args/:id/:filename\n```\n\nSuppose we have uploaded a file:\n\n``` ruby\nRefile.cache.upload(StringIO.new(\"hello\")).id # =\u003e \"a4e8ce\"\n```\n\nAnd we've defined a processor like this:\n\n``` ruby\nRefile.processor :reverse do |file|\n  StringIO.new(file.read.reverse)\nend\n```\n\nThen you could do the following.\n\n``` sh\ncurl http://127.0.0.1:3000/attachments/token/cache/reverse/a4e8ce/some_file.txt\nelloh\n```\n\nRefile calls `call` on the processor and passes in the retrieved file, as well\nas all additional arguments sent through the URL.\n\n## 4. Rails helpers\n\nRefile provides the `attachment_field` form helper which generates a file field\nas well as a hidden field. This field keeps track of the file in case it is not\nyet permanently stored, for example if validations fail. It is also used for\ndirect and presigned uploads. For this reason it is highly recommended to use\n`attachment_field` instead of `file_field`.\n\n``` erb\n\u003c%= form_for @user do |form| %\u003e\n  \u003c%= form.attachment_field :profile_image %\u003e\n\u003c% end %\u003e\n```\n\nWill generate something like:\n\n``` html\n\u003cform action=\"/users\" enctype=\"multipart/form-data\" method=\"post\"\u003e\n  \u003cinput name=\"user[profile_image]\" type=\"hidden\"\u003e\n  \u003cinput name=\"user[profile_image]\" type=\"file\"\u003e\n\u003c/form\u003e\n```\n\nThe `attachment_url` helper can then be used for generating URLs for the uploaded\nfiles:\n\n``` erb\n\u003c%= link_to \"Image\", attachment_url(@user, :profile_image) %\u003e\n```\n\nAny additional arguments to it are included in the URL as processor arguments:\n\n``` erb\n\u003c%= link_to \"Image\", attachment_url(@user, :profile_image, :fill, 300, 300) %\u003e\n```\n\nThere's also a helper for generating image tags:\n\n``` erb\n\u003c%= attachment_image_tag(@user, :profile_image, :fill, 300, 300) %\u003e\n```\n\nYou can also provide a limit to the image:\n\n``` erb\n\u003c%= link_to \"Image\", attachment_url(@user, :profile_image, :limit, 400, 500) %\u003e\n```\n\nIf you don't care about the aspect ratio and want an exact dimension, you can use `!`:\n\n``` erb\n\u003c%= link_to \"Image\", attachment_url(@user, :profile_image, :limit, 400, \"1000!\") %\u003e\n```\n\nKeep in mind that it's also important to remember you can not stretch the image, even you set\na larger width or height the image will keep its default dimensions. For example: if you set\n`400x1000!` for an image `600x800` it'll keep its height of `400x800`.\n\nIf you just care about limit only one dimension, you can use `nil` in widht or height:\n\n``` erb\n\u003c%= link_to \"Image\", attachment_url(@user, :profile_image, :limit, 400, nil) %\u003e\n\u003c%= link_to \"Image\", attachment_url(@user, :profile_image, :limit, nil, 400) %\u003e\n```\n\nWith this helper you can specify an image/asset which is used as a fallback in case\nno file has been uploaded:\n\n``` erb\n\u003c%= attachment_url(@user, :profile_image, :fill, 300, 300, fallback: \"default.png\") %\u003e\n\u003c%= attachment_image_tag(@user, :profile_image, :fill, 300, 300, fallback: \"default.png\") %\u003e\n```\n\nYou can also set the URL to force the download of the uploaded file:\n\n``` erb\n\u003c%= link_to \"Download\", attachment_url(@user, :profile_image, force_download: true) %\u003e\n```\n\nUse `Refile.attachment_url` if you already have `attachment` in your routes.\n\n## 5. JavaScript library\n\nRefile's JavaScript library is small but powerful.\n\nUploading files is slow, so anything we can do to speed up the process is going\nto lead to happier users. One way to cheat is to start uploading files directly\nafter the user has chosen a file, instead of waiting until they hit the submit\nbutton. This provides a significantly better user experience. Implementing this\nis usually tricky, but thankfully Refile makes it very easy.\n\nFirst, load the JavaScript file. If you're using the asset pipeline, you can\nsimply include it like this:\n\n``` javascript\n//= require refile\n```\n\nOtherwise you can grab a copy [here](https://raw.githubusercontent.com/refile/refile/master/app/assets/javascripts/refile.js).\nBe sure to always update your copy of this file when you upgrade to the latest\nRefile version.\n\nNow mark the field for direct upload:\n\n``` erb\n\u003c%= form.attachment_field :profile_image, direct: true %\u003e\n```\n\nThere is no step 3 ;)\n\nThe file is now uploaded to the `cache` immediately after the user chooses a file.\nIf you try this in the browser, you'll notice that an AJAX request is fired as\nsoon as you choose a file. Then when you submit to the server, the file is no\nlonger submitted, only its id.\n\nIf you want to improve the experience of this, the JavaScript library fires\na couple of custom DOM events. These events bubble, so you can also listen for\nthem on the form for example:\n\n``` javascript\nform.addEventListener(\"upload:start\", function() {\n  // ...\n});\n\nform.addEventListener(\"upload:success\", function() {\n  // ...\n});\n\ninput.addEventListener(\"upload:progress\", function() {\n  // ...\n});\n```\n\nYou can also listen for them with jQuery, even with event delegation:\n\n``` javascript\n$(document).on(\"upload:start\", \"form\", function(e) {\n  // ...\n});\n```\n\nThis way you could for example disable the submit button until all files have\nuploaded:\n\n``` javascript\n$(document).on(\"upload:start\", \"form\", function(e) {\n  $(this).find(\"input[type=submit]\").attr(\"disabled\", true)\n});\n\n$(document).on(\"upload:complete\", \"form\", function(e) {\n  if(!$(this).find(\"input.uploading\").length) {\n    $(this).find(\"input[type=submit]\").removeAttr(\"disabled\")\n  }\n});\n```\n\n### Presigned uploads\n\nAmazon S3 supports uploads directly from the browser to S3 buckets. With this\nfeature you can bypass your application entirely; uploads never hit your application\nat all. Unfortunately the default configuration of S3 buckets does not allow\ncross site AJAX requests from posting to buckets. Fixing this is easy though.\n\n- Open the AWS S3 console and locate your bucket\n- Right click on it and choose \"Properties\"\n- Open the \"Permission\" section\n- Click \"Add CORS Configuration\"\n\nThe default configuration only allows \"GET\", you'll want to allow \"POST\" as\nwell. You'll also want to permit the \"Content-Type\" and \"Origin\" headers.\n\nIt could look something like this:\n\n``` xml\n\u003cCORSConfiguration\u003e\n    \u003cCORSRule\u003e\n        \u003cAllowedOrigin\u003e*\u003c/AllowedOrigin\u003e\n        \u003cAllowedMethod\u003eGET\u003c/AllowedMethod\u003e\n        \u003cAllowedMethod\u003ePOST\u003c/AllowedMethod\u003e\n        \u003cMaxAgeSeconds\u003e3000\u003c/MaxAgeSeconds\u003e\n        \u003cAllowedHeader\u003eAuthorization\u003c/AllowedHeader\u003e\n        \u003cAllowedHeader\u003eContent-Type\u003c/AllowedHeader\u003e\n        \u003cAllowedHeader\u003eOrigin\u003c/AllowedHeader\u003e\n    \u003c/CORSRule\u003e\n\u003c/CORSConfiguration\u003e\n```\n\nIf you're paranoid you can restrict the allowed origin to only your domain, but\nsince your bucket is only writable with authentication anyway, this shouldn't\nbe necessary. Note that you do not need to, and in fact you shouldn't, make\nyour bucket world writable.\n\nOnce you've put in the new configuration, click \"Save\". After that it may take\nsome time for the CORS setup to kick in (because of DNS propagation).\n\nNow you can enable presigned uploads:\n\n``` erb\n\u003c%= form.attachment_field :profile_image, presigned: true %\u003e\n```\n\nYou can also enable both direct and presigned uploads, and it'll fall back to\ndirect uploads if presigned uploads aren't available. This is useful if you're\nusing the FileSystem backend in development or test mode and the S3 backend in\nproduction mode.\n\n``` erb\n\u003c%= form.attachment_field :profile_image, direct: true, presigned: true %\u003e\n```\n\n### Browser compatibility\n\nRefile's JavaScript library requires HTML5 features which are unavailable on\nIE9 and earlier versions. All other major browsers are supported.\n\n## Authentication\n\nURLs generated by Refile are cryptographically signed. This ensures that a file\ncannot be downloaded unless you hand someone the URL to that file. This is\nessentially equivalent to token base authentication.\n\nUnfortunately a similar system is not in place for file uploads, meaning that\ndirect file uploads are open to anyone. By default only the `cache` backend can\nbe uploaded to, and you are encouraged to purge unused files from this backend\nperiodically. This might seem insecure, but consider the fact that anyone who\ncan access file uploads in your application will be able to upload files into\nit anyway.\n\nNevertheless, you can disable direct file uploads by setting:\n\n```\nRefile.allow_uploads_to = []\n```\n\nYou also have the option of explicitly authenticating anyone who tries to\naccess the Refile application. Since the Refile application is a Sinatra\napplication, you can use Sinatra's before hooks to set up authentication like\nthis:\n\n```\nRefile::App.before do\n  halt 403 unless User.find_by(id: session[:user_id])\nend\n```\n\n## Additional metadata\n\nIn the quick start example above, we chose to only store the file id, but often\nit is useful to store the file's filename, size and content type as well.\nRefile makes it easy to extract this data and store it alongside the id. All you\nneed to do is add columns for these:\n\n``` ruby\nclass StoreMetadata \u003c ActiveRecord::Migration\n  def change\n    add_column :users, :profile_image_filename, :string\n    add_column :users, :profile_image_size, :integer\n    add_column :users, :profile_image_content_type, :string\n  end\nend\n```\n\nThese columns will now be filled automatically.\n\n## File type validations\n\nRefile can check that attached files have a given content type or extension.\nThis allows you to warn users if they try to upload an invalid file.\n\n**Important:** You should regard this as a convenience feature for your users,\nnot a security feature. Both file extension and content type can easily be\nspoofed.\n\nIn order to limit attachments to an extension or content type, you can provide\nthem like this:\n\n``` ruby\nattachment :cv, extension: \"pdf\"\nattachment :profile_image, content_type: \"image/jpeg\"\n```\n\nYou can also provide a list of content types or extensions:\n\n``` ruby\nattachment :cv, extension: [\"pdf\", \"doc\"]\nattachment :profile_image, content_type: [\"image/jpeg\", \"image/png\", \"image/gif\"]\n```\n\nSince the combination of JPEG, PNG and GIF is so common, you can also specify\nthis more succinctly like this:\n\n``` ruby\nattachment :profile_image, type: :image\n```\n\nWhen a user uploads a file with an invalid extension or content type and\nsubmits the form, they'll be presented with a validation error.\n\nIf you use a particular content type or set of content types frequently\nyou can define your own types like this:\n\n``` ruby\nRefile.types[:document] = Refile::Type.new(:document,\n  content_type: %w[text/plain application/pdf]\n)\n```\n\nNow you can use them like this:\n\n``` ruby\nattachment :profile_image, type: :document\n```\n\n## Multiple file uploads\n\nFile input fields support the `multiple` attribute which allows users to attach\nmultiple files at once. Refile supports this attribute. You can add the\nattribute to your attachment fields like this:\n\n``` erb\n\u003c%= form.attachment_field :images_files, multiple: true %\u003e\n```\n\nMultiple file uploads also work nicely with direct and presigned uploads:\n\n``` erb\n\u003c%= form.attachment_field :images_files, multiple: true, direct: true, presigned: true %\u003e\n```\n\nNote that you will get separate events for each uploaded file. So when you\nattach two files, the `upload:start` event and other events will be triggered\ntwice, once for each file.\n\nWhen you upload multiple files, your application will receive an array of\nfiles, instead of a single file. To map these files to model object, Refile's\nActiveRecord integration ships with a nice macro that makes this trivial. Suppose\nyou have an image model like this:\n\n``` ruby\nclass Image \u003c ActiveRecord::Base\n  belongs_to :post\n  attachment :file\nend\n```\n\nNote it must be possible to persist images given only the associated post and a\nfile. There must not be any other validations or constraints which prevent\nimages from being saved.\n\nFrom the post model, you can use the `accepts_attachments_for` macro:\n\n``` ruby\nclass Post \u003c ActiveRecord::Base\n  has_many :images, dependent: :destroy, autosave: true\n  accepts_attachments_for :images, attachment: :file\nend\n```\n\nThe `attachment` option defaults to `:file`, so we could have left it out in\nthis case.\n\n``` ruby\nclass Post \u003c ActiveRecord::Base\n  has_many :images, dependent: :destroy, autosave: true\n  accepts_attachments_for :images\nend\n```\n\nNote: Leaving out the `autosave` option will only save the attachments when the post is created.\n\nYou can add the attachment field to your post form:\n\n``` erb\n\u003c%= form_for @post do |form| %\u003e\n  \u003c%= form.label :images_files %\u003e\n  \u003c%= form.attachment_field :images_files, multiple: true %\u003e\n\u003c% end %\u003e\n```\n\nNow you only need to permit the generated accessor in your controller.  Since\n`images_files` is an array, you need to tell Rails to allow array values for\nit:\n\n``` ruby\ndef post_params\n  params.require(:post).permit(images_files: [])\nend\n```\n\nWhen editing a record with `accepts_attachments_for`, the default behaviour is\nto replace the entire list of attachments when new attachments are uploaded. It\nis also possible to append the new attachments to the list of attachments instead\nso that older attachments are kept. To enable this, set the `append` option to\n`true`.\n\n``` ruby\nclass Post \u003c ActiveRecord::Base\n  has_many :images, dependent: :destroy\n  accepts_attachments_for :images, append: true\nend\n```\n### Multiple file uploads for pure Ruby classes\n\nYou can also use `accepts_attachments_for` macro in pure Ruby classes.\n\nSuppose you have a `Document` class to be associated with a `Post` class.\nA post has many documents, and each document has a file.\nFirst, you will need to use the `attachment` macro in the `Document` class to\ndeclare your `:file` attachment, and also implement a constructor to receive\nthe attachment:\n\n```ruby\nclass Document\n  extend Refile::Attachment\n  attr_accessor :file_id\n\n  attachment :file\n\n  def initialize(attributes = {})\n    self.file = attributes[:file]\n  end\nend\n```\n\nIn the `Post` class, you will need a constructor to initialize the `@documents`\nvariable and an `attr_accessor :documents`. Then you can use the `accepts_attachments_for`\nmacro for declaring the `:documents` collection:\n\n```ruby\nclass Post\n  extend Refile::Attachment\n  include ActiveModel::Model\n\n  attr_accessor :documents\n\n  accepts_attachments_for(\n    :documents,\n    accessor_prefix: 'documents_files',\n    collection_class: Document\n  )\n\n  def initialize(attributes = {})\n    @documents = attributes[:documents] || []\n  end\nend\n```\n\nIn this example, we specified the following options:\n\n- `collection_class` is the attachments class, `Document`\n- `accessor_prefix` gives a prefix to the generated accessors, `documents_files`.\n\nNow you can append attachments with your HTML form in the following way:\n\n```erb\n\u003c%= form_for @post do |form| %\u003e\n  \u003c%= form.label :documents_files %\u003e\n  \u003c%= form.attachment_field :documents_files, multiple: true %\u003e\n\u003c% end %\u003e\n```\n\nThe default values for the `accepts_attachments_for` macro are\n`{ attachment: :file, append: false }`. Everything else should be similar to\nthe Active Record version of this macro.\n\n## Single file upload\n\nFile input fields support single file upload, allows users to attach\none file at the time instead of the common multiple files feature.\nLet's suppose you have an image model like this:\n\n``` ruby\nclass Image \u003c ActiveRecord::Base\n  belongs_to :post\n  attachment :file\nend\n```\n\nNote it must be possible to persist images given only the associated post and a\nfile. There must not be any other validations or constraints which prevent\nimages from being saved.\n\nFrom the post model, you can use the `accepts_attachments_for` macro:\n\n``` ruby\nclass Post \u003c ActiveRecord::Base\n  has_many :images, dependent: :destroy\n  accepts_attachments_for :images, attachment: :file\nend\n```\n\nThe `attachment` option defaults to `:file`, so we could have left it out in\nthis case.\n\n``` ruby\nclass Post \u003c ActiveRecord::Base\n  has_many :images, dependent: :destroy\n  accepts_attachments_for :images\nend\n```\n\nYou can add the attachment field to your post form without using the `multiple`\nattribute:\n\n``` erb\n\u003c%= form_for @post do |form| %\u003e\n  \u003c%= form.label :images_files %\u003e\n  \u003c%= form.attachment_field :images_files %\u003e\n\u003c% end %\u003e\n```\n\nNow you only need to permit the generated accessor in your controller.  Since\n`images_files` is not an array, you need to tell Rails to only allow the\nattribute symbol:\n\n``` ruby\ndef post_params\n  params.require(:post).permit(:images_files)\nend\n```\n\n## Removing attached files\n\nFile input fields unfortunately do not have the option of removing an already\nuploaded file. This is problematic when editing a model which has a file attached\nand the user wants to remove this file. To work around this, Refile automatically\nadds an attribute to your model when you use the `attachment` method, which is\ndesigned to be used with a checkbox in a form.\n\n``` erb\n\u003c%= form_for @user do |form| %\u003e\n  \u003c%= form.label :profile_image %\u003e\n  \u003c%= form.attachment_field :profile_image %\u003e\n\n  \u003c%= form.check_box :remove_profile_image %\u003e\n  \u003c%= form.label :remove_profile_image %\u003e\n\u003c% end %\u003e\n```\n\nDon't forget to permit this attribute in your controller:\n\n``` ruby\ndef user_params\n  params.require(:user).permit(:profile_image, :remove_profile_image)\nend\n```\n\nNow when you check this checkbox and submit the form, the previously attached\nfile will be removed.\n\n## Fetching remote files by URL\n\nYou might want to give you users the option of uploading a file by its URL.\nThis could be either just via a textfield or through some other interface.\nRefile makes it easy to fetch this file and upload it. Just add a field like\nthis:\n\n``` erb\n\u003c%= form_for @user do |form| %\u003e\n  \u003c%= form.label :profile_image, \"Attach image\" %\u003e\n  \u003c%= form.attachment_field :profile_image %\u003e\n\n  \u003c%= form.label :remote_profile_image_url, \"Or specify URL\" %\u003e\n  \u003c%= form.text_field :remote_profile_image_url %\u003e\n\u003c% end %\u003e\n```\n\nThen permit this field in your controller:\n\n``` ruby\ndef user_params\n  params.require(:user).permit(:profile_image, :remote_profile_image_url)\nend\n```\n\nRefile will now fetch the file from the given URL, following redirects if\nneeded.\n\n## Cache expiry\n\nFiles will accumulate in your cache, and you'll probably want to remove them\nafter some time.\n\nThe FileSystem backend does not currently provide any method of doing this. PRs\nwelcome ;)\n\nOn S3 this can be conveniently handled through lifecycle rules. Exactly how\ndepends a bit on your setup. If you are using the suggested setup of having\none bucket with `cache` and `store` being directories in that bucket (or prefixes\nin S3 parlance), then follow the following steps, otherwise adapt them to your\nneeds:\n\n- Open the AWS S3 console and locate your bucket\n- Right click on it and choose \"Properties\"\n- Open the \"Lifecycle\" section\n- Click \"Add rule\"\n- Choose \"Apply the rule to: A prefix\"\n- Enter \"cache/\" as the prefix (trailing slash!)\n- Click \"Configure rule\"\n- For \"Action on Objects\" you'll probably want to choose \"Permanently Delete Only\"\n- Choose whatever number of days you're comfortable with, I chose \"1\"\n- Click \"Review\" and finally \"Create and activate Rule\"\n\n## Testing\n\nWhen testing your own classes that use Refile, you can use `Refile::FileDouble` objects instead of real files.\n\n```ruby\n# app/models/post.rb\nclass Post \u003c ActiveRecord::Base\n  attachment :image, type: :image\nend\n\n# spec/models/post_spec.rb\nrequire \"rails_helper\"\nrequire \"refile/file_double\"\n\nRSpec.describe Post, type: :model do\n  it \"allows attaching an image\" do\n    post = Post.new\n\n    post.image = Refile::FileDouble.new(\"dummy\", \"logo.png\", content_type: \"image/png\")\n    post.save\n\n    expect(post.image_id).not_to be_nil\n  end\n\n  it \"doesn't allow attaching other files\" do\n    post = Post.new\n\n    post.image = Refile::FileDouble.new(\"dummy\", \"file.txt\", content_type: \"text/plain\")\n    post.save\n\n    expect(post.image_id).to be_nil\n  end\nend\n```\n\n## simple_form\n\nsimple_form gem is also supported:\n\n```Ruby\n# in initializer or Gemfile\nrequire 'refile/simple_form'\n\n# in forms\n\u003c%= f.input :cover_image, as: :attachment, direct: true, presigned: true %\u003e\n```\n\n## License\n\n[MIT](LICENSE.txt)\n","funding_links":[],"categories":["File Uploading","Ruby","文件上传","File Upload"],"sub_categories":["Omniauth"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frefile%2Frefile","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frefile%2Frefile","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frefile%2Frefile/lists"}