{"id":13411893,"url":"https://github.com/shrinerb/shrine","last_synced_at":"2026-10-19T01:03:38.663Z","repository":{"id":37887160,"uuid":"44935124","full_name":"shrinerb/shrine","owner":"shrinerb","description":"File Attachment toolkit for Ruby applications","archived":false,"fork":false,"pushed_at":"2026-10-11T16:30:15.000Z","size":20653,"stargazers_count":3284,"open_issues_count":0,"forks_count":279,"subscribers_count":36,"default_branch":"master","last_synced_at":"2026-10-13T23:28:56.649Z","etag":null,"topics":["attachment","background-jobs","direct-upload","file-upload","filesystem","metadata","orm","rack","ruby","s3","storage"],"latest_commit_sha":null,"homepage":"https://shrinerb.com","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/shrinerb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null},"funding":{"github":"janko","open_collective":"shrine"}},"created_at":"2015-10-25T23:33:32.000Z","updated_at":"2026-10-11T16:28:59.000Z","dependencies_parsed_at":"2026-09-28T22:56:27.630Z","dependency_job_id":null,"html_url":"https://github.com/shrinerb/shrine","commit_stats":{"total_commits":2335,"total_committers":107,"mean_commits":"21.822429906542055","dds":"0.10278372591006424","last_synced_commit":"ef636ceb37e14a2f686bcca69e6637dd651eb831"},"previous_names":["janko-m/shrine"],"tags_count":58,"template":false,"template_full_name":null,"purl":"pkg:github/shrinerb/shrine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shrinerb%2Fshrine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shrinerb%2Fshrine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shrinerb%2Fshrine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shrinerb%2Fshrine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shrinerb","download_url":"https://codeload.github.com/shrinerb/shrine/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shrinerb%2Fshrine/sbom","scorecard":{"id":505297,"data":{"date":"2025-08-11","repo":{"name":"github.com/shrinerb/shrine","commit":"e630ad6a1591ae2d452f8858b6e1cb6153c9464e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"Maintained","score":5,"reason":"7 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"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":"Code-Review","score":3,"reason":"Found 10/30 approved changesets -- score normalized to 3","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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"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":"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":"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":"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":"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":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:38: update your workflow using https://app.stepsecurity.io/secureworkflow/shrinerb/shrine/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/shrinerb/shrine/ci.yml/master?enable=pin","Warn: containerImage not pinned by hash: Dockerfile:1: pin your Docker image by updating node:8.11.4 to node:8.11.4@sha256:3422df4f7532b26b55275ad7b6dc17ec35f77192b04ce22e62e43541f3d28eb3","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned"],"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 12 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"}},{"name":"Vulnerabilities","score":0,"reason":"34 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-c7qv-q95q-8v27","Warn: Project is vulnerable to: GHSA-4www-5p9h-95mh","Warn: Project is vulnerable to: GHSA-9gqv-wp59-fq42","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-x7hr-w5r2-h6wg","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-76p7-773f-r4q5","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-w5p7-h5w8-2hfq","Warn: Project is vulnerable to: GHSA-4vvj-4cpr-p986","Warn: Project is vulnerable to: GHSA-wr3j-pwj9-hqq6","Warn: Project is vulnerable to: GHSA-4v9v-hfq4-rm2v","Warn: Project is vulnerable to: GHSA-9jgg-88mc-972h","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T23:08:22.468Z","repository_id":37887160,"created_at":"2025-08-19T23:08:22.469Z","updated_at":"2025-08-19T23:08:22.469Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":343843146,"owners_count":38294267,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-13T02:00:04.984Z","response_time":52,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["attachment","background-jobs","direct-upload","file-upload","filesystem","metadata","orm","rack","ruby","s3","storage"],"created_at":"2024-07-30T20:01:18.148Z","updated_at":"2026-10-19T01:03:38.220Z","avatar_url":"https://github.com/shrinerb.png","language":"Ruby","funding_links":["https://github.com/sponsors/janko","https://opencollective.com/shrine"],"categories":["Ruby","Uncategorized","File Upload","File uploaders"],"sub_categories":["Uncategorized"],"readme":"# [Shrine]\n\n\u003cimg src=\"https://shrinerb.com/img/logo.png\" width=\"100\" alt=\"Shrine logo: a red paperclip\" align=\"right\" /\u003e\n\nShrine is a toolkit for handling file attachments in Ruby applications. Some highlights:\n\n* **Modular design** – the [plugin system] allows you to load only the functionality you need\n* **Memory friendly** – streaming uploads and [downloads][Retrieving Uploads] make it work great with large files\n* **Cloud storage** – store files on [disk][FileSystem], [AWS S3][S3], [Google Cloud][GCS], [Cloudinary] and others\n* **Persistence integrations** – works with [Sequel], [ActiveRecord], [ROM], [Hanami] and [Mongoid] and others\n* **Flexible processing** – generate thumbnails [eagerly] or [on-the-fly] using [ImageMagick] or [libvips]\n* **Metadata validation** – [validate files][validation] based on [extracted metadata][metadata]\n* **Direct uploads** – upload asynchronously [to your app][simple upload] or [to the cloud][presigned upload] using [Uppy]\n* **Resumable uploads** – make large file uploads [resumable][resumable upload] on [S3][uppy-s3_multipart] or [tus][tus-ruby-server]\n* **Background jobs** – built-in support for [background processing][backgrounding] that supports [any backgrounding library][Backgrounding Libraries]\n\nIf you're curious how it compares to other file attachment libraries, see the\n[Advantages of Shrine]. Otherwise, follow along with the **[Getting Started\nguide]**.\n\n## Links\n\n| Resource                   | URL                                                                                      |\n| :----------------          | :-----------------------------------------------------------------------------           |\n| Website \u0026 Documentation    | [shrinerb.com](https://shrinerb.com)                                                     |\n| Demo code                  | [Roda][roda demo] / [Rails][rails demo]                                                  |\n| Wiki                       | [github.com/shrinerb/shrine/wiki](https://github.com/shrinerb/shrine/wiki)               |\n| Discussion forum           | [github.com/shrinerb/shrine/discussions](https://github.com/shrinerb/shrine/discussions) |\n| Alternate Discussion forum | [discourse.shrinerb.com](https://discourse.shrinerb.com)                                 |\n\n## Setup\n\nRun:\n\n```sh\nbundle add shrine\n```\n\nThen add `config/initializers/shrine.rb` which sets up the storage and loads\nORM integration:\n\n```rb\nrequire \"shrine\"\nrequire \"shrine/storage/file_system\"\n\nShrine.storages = {\n  cache: Shrine::Storage::FileSystem.new(\"public\", prefix: \"uploads/cache\"), # temporary\n  store: Shrine::Storage::FileSystem.new(\"public\", prefix: \"uploads\"),       # permanent\n}\n\nShrine.plugin :activerecord           # loads Active Record integration\nShrine.plugin :cached_attachment_data # enables retaining cached file across form redisplays\nShrine.plugin :restore_cached_data    # extracts metadata for assigned cached files\n```\n\nNext, add the `\u003cname\u003e_data` column to the table you want to attach files to. For\nan \"image\" attachment on a `photos` table this would be an `image_data` column:\n\n```\n$ rails generate migration add_image_data_to_photos image_data:text # or :jsonb\n```\nIf using `jsonb` consider adding a [gin index] for fast key-value pair searchability within `image_data`.\n\nNow create an uploader class (which you can put in `app/uploaders`) and\nregister the attachment on your model:\n\n```rb\nclass ImageUploader \u003c Shrine\n  # plugins and uploading logic\nend\n```\n```rb\nclass Photo \u003c ActiveRecord::Base\n  include ImageUploader::Attachment(:image) # adds an `image` virtual attribute\nend\n```\n\nIn our views let's now add form fields for our attachment attribute that will\nallow users to upload files:\n\n```erb\n\u003c%= form_for @photo do |f| %\u003e\n  \u003c%= f.hidden_field :image, value: @photo.cached_image_data, id: nil %\u003e\n  \u003c%= f.file_field :image %\u003e\n  \u003c%= f.submit %\u003e\n\u003c% end %\u003e\n```\n\nWhen the form is submitted, in your controller you can assign the file from\nrequest params to the attachment attribute on the model:\n\n```rb\nclass PhotosController \u003c ApplicationController\n  def create\n    Photo.create(photo_params) # attaches the uploaded file\n    # ...\n  end\n\n  private\n\n  def photo_params\n    params.require(:photo).permit(:image)\n  end\nend\n```\n\nOnce a file is uploaded and attached to the record, you can retrieve the file\nURL and display it on the page:\n\n```erb\n\u003c%= image_tag @photo.image_url %\u003e\n```\n\nSee the **[Getting Started guide]** for further documentation.\n\n## Inspiration\n\nShrine was heavily inspired by [Refile] and [Roda]. From Refile it borrows the\nidea of \"backends\" (here named \"storages\"), attachment interface, and direct\nuploads. From Roda it borrows the implementation of an extensible plugin\nsystem.\n\n## Similar libraries\n\n* Paperclip\n* CarrierWave\n* Dragonfly\n* Refile\n* Active Storage\n\n## Contributing\n\nPlease refer to the [contributing page][Contributing].\n\n## Code of Conduct\n\nEveryone interacting in the Shrine project’s codebases, issue trackers, and\nmailing lists is expected to follow the [Shrine code of conduct][CoC].\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License].\n\n[Shrine]: https://shrinerb.com\n[Advantages of Shrine]: https://shrinerb.com/docs/advantages\n[plugin system]: https://shrinerb.com/docs/getting-started#plugin-system\n[Retrieving Uploads]: https://shrinerb.com/docs/retrieving-uploads\n[FileSystem]: https://shrinerb.com/docs/storage/file-system\n[S3]: https://shrinerb.com/docs/storage/s3\n[GCS]: https://github.com/renchap/shrine-google_cloud_storage\n[Cloudinary]: https://github.com/shrinerb/shrine-cloudinary\n[Sequel]: https://shrinerb.com/docs/plugins/sequel\n[ActiveRecord]: https://shrinerb.com/docs/plugins/activerecord\n[ROM]: https://github.com/shrinerb/shrine-rom\n[Hanami]: https://github.com/katafrakt/hanami-shrine\n[Mongoid]: https://github.com/shrinerb/shrine-mongoid\n[eagerly]: https://shrinerb.com/docs/getting-started#eager-processing\n[on-the-fly]: https://shrinerb.com/docs/getting-started#on-the-fly-processing\n[ImageMagick]: https://github.com/janko/image_processing/blob/master/doc/minimagick.md#readme\n[libvips]: https://github.com/janko/image_processing/blob/master/doc/vips.md#readme\n[validation]: https://shrinerb.com/docs/validation\n[metadata]: https://shrinerb.com/docs/metadata\n[simple upload]: https://shrinerb.com/docs/getting-started#simple-direct-upload\n[presigned upload]: https://shrinerb.com/docs/getting-started#presigned-direct-upload\n[resumable upload]: https://shrinerb.com/docs/getting-started#resumable-direct-upload\n[Uppy]: https://uppy.io/\n[uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart\n[tus-ruby-server]: https://github.com/janko/tus-ruby-server\n[backgrounding]: https://shrinerb.com/docs/plugins/backgrounding\n[Backgrounding Libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-Libraries\n[Getting Started guide]: https://shrinerb.com/docs/getting-started\n[roda demo]: /demo\n[rails demo]: https://github.com/erikdahlstrand/shrine-rails-example\n[Refile]: https://github.com/refile/refile\n[Roda]: https://github.com/jeremyevans/roda\n[CoC]: /CODE_OF_CONDUCT.md\n[MIT License]: /LICENSE.txt\n[Contributing]: https://github.com/shrinerb/shrine/blob/master/CONTRIBUTING.md\n[gin index]: https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshrinerb%2Fshrine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshrinerb%2Fshrine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshrinerb%2Fshrine/lists"}