Re: Specifying a second S3 bucket without defining a new Shrine uploader class

372 views
Skip to first unread message
Message has been deleted

Janko Marohnić

unread,
Sep 19, 2017, 3:23:05 AM9/19/17
to Joshua Gorchov, Shrine
I'm assuming that by "two different places" you meant two different models. In that case you can declare the two S3 storages and then override them on Shrine::Attachment.new:

Shrine.storages = {
  cache: ...,
  store1: Shrine::Storage::S3.new(...),
  store2: Shrine::Storage::S3.new(...),
}

class MyUploader < Shrine
  # ...
end

class Model1
  include MyUploader::Attachment.new(:attachment, store: :store1)
end

class Model2
  include MyUploader::Attachment.new(:attachment, store: :store2)
end

Alternatively, you can use the default_storage plugin to configure the storage dynamically inside the uploader:

class MyUploader < Shrine
  plugin :default_storage, store: -> (record, name) {
    if record.is_a?(Model1)
    end
  }
end

On Mon, Sep 18, 2017 at 11:13 PM, Joshua Gorchov <joshua...@gmail.com> wrote:
I have a rails app where you can upload images in two different places. Everything is the same between both uploads but they need to go to different S3 buckets. Since the bucket is defined in the Shrine uploader model, do I have to create a new Shrine uploader model for the page with the different bucket? Or is there a way to use the same Shrine uploader model and specify a different bucket?

--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/e8f6326a-32c1-4af4-bd17-192b4502efe8%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Janko Marohnić

unread,
Sep 19, 2017, 3:25:07 AM9/19/17
to Joshua Gorchov, Shrine
...

class MyUploader < Shrine
  plugin :default_storage, store: -> (record, name) {
    if record.is_a?(Model1)
      :store1
    elsif record.is_a?(Model2)
      :store2
    end
  }
end

If you're using the Shrine::Attacher directly, you can also override the storage on initializing:

Shrine::Attacher.new(record, name, store: store1)
# ...
Shrine::Attacher.new(record, name, store: store2)
# ...

Kind regards,
Janko
Message has been deleted

Janko Marohnić

unread,
Sep 19, 2017, 4:18:04 PM9/19/17
to Joshua Gorchov, Shrine
This feature was added in the latest Shrine version – 2.7.0 – so you'll need to upgrade to it first.

On Tue, Sep 19, 2017 at 9:44 PM, Joshua Gorchov <joshua...@gmail.com> wrote:
This looks like a great solution. And you are correct, I am trying to upload two different modles into separate folders(?) within the same bucket and they can share the same cache. But when I try to implement your first suggestion, I get an error that I don't understand:

wrong number of arguments (given 2, expected 1)
include OutsideImageUploader::Attachment.new(:image, store: :store2)

I'll include the relevant code here:

require "shrine/storage/s3"


if Rails.env.test?
 
require "shrine/storage/file_system"


 
Shrine.storages = {
    cache
: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
    store
: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
 
}
else  
  s3_options
= {
    access_key_id
:     ENV['AWS_ACCESS_KEY_ID'],
    secret_access_key
: ENV['AWS_SECRET_ACCESS_KEY'],
    region
:            ENV['AWS_REGION'],
    bucket
:            ENV['S3_BUCKET_NAME'],
 
}

 
Shrine.storages = {
    cache
: Shrine::Storage::S3.new(prefix: "cache", upload_options: {acl: "public-read"}, **s3_options),
    store1
: Shrine::Storage::S3.new(prefix: "outside_images", upload_options: {acl: "public-read"}, **s3_options),
    store2
: Shrine::Storage::S3.new(prefix: "images", upload_options: {acl: "public-read"}, **s3_options),
 
}
end


Shrine.plugin :activerecord
Shrine.plugin :direct_upload
Shrine.plugin :restore_cached_data # for metadata
Shrine.plugin :determine_mime_type

class Image < ApplicationRecord
  include
OutsideImageUploader::Attachment.new(:image, store: :store2)

class OutsideImage < ApplicationRecord
  include
