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 · 24 comments
Closed

ActiveStorage with Rails API #32500

NicholasLYang opened this issue Apr 9, 2018 · 24 comments

Comments

@NicholasLYang
Copy link

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
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
Copy link
Contributor

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.

@NicholasLYang
Copy link
Author

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
Copy link

@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
Copy link
Contributor

@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
Copy link

@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
Copy link

@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
Copy link
Contributor

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
Copy link
Contributor

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
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
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
Copy link

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

@belgoros
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
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.

@scarroll32
Copy link

scarroll32 commented Nov 17, 2018

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

@codeholic
Copy link

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
Copy link

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

@martinstreicher
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
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

@Cperbony
Copy link

Cperbony commented May 1, 2019

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

Thats it. thks

@IvanDreamer
Copy link

I ended up with following helper.

module AvatarVariantsHelper
  include Rails.application.routes.url_helpers

  def avatar_50x50
    if avatar.attached?
      avatar.variant(resize_to_limit: [100, 100]).processed
    else
      #MAYBE SOME FALLBACK
    end
  end

  def avatar_50x50_path
    rails_representation_path(avatar_50x50) if avatar.attached?
  end

  ## for some reason without it it gives following error:
  ##NameError (undefined local variable or method `default_url_options' for #<User:0x00007f8282fd9718>)
  ##Did you mean?  default_url_options=
  ##               @@default_url_options
  def default_url_options
    {}
  end
end

@ghost
Copy link

ghost commented May 11, 2020

If you want to use url_for with fast_jsonapi serializer
Hope it will work for you add this in development.rb
Rails.application.routes.default_url_options[:host] = 'localhost:3000'

And in your serializer:
Rails.application.routes.url_helpers.url_for(object.image)

@yopaz-giapnh
Copy link

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

When I use Amazon S3 as a storage service in production env, what is the Current.host I have to set?

@Frederik-Baetens
Copy link

Should rails_representation_url(variant) also work when using activestorage in proxy mode? I'm attempting this, but am still getting redirected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests