{"id":21554998,"url":"https://github.com/algonauti/ember-active-storage","last_synced_at":"2025-04-10T10:10:49.296Z","repository":{"id":29770309,"uuid":"122734912","full_name":"algonauti/ember-active-storage","owner":"algonauti","description":"Direct uploads with Rails' Active Storage","archived":false,"fork":false,"pushed_at":"2023-06-06T09:56:06.000Z","size":982,"stargazers_count":22,"open_issues_count":0,"forks_count":5,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-10-14T06:33:20.187Z","etag":null,"topics":["ember","rails"],"latest_commit_sha":null,"homepage":"https://github.com/algonauti/ember-active-storage","language":"JavaScript","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/algonauti.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"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":"2018-02-24T11:33:48.000Z","updated_at":"2024-05-30T02:51:28.000Z","dependencies_parsed_at":"2024-06-21T17:51:58.336Z","dependency_job_id":"9ef0997d-f534-43a0-9576-c9eefb4c47f9","html_url":"https://github.com/algonauti/ember-active-storage","commit_stats":{"total_commits":110,"total_committers":7,"mean_commits":"15.714285714285714","dds":0.4727272727272728,"last_synced_commit":"52ebc9f204eb10ce63ad6b3bdf8efb32fb91a7d9"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/algonauti%2Fember-active-storage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/algonauti%2Fember-active-storage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/algonauti%2Fember-active-storage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/algonauti%2Fember-active-storage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/algonauti","download_url":"https://codeload.github.com/algonauti/ember-active-storage/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248198888,"owners_count":21063628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["ember","rails"],"created_at":"2024-11-24T08:00:29.649Z","updated_at":"2025-04-10T10:10:49.264Z","avatar_url":"https://github.com/algonauti.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ember-active-storage\n\n[![CI](https://github.com/algonauti/ember-active-storage/workflows/CI/badge.svg)](https://github.com/algonauti/ember-active-storage/actions)\n[![Ember Observer Score](https://emberobserver.com/badges/-algonauti-ember-active-storage.svg)](https://emberobserver.com/addons/@algonauti/ember-active-storage)\n\n## Installation\n\n```\nember install @algonauti/ember-active-storage\n```\n\n## Usage\n\nThe addon provides an `activeStorage` service that allows you to:\n\n- send files to your Rails backend's direct upload controller;\n- listen to upload progress events.\n\nAssuming your template has a file input like:\n\n```hbs\n\u003cinput type=\"file\" {{on \"change\" (fn this.upload)}} /\u003e\n```\n\nand your ember model has an `avatar` attribute defined as `has_one_attached :avatar` on its corresponding Active Record model, then in your component (or controller) the `upload` action would look like:\n\n```javascript\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tracking';\nimport { inject as service } from '@ember/service';\n\nexport default class UploadComponent extends Component {\n  @service\n  activeStorage;\n\n  @tracked\n  uploadProgress = 0;\n\n  @action\n  upload(event) {\n    const files = event.target.files;\n    if (files) {\n      const directUploadURL = '/rails/active_storage/direct_uploads';\n\n      for (var i = 0; i \u003c files.length; i++) {\n        this.activeStorage\n          .upload(files.item(i), directUploadURL, {\n            onProgress: (progress, event) =\u003e {\n              this.uploadProgress = progress;\n            },\n          })\n          .then((blob) =\u003e {\n            const signedId = blob.signedId;\n\n            this.model.avatar = signedId;\n          });\n      }\n    }\n  }\n}\n```\n\n- `directUploadURL` is the path referencing `ActiveStorage::DirectUploadsController` on your Rails backend (or a custom one built on top of that).\n- The `uploadProgress` property will hold a value between 0 and 100 that you might use in your template to show upload progress.\n- After the `upload` promise is resolved and `signedId` is set in your model, when a `model.save()` is triggered, the Rails backend will use such `signedId` to associate an `ActiveStorage::Attachment` record to your backend model's record.\n\n\n### Events\n\n`loadstart`, `load`, `loadend`, `error`, `abort`, `timeout` events invokes `onLoadstart`, `onLoad`, `onLoadend`, `onError`, `onAbort`, `onTimeout` accordingly. For example; If you want to use the `loadend` event in your app, you can use like;\n\n```javascript\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tracking';\nimport { inject as service } from '@ember/service';\n\nexport default class UploadComponent extends Component {\n  @service\n  activeStorage;\n\n  @tracked\n  uploadProgress = 0;\n\n  @action\n  upload(event) {\n    const files = event.target.files;\n    if (files) {\n      const directUploadURL = '/rails/active_storage/direct_uploads';\n\n      for (var i = 0; i \u003c files.length; i++) {\n        this.activeStorage\n          .upload(files.item(i), directUploadURL, {\n            onProgress: (progress, event) =\u003e {\n              this.uploadProgress = progress;\n            },\n            onLoadend: (event) =\u003e {\n              debug(`Event captured ${event}`); // https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent\n            },\n          })\n          .then((blob) =\u003e {\n            const signedId = blob.signedId;\n\n            this.model.avatar = signedId;\n          });\n      }\n    }\n  }\n}\n```\n\n\n### XHR object\n\nIf you need the actual `XHR object` in your app, you can use the `onXHROpened` event. It returns the `XHR object` reference. For example:\n\n```javascript\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tracking';\nimport { inject as service } from '@ember/service';\n\nexport default class UploadComponent extends Component {\n  @service\n  activeStorage;\n\n  @tracked\n  uploadProgress = 0;\n\n  @tracked\n  xhrs = [];\n\n  @action\n  upload(event) {\n    const files = event.target.files;\n    if (files) {\n      const directUploadURL = '/rails/active_storage/direct_uploads';\n\n      for (var i = 0; i \u003c files.length; i++) {\n        this.activeStorage\n          .upload(files.item(i), directUploadURL, {\n            onProgress: (progress, event) =\u003e {\n              this.uploadProgress = progress;\n            },\n            onXHROpened: (xhr) =\u003e {\n              this.xhrs.push(xhr);  // so you can loop over this.xhrs and invoke abort()\n            },\n          })\n          .then((blob) =\u003e {\n            const signedId = blob.signedId;\n\n            this.model.avatar = signedId;\n          });\n      }\n    }\n  }\n}\n```\n\n\n### Metadata\n\nActiveStorage supports metadata for direct uploads. That is a nice way to provide extra information to the rails app.\n\n```ruby\nclass DirectUploadsController \u003c ActiveStorage::DirectUploadsController\n  def create\n    # blob_args[:metadata]['additional_type']\n    # =\u003e my_type\n    blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)\n    render json: direct_upload_json(blob)\n  end\nend\n```\n\n```javascript\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\nimport { inject as service } from '@ember/service';\n\nexport default class UploadComponent extends Component {\n  @service\n  activeStorage;\n\n  @action\n  upload(event) {\n    const files = event.target.files;\n    if (files) {\n      const directUploadURL = '/rails/active_storage/direct_uploads';\n\n      for (var i = 0; i \u003c files.length; i++) {\n        this.activeStorage\n          .upload(files.item(i), directUploadURL, {\n            metadata: {\n              additional_type: 'my_type'\n            },\n          })\n          .then((blob) =\u003e {\n            const signedId = blob.signedId;\n\n            this.model.avatar = signedId;\n          });\n      }\n    }\n  }\n}\n```\n\n\n### Configuration\n\nThere is an `ember-active-storage` ENV config with only one parameter called `url`. With this config help, you can omit the upload url now. For example:\n\n```javascript\nENV['ember-active-storage'] = {\n  url: 'http://your-domain/rails/active_storage/direct_uploads',\n};\n```\n\nNow you can call the upload function without the upload url.\n\n```javascript\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tracking';\nimport { inject as service } from '@ember/service';\n\nexport default class UploadComponent extends Component {\n  @service\n  activeStorage;\n\n  @tracked\n  uploadProgress = 0;\n\n  @action\n  upload(event) {\n    const files = event.target.files;\n    if (files) {\n      for (var i = 0; i \u003c files.length; i++) {\n        this.activeStorage\n          .upload(files.item(i), {\n            onProgress: (progress, event) =\u003e {\n              this.uploadProgress = progress;\n            },\n          })\n          .then((blob) =\u003e {\n            const signedId = blob.signedId;\n\n            this.model.avatar = signedId;\n          });\n      }\n    }\n  }\n}\n```\n\n### Sending authentication headers\n\nIt's pretty common that you want to protect with authentication the direct uploads endpoint on your Rails backend. If that's the case, the `activeStorage` service will need to send authentication headers together with the direct upload request.\n\nTo achieve that, you'll need to extend the `activeStorage` service provided by the addon and add a `headers` computed property. For example, if you're using [ember-simple-auth](/simplabs/ember-simple-auth), it will be a 2-steps process. First you'll need to define an `authenticatedHeaders` computed property in your `session` service, like this:\n\n```javascript\n// app/services/session.js\nimport Service from '@ember/service';\nimport { inject as service } from '@ember/service';\n\nexport default class MySessionService extends Service {\n  @service\n  session;\n\n  get authenticatedHeaders() {\n    const { access_token } = this.session.authenticated;\n\n    return { Authorization: `Bearer ${access_token}` };\n  }\n}\n```\n\nThen, you will alias that property in your `activeStorage` service, like this:\n\n```javascript\n// app/services/active-storage.js\nimport ActiveStorage from '@algonauti/ember-active-storage/services/active-storage';\nimport { inject as service } from '@ember/service';\n\nexport default class ActiveStorageService extends ActiveStorage {\n  @service('my-session')\n  session;\n\n  get headers() {\n    this.session.authenticatedHeaders;\n  }\n}\n```\n\nAlso note: if the download endpoint is protected as well, and you're using an ajax request to download files, then don't forget to include the same headers in that request as well.\n\n## Contributing\n\n### Installation\n\n- `git clone \u003crepository-url\u003e`\n- `cd ember-active-storage`\n- `yarn install`\n\n### Linting\n\n- `yarn lint:js`\n- `yarn lint:js --fix`\n\n### Running tests\n\n- `ember test` – Runs the test suite on the current Ember version\n- `ember test --server` – Runs the test suite in \"watch mode\"\n- `yarn test` – Runs `ember try:each` to test your addon against multiple Ember versions\n\n### Running the dummy application\n\n- `ember serve`\n- Visit the dummy application at [http://localhost:4200](http://localhost:4200).\n\nFor more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falgonauti%2Fember-active-storage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falgonauti%2Fember-active-storage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falgonauti%2Fember-active-storage/lists"}