Skip to content

How to upgrade to 1.0.0 or newer

eagsalazar edited this page Jan 14, 2013 · 5 revisions

There're several backward incompatible changes.

  1. Model.slug and Model.slug_history are replaced by Model._slugs, a single field of Array type. If you are migrating from an older version you need to copy all slugs from the Model.slug and Model.slug_history into a new field called _slugs. See the "Data Migration" section below.

  2. If you're using a block to define a slug in Mongoid 0.10, you must change it to return a slug. The older version would automatically convert it to a valid slug, which is no longer the case. For example

    slug :first, :last do |doc|
        [ doc.first, doc.last ].compact.join(" ")
    end

    becomes

    slug :first, :last do |doc|
        [ doc.first, doc.last ].compact.join(" ").to_url
    end

Data Migration

It is recommended to do the data migration in code. If you can take downtime, the process is pretty straightforward, copy the data. You can also do a slow migration without any production impact, described below.

Migration with Downtime

  1. Declare the _slugs array in any model that use Mongoid slug:

    field :_slugs, type: Array, default: []
  2. Copy the value from the slug field to _slugs. Also copy the value from the slug history array if you used slug history. The current slug should be the last entry in the _slugs array (e.g _slugs = ["old_slug", "current_slug"]). You can use dynamic fields for this before Mongoid slug is updated to 1.0:

    def copy_slug(e)
      slugs = []
      if e[:slug_history] != nil
        e[:slug_history].each do |h|
          slugs << h unless (h == nil || h == "")
        end
      end
      slugs << e[:slug]
      e[:_slugs] = slugs
      e.save
      p " - #{e[:_slugs]}"
    end
  3. Verify that the copying in the second step was successful and upgrade Mongoid slug.

Slow Migration

Important: please note that the following instructions do not support migrating slug history, which you're welcome to contribute.

Important: The slow migration is community contributed. Please report any issues with the v0.10.0-mongoid3-with-slugs branch in step #1 in the @dblock fork issue tracker.

  1. Upgrade to Mongoid 3.x and use a forked 0.10 implementation of Mongoid slug from https://github.com/dblock/mongoid-slug/tree/v0.10.0-mongoid3-with-slugs. Change your Gemfile reference to:

    gem "mongoid_slug", :git => "https://github.com/dblock/mongoid-slug.git", :branch => "v0.10.0-mongoid3-with-slugs"

The code in this fork will write both the slug and the _slugs fields when a document is updated. You can see the implementation here.

  1. Now that any new update creates a corresponding _slugs array, you can do the copy for any old record with a Rake task.

    namespace :db do
      namespace :slugs do
    
        desc "Migrate all model slugs."
        task :migrate => :environment do
          Dir[File.join(Rails.root, "app/models/**/*.rb")].each do |f|
            klass = File.basename(f, ".rb").camelize.constantize
            next unless klass.respond_to? :slug
            logger.info "Migrating #{klass.count} #{klass} instance(s) ..."
            count = 0
            klass.all.each do |instance|
              raise "missing slug: #{instance.inspect}" if instance.slug.blank?
              next unless instance[:_slugs].empty?
              klass.collection.where({ _id: instance.id }).update({ "$set" => { _slugs: [ instance.slug ] }})
              count += 1
            end
            logger.info " => #{count}" if count > 0
          end
        end
    
      end
    end
  2. Upgrade to the latest Mongoid slug 2.x.

    gem "mongoid_slug", "~> 2.0"
  3. Remove the old slug field. See below for a monkey patch that preserves and writes the slug value in case you have processes that rely on it.

    namespace :db do
      namespace :slugs do
    
        desc "Remove all obsolete model slugs."
        task :remove => :environment do
          Dir[File.join(Rails.root, "app/models/**/*.rb")].each do |f|
            klass = File.basename(f, ".rb").camelize.constantize
            next unless klass.respond_to? :slug
            slugs_count = klass.where({ slug: { "$exists" => 1 }}).count
            logger.info "Removing :slug in #{slugs_count} #{klass} instance(s) ..."
            klass.collection.indexes.drop rescue nil
            klass.create_indexes
            klass.collection.where({ slug: { "$exists" => 1 }}).update({ "$unset" => { slug: 1 } }, { multi: true })
          end
        end
    
      end
    end

Preserving Backward Compatibility

If you have processes that rely on the old slug value after data migration, you may choose to write it every time a slug changes. This can be achieved with a monkey patch.

module Mongoid
  module Slug

    alias_method :__build_slug, :build_slug

    def build_slug
      __build_slug
      self["slug"] = self.slug
      true
    end

  end
end

Alternatively:

Any reason why this, from the console, is a bad idea? No need to temporarily modify models or add fields.

User.all.each { |u| u._slugs = [u.attributes["slug"]] unless u.attributes["slug"].nil? ; u.save ; u.unset(:slug) unless u.attributes["slug"].nil? }