Seeds upgrade v2->v3 with vips

Hello Janko and friends,

In Rails + Shrine v2, in seeds while creating a gallery, I used to populate images for a specific gallery like this:

# seeds.rb
def pupulate_galleries(house)
  house.galleries.each do |g|
    rand(1..MAX_IMAGES).times do
      t = Tempfile.create(["image", ".jpg"], binmode: true)
      begin
        t.write File.binread("test/images/#{rand(1...30)}.jpg")
        t.flush 
        t.rewind
        binding.pry
        g.images.create!(
            # workaround for delete_raw plugin
            image:          t,
            user_id:        rand(1..@users_count),
            title:          Faker::Lorem.words(number: 2..5).join(' '),
            description:    Faker::Lorem.words(number: 5..15).join(' '),
            tags:           Faker::Lorem.words(number: 1..5).join(' ')
        )
        binding.pry
      ensure
        t.close
      end
    end
  end
end

# image_uploader.rb
class ImageUploader < Shrine
  plugin :remove_attachment
  plugin :pretty_location
  plugin :processing
  plugin :versions
  plugin :validation_helpers
  plugin :store_dimensions, analyzer: :ruby_vips

  process(:store) do |io, context|
    processed_file = io.download

    pipeline = ImageProcessing::Vips
                    .convert('webp')
                    .saver(strip: true)
                    .source(processed_file)

    thumbnail = pipeline.resize_to_limit!(480, 290, crop: :centre)
    medium    = pipeline.resize_to_limit!(600, nil)
    full      = pipeline.resize_to_limit!(1500, 1200)

    processed_file.close!

    { original: full, thumb: thumbnail, medium: medium }
  end
end

This worked.

However, it doesn’t work with Shrine 3 anymore as it will upload only JPEG image, will not do the VIPS conversion and will not create derivatives, which were previously automatically created with :versions plugin.

With the new Shrine version and my (see below) rewritten uploader, this will be inserted in the Images DB table:

{"id": "image/1/image/4b06b25cef47a965279a2debb52f299f.jpg", "storage": "store", "metadata": {"size": 76004, "width": 525, "height": 333, "filename": "image20191215-25358-5yj9lx.jpg", "mime_type": "image/jpeg"}}

I rewrote the uploader to the new syntax:

class ImageUploader < Shrine
  plugin :derivatives
  plugin :remove_attachment
  plugin :pretty_location
  plugin :validation_helpers
  plugin :store_dimensions, analyzer: :ruby_vips

  Attacher.derivatives_processor do |original|
    processed_file = ImageProcessing::Vips.source(original)

    chain = ImageProcessing::Vips
                .convert('webp')
                .saver(strip: true)
                .source(processed_file)

    {
        thumbnail: chain.resize_to_limit!(480, 290, crop: :centre),
        medium: chain.resize_to_limit!(600, nil),
        full: pipeline.resize_to_limit!(1500, 1200)
    }
  end
end

but previously specified seeds.rb does not create the derivatives.

I tried to play around with it in my seeds.rb by trying different approaches, e.g:

io = StringIO.new
io = Rails.root.join("test/images/#{rand(1...30)}.jpg").open("rb")
io.rewind
photo = Image.new
attacher = ImageUploader::Attacher.from_model(photo, :image)
attacher.attach(io)
attacher.process_derivatives

, but they all result in the same error:

ImageProcessing::Error: invalid source: #<ImageProcessing::Builder:0x0000564a75f3e380 @options={:source=>#<Tempfile:/tmp/shrine20191215-21300-bxtpwq.jpg>, :loader=>{}, :saver=>{}, :format=>nil, :operations=>[], :processor=>ImageProcessing::Vips::Processor}>

Would you be so kind to point me in the right direction on how to make seeds.rb work with the new Shrine version?

Thank you very much.

Rails: 6.0.1
shrine (3.1.0)
image_processing (1.9.3)
vips (via homebrew): vips: stable 8.8.4 (bottled)

A big difference between procesing + versions and derivatives plugin is that derivatives creation needs to be triggered manually by default.

So, your seeds.rb file would look something like this:

def pupulate_galleries(house)
  house.galleries.each do |g|
    rand(1..MAX_IMAGES).times do
        image = g.images.build(
          image:          File.open("test/images/#{rand(1...30)}.jpg", "rb"),
          user_id:        rand(1..@users_count),
          title:          Faker::Lorem.words(number: 2..5).join(' '),
          description:    Faker::Lorem.words(number: 5..15).join(' '),
          tags:           Faker::Lorem.words(number: 1..5).join(' ')
        )
        image.image_derivatives! # create derivatives
        image.save! # persist original file & derivatives data
    end
  end
end

Next, in the derivatives processor you’re initializing ImageProcessing::Vips.source(...) twice, passing the output of one into the input of other, which causes the error you’re seeing. You just need to pass the raw original file directly once:

  Attacher.derivatives_processor do |original|
    chain = ImageProcessing::Vips
                .convert('webp')
                .saver(strip: true)
                .source(original)

    {
        thumbnail: chain.resize_to_limit!(480, 290, crop: :centre),
        medium: chain.resize_to_limit!(600, nil),
        full: pipeline.resize_to_limit!(1500, 1200)
    }
  end

If you want derivatives to be created automatically with promotion (as it was with versions), and not have to call #image_derivatives! manually, you can put the following in your shrine.rb initializer:

class Shrine::Attacher
  def promote(*)
    create_derivatives
    super
  end
end

Thank you for your help Janko, your Shrine support is truly excellent. I completely overlooked that double initialization. Seeding works fine now and so does WEBP conversion.

All the best to you!

1 Like