Anton Ipatov
Anton Ipatov

Reputation: 23

How to migrate images to new field in Rails?

I use Ruby on Rails 5.2.3, Mongoid, Attachinary and Cloudinary for images.

class User
  include Mongoid::Document

  has_attachment :image, accept: [:jpg, :png, :gif]

  field :pic, type: String

  before_update :migrate_images

  def migrate_images
    self.image_url = self.pic
  end
end

Images are saved in pic field as links. Now I use this code, the problem is that this takes a very long time and not all images are saved.

User.where(:pic.exists => true).all.each &:update

log

irb(main):001:0> User.where(:pic.exists => true).all.each &:update
=> #<Mongoid::Contextual::Mongo:0x00007ffe5a3f98e0 @cache=nil, @klass=User, @criteria=#<Mongoid::Criteria
  selector: {"pic"=>{"$exists"=>true}}
  options:  {}
  class:    User
  embedded: false>
, @collection=#<Mongo::Collection:0x70365213493680 namespace=link_development.users>, @view=#<Mongo::Collection::View:0x70365213493380 namespace='link_development.users' @filter={"pic"=>{"$exists"=>true}} @options={"session"=>nil}>, @cache_loaded=true>

Upvotes: 0

Views: 573

Answers (1)

Schwern
Schwern

Reputation: 165366

User.where(:pic.exists => true).all.each &:update

This is slow because .all.each loads all matching Users into memory, find_each is a bit more efficient on memory as it will load in batches, but it's still a waste of time and memory to load each object into memory and turn it into an object to copy one attribute. Then it runs an update on each individual one.

Instead, you can do this entirely in the database in a single query.


If the intent is to copy from User.pic to User.image_url, you can do this in a single statement.

# Find all the users who do not already have an image_url set
User.where(image_url: nil)
    # Set their image_url to be their pic.
    .update_all("image_url = pic")

This will run a single query:

update users
  set image_url = pic
  where image_url is null

There's no need to also check for users who lack a pic because there's no harm in setting nil to nil, and a simpler search might be faster. But if you like check you can use where.not. Users.where(image_url: nil).where.not(pic: nil)

Upvotes: 1

Related Questions