OutsideImageUploader::Attachment.new(:outside_image, store: :store1)

To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine...@googlegroups.com.

To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/e8f6326a-32c1-4af4-bd17-192b4502efe8%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
Message has been deleted
Message has been deleted

Janko Marohnić

unread,
Sep 20, 2017, 1:12:53 PM9/20/17
to Joshua Gorchov, Shrine
Yes, that's unfortunately possible in Rails, because the code loading order isn't strictly defined due to autoloading; it surprised many people already. It can happen if another initializer references a model which has the attachment, because then that model will autoload the Shrine uploader, which will then be instantiated with a blank copy of `Shrine.storages`, so when the Shrine initializer later gets loaded and modifies `Shrine.storages`, it's too late because the uploader already took the copy that was available.

There isn't really anything I can do about it in Shrine, because the only thing I can do is to break inheritance regarding storages, where instead of each uploader receiving a copy of `Shrine.storages`, they rather all share the same `Shrine.storages`, and that's something I don't want to do (for one because it would break documented usage of shrine-tus).

People have worked around this by renaming `config/initializers/shrine.rb` into `config/initializers/01_shrine.rb`, to force it to load before any other initializers. But I think it's better to prevent autoloading models during initialization in the first place, by not referencing them from initializers.

Kind regards,
Janko

On Wed, Sep 20, 2017 at 6:59 PM, Joshua Gorchov <joshua...@gmail.com> wrote:
Is it possible that my models are getting loaded before the shrine initializer?
No matter how I supply the second argument to Attachment.new I get an error that the storage isn't registered with the Uploader

  Shrine.storages = {
    cache: Shrine::Storage::S3.new(prefix: "cache", upload_options: {acl: "public-read"}, **s3_options),
    store1: Shrine::Storage::S3.new(prefix: "outside_images", upload_options: {acl: "public-read"}, **s3_options),
    image: Shrine::Storage::S3.new(prefix: "images", upload_options: {acl: "public-read"}, **s3_options),
  }

  class Image < ApplicationRecord
   
include OutsideImageUploader::Attachment.new(:image, store: :image)

error:

  storage :image isn't registered on OutsideImageUploader


On Tuesday, September 19, 2017 at 10:26:53 PM UTC-7, Joshua Gorchov wrote:
Thank you Janko. I updated shrine in my Gemfile, ran bundle and restarted the server. All of my code is the same as above. Now I'm getting this error:

storage "store" isn't registered on OutsideImageUploader
Extracted source (around line #1):
<%= image_tag image.image_url(public: true), class: 'img-responsive' %>

--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
Message has been deleted

Janko Marohnić

unread,
Sep 20, 2017, 1:45:14 PM9/20/17
to Joshua Gorchov, Shrine
So is it possible to have uploads from one model to go into a folder within a S3 bucket, and uploads from another model go into a different folder within the same bucket?

Yes, I thought we already discussed three possible solutions for that. The last error you're getting is a problem with the Rails framework, not Shrine, and I already mentioned a workaround.

It is odd though that you're getting an error for the permanent storage key, I would expect you to get the error that the "cache" storage is missing first. Can you add a

  p Shrine.storages

statement on the top of your uploader file so that we see what storages it has.

In any case, if you somehow don't manage to fix it, you can always use the same S3 storage for both models that doesn't have any `:prefix` configured, and instead override #generate_location to put files in different locations depending on the record:

class MyUploader < Shrine
  def generate_location(io, context)
    if storage_key == :store
      case context[:record]
      when Image        then "images/#{super}"
      when OutsideImage then "outside_images/#{super}"
      else super
      end
    else
      super
    end
  end
end

Kind regards,
Janko

On Wed, Sep 20, 2017 at 7:32 PM, Joshua Gorchov <joshua...@gmail.com> wrote:
So is it possible to have uploads from one model to go into a folder within a S3 bucket,
and uploads from another model go into a different folder within the same bucket?

--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
Reply all
Reply to author
Forward
Message has been deleted
0 new messages