Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActiveStorage with Rails API #32500

Closed
NicholasLYang opened this Issue Apr 9, 2018 · 19 comments

Comments

Projects
None yet
@NicholasLYang
Copy link

NicholasLYang commented Apr 9, 2018

I'm basically trying to get ActiveStorage working with Rails API. The main issue I have is getting a URL from an Attachment object. url_for doesn't work outside of Rails views, so I cannot get the url from that. In addition, I'm not quite clear on creating an article with an image. The following code when run in the Rails console does not create an attachment:

School.create(name: "New York University", image: File.open(Rails.root + "db/sample.jpg"))

Instead, it simply gives the following:

(0.1ms)  begin transaction
  School Create (0.8ms)  INSERT INTO "schools" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "New York University"], ["created_at", "2018-04-09 03:36:54.304927"], ["updated_at", "2018-04-09 03:36:54.304927"]]
   (2.8ms)  commit transaction
=> #<School id: 4, name: "New York University", created_at: "2018-04-09 03:36:54", updated_at: "2018-04-09 03:36:54">

Steps to reproduce

Try to run url_for in the Rails console.

Expected behavior

I get the url. Granted, I get that this is not the purpose of url_for (it's a view helper, not a method). But I'd like some equivalent, ideally a method in the ActiveStorage::Attached object.

Actual behavior

url_for gives the following error: (undefined method 'url_for' for main:Object). Even when I include the Rails view helpers, I get the following:

ArgumentError (Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true)

I could try adding a host to the ActiveStorage::Attached object, but that's sorta hackneyed as splats don't work with it, and I can't mutate it easily.

System configuration

Rails version:
5.2.0.rc2
Ruby version:
ruby 2.5.0p0

Thank you for your time. I understand that ActiveStorage is not fully stable or released yet, but it looked too damn cool to not use 😄 . I'd be open to contributing/opening a PR if someone could point me in the right direction.

@saurabhbhatia

This comment has been minimized.

Copy link

saurabhbhatia commented Apr 9, 2018

Hi @NicholasLYang I've been using @model.attachment.service_url in my serializers to get the path of the image, but it has it's limitations. service_url is generated fresh with every request. I would also like to know a better solution( if there is) for getting the image display url that we can pass through our serializers to the frontend.

@pixeltrix

This comment has been minimized.

Copy link
Member

pixeltrix commented Apr 9, 2018

Active Storage provides rails_blob_path and rails_blob_url url helpers that provide a permanent link to a blob. These permanent links are signed and will redirect to a service url so for example the S3 service url will be a presigned temporary url. Note that the urls are public and if you need to make them protected you'll need to create your own version of ActiveStorage::BlobsController.

For further information I suggest reading the beta guide for Active Storage.

@pixeltrix pixeltrix closed this Apr 9, 2018

@NicholasLYang

This comment has been minimized.

Copy link
Author

NicholasLYang commented Apr 10, 2018

Hey @pixeltrix, I appreciate the response, but I'm still a little confused on link generation. For instance, if I'm creating a variant, I get the following error: NoMethodError (undefined method 'signed_id' for #<ActiveStorage::Variant:0x00007fb73590b5e8>), which appears to be due to the fact that variants are not the same as attachments. However, this seems rather awkward, as I can't use the same helper for variants and attachments (even though imo the interface should be pretty similar if not identical). Furthermore, I just bumped my Rails version to 5.2.0 and now the solution I was using for variants, @model.attachment.variant(resize: "100x100>").processed.service_url, gives a host error. Could you clarify the link generation process for variants vs attachments and why they're different? I get that variants need to be generated lazily but I'm not quite clear on the entire picture.

@saurabhbhatia

This comment has been minimized.

Copy link

saurabhbhatia commented Apr 10, 2018

@NicholasLYang this is what is working for me ( I upgraded to Rails 5.2 this morning from RC2 ).

class API::V1::ProfileSerializer < ActiveModel::Serializer
  include Rails.application.routes.url_helpers
  attributes :id, :name, :avatar_url

  def avatar_url
    rails_blob_path(object.avatar) if object.avatar.attachment
  end
end

This is how my development.rb looks like

  config.action_mailer.default_url_options = { host: 'test.com' }
  Rails.application.routes.default_url_options[:host] = 'test.com'
@pixeltrix

This comment has been minimized.

Copy link
Member

pixeltrix commented Apr 10, 2018

@NicholasLYang for a variant you need to use rails_representation_url(variant) - this will build a url similar to the one that rails_blob_url builds but specifically for that variant. Don't use service_url directly within things like model serializers as it's generally a temporary url. The idea is to use permanent urls that are independent of the service that redirect to the temporary service url so that you can migrate to a different service without affecting end users.

@PrimeTimeTran

This comment has been minimized.

Copy link

PrimeTimeTran commented May 30, 2018

@pixeltrix thanks for the work you've put in so far. Anyway you guys could add documentation to the guides for working with AWS? I'm having the same error message when I use ActiveModelSerializers and AWS S3 to host the media files.

@PrimeTimeTran

This comment has been minimized.

Copy link

PrimeTimeTran commented May 30, 2018

@saurabhbhatia are you hosting your images on the same server? I keep getting the same error message and I think it has to do with using AWS instead of saving the images to the application

@philiplambok

This comment has been minimized.

Copy link

philiplambok commented Jun 11, 2018

I have the same problem too,

When im using service_url and processed.service_url (in variant), its working in production but error in development. It's still error when i add code in config.

# config/development.rb
config.active_storage.service = :local
config.default_url_options = { host: "localhost:3000" }
config.action_mailer.default_url_options = { :host => "localhost:3000" }
#config/production.rb
config.active_storage.service = :amazon
config.action_mailer.default_url_options = { host: 'https://kappuchino.herokuapp.com' }
config.default_url_options = { host: "https://kappuchino.herokuapp.com" }

And if i am use rails_blob_url its working in development, but in production it doesn't work its return link localhost.

@philiplambok

This comment has been minimized.

Copy link

philiplambok commented Jun 11, 2018

Oh i just fixed the problem,

i'm using method rails_blob_path and rails_representation_path(for variant). rails_blob_url and rails_representation_url don't work for me.

@LuisHCK

This comment has been minimized.

Copy link

LuisHCK commented Aug 8, 2018

if you are using fast_jsonapi serializer this may work

attribute :cover do |object| 
  Rails.application.routes.url_helpers.rails_blob_url(object.cover) if object.cover.attached?
end

and add this in any initializer
Rails.application.routes.default_url_options[:host] = 'localhost:3000'

@madmatvey

This comment has been minimized.

Copy link

madmatvey commented Aug 17, 2018

if you need service_url only method, you can write callback at your controller to handle service_url method at development and test environments.

class Api::V1::YourPerfectApiController < ApplicationController
    before_action :set_host_for_local_storage
    ...
    private
     def set_host_for_local_storage
        ActiveStorage::Current.host = request.base_url if Rails.application.config.active_storage.service == :local
    end
end

see ActiveStorage controllers concerns

@TienNT1993

This comment has been minimized.

Copy link

TienNT1993 commented Sep 13, 2018

@madmatvey Thank you so much, it made my day!

@belgoros

This comment has been minimized.

Copy link

belgoros commented Nov 1, 2018

When using the settings suggested by @philiplambok, it fails as follows:

Completed 500 Internal Server Error in 277ms (ActiveRecord: 8.7ms)
ArgumentError (Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true):

rails: 5.2.0

In development.rb:

...
config.active_storage.service = :local
config.default_url_options = { host: "localhost:3000" }

What am I missing ?

@belgoros

This comment has been minimized.

Copy link

belgoros commented Nov 1, 2018

Finally the option that worked for me was:

#development.rb

Rails.application.routes.default_url_options[:host] = 'localhost:3000'

And in my serialiser I used:

#post_serializer.rb
class PostSerializer < ActiveModel::Serializer
  include Rails.application.routes.url_helpers

  attributes :id, :title, :body, :tag_ids, :archived, :photo

  def photo
    rails_blob_url(object.photo) if object.photo.attached?
  end
end

The code source of the app is here.

@seanfcarroll

This comment has been minimized.

Copy link

seanfcarroll commented Nov 17, 2018

Thanks for the code @ belgoros , the link is dead. Maybe it is a private repo?

@codeholic

This comment has been minimized.

Copy link

codeholic commented Nov 26, 2018

The following worked the best for me:

#concerns/set_current_host_for_disk_service.rb
module Concerns
  module SetCurrentHostForDiskService
    extend ActiveSupport::Concern

    included do
      before_action :set_current_host_for_disk_service, if: -> { ActiveStorage::Blob.service.is_a?(ActiveStorage::Service::DiskService) }
    end

    private

    def set_current_host_for_disk_service
      ActiveStorage::Current.host = request.base_url
    end
  end
end
#application_controller.rb
class ApplicationController < ActionController::Base
  include Concerns::SetCurrentHostForDiskService
  ...
end

P.S. There is a concern on master that will implement a similar concern for you.

@belgoros

This comment has been minimized.

Copy link

belgoros commented Nov 26, 2018

@seanfcarroll Oups, my bad, I've merged the branch, everything is on master now :)

@martinstreicher

This comment has been minimized.

Copy link

martinstreicher commented Feb 12, 2019

One additional question... If my application is multi-tenant and I want to serialize a model with an image in AS, can I use ActiveStorage::Current.host = my_subdomain_and_domain(request) to set the host for each request? my_subdomain_and_domain(request) would yield something like abc.lvh.me.

I assume setting ActiveStorage::Current.host addresses this error: ArgumentError (Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true)

@phlegx

This comment has been minimized.

Copy link

phlegx commented Apr 8, 2019

Hi!

I have used this config and it works for me (Rails 5.2.3). It works for url_helpers and therefore also for ActiveStorage and my Serializer.

# config/routes.rb
Rails.application.routes.draw do
  default_url_options protocol: Rails.env.development? ? :http : :https
  ...
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.