Search code examples
ruby-on-railsrubyruby-on-rails-4rakerake-task

How to know that a rake task has been run in rails?


I've a migration in my Rails app, that I'd like to run only if a particular rake task has been run, otherwise I'll lose a bunch of data. Following is something that I'd like to do:

if has_rake_task_been_run?
  remove_column :transactions, :paid_by
end

Currently, I couldn't find anyway, instead of assuring this thing manually. Is there any work around for it?


Solution

  • Using rake task for data migration is an extremely risky idea. Couple of reasons not to do this:

    1. Even if you manage to find out whether your rake task has finished or not, your migration will still be marked as completed and you won't be able to replay it. Only way around is raising an exception in your migration.

      No, you won't be able to rollback that migration neither. If rake task finishes after the migration has run, rollback will try to add already existing column.

    2. Setting up your database from scratch by new devs will become painful as hell, as they will need to know which rake tasks are to be run when. Not to mentioned that rake db:migrate executes all migrations.

    3. You're polluting your rake task list with non-reusable tasks

    It seems that what you're doing is just a regular data migration, so all the stuff done by your rake task should be in fact part of your migration. That will even allow you to make a reversible data migration (in majority of cases).

    Note however that data migrations are not that simple as regular scheme-only migrations. Because your migration should be completely independent on your code (as they are to work in the future, even when migrated model is completely removed from your codebase), so it is a common practice to redefine the models you are about to use in your migrations (only the bits required fro the migration). This is not that simple as it sounds, unfortunately, and honestly I am still looking for a perfect solution to that. The best I've seen so far is simple (I'm assuming that paid_by used to be string and you changed it paid_by_id, which references the user):

    class YOURMIGRATIONNAME < ActiveRecord::Migration
      class Transaction < ActiveRecord::Base
        belongs_to :paid_by, class_name: "User"
      end
    
      class User < ActiveRecord::Base
      end
    
      def up
        add_column :transaction, :paid_by_id, :integer
    
        Transaction.transaction do # for speed
          Transaction.find_each do |t|
            t.paid_by_id = User.find_by(username: t[:paid_by])
            t.save!         # Always banged save in migration!
          end
        end
    
        remove_column :paid_by
      end
    
      def down
        add_column :transaction, :paid_by, :string
    
        Transactions.transaction do 
          Transaction.find_each do |t|
            t[:paid_by] = t.paid_by && t.paid_by.username
            t.save!
          end
        end
    
        remove_column :transactions, :paid_by_id
       
    end
    

    The only downfall of using the code above is that it won't work well if any of those models is using STI (I've made that mistake once, took a while to find out what's wrong). The work around is to define it outside of the migration class, but then those classes are available across all migrations and can be affected with your actual model code (especially in production when all the models are preloaded). In short, data migration with STI is something I am still looking into at the moment.