Step through each model inside a rake task

Posted by bitbutter on September 17, 2007

Sometimes you need to run a rake task that needs to 'know' about all the models in your application.

Here's one such situation that I've run into a couple of times. You've installed (or created) an 'act_as' type plugin that automatically creates a record in another table (eg. 'feed_items') when a new instance of the model it's been applied to (eg. 'post') is created. But because the plugin was added after data had been added to the db you now need to go back and make sure all the Post records have a corresponding record in the feed_items table. If a Post item is found without a FeedItem then one needs to be created.

Rake is good choice for creating this kind of synchronising task that you might need to run more than once. If you don't already have one, create a file called myapp.rake (substituting your app name of course) in myapp/lib/tasks.

Here's the skeleton I use when building tasks like this. Paste this into your myapp.rake file and save it.

desc "Iterates through models in the app and does something with each of them"
task :do_something_with_models => :environment do
  require 'find'
  models=[]
  path="#{RAILS_ROOT}/app/models"
  Find.find(path) do |p|
    if FileTest.directory?(p) and p!=path
      Find.prune # don't recurse into directories
      next
    end
    if p =~ /.rb$/
      puts p
      models << File.basename(p,".*").classify
    end
  end
  models.each do |m|
    puts m
    # Insert code here to do something useful with each model.
    # You can retrieve a collection from the model like this
    # eval("collection=#{m}.find(:all)") …
  end
end

To run this task, cd to the root directory of your application and type

rake do_something_with_models

If all is well you should see a readout of the detected models. Now you can insert code into the models.each loop to do something useful for your application.

N.B This method will only 'see' the models which have corresponding .rb files in your models directory, so models that get created by plugins won't be detected.

Here's a task from a real application that attaches feed items to existing records that should have them. I'm sure it could be improved and optimised, I'm adding it here in case it's useful for others to build on.

desc "Creates feed items for records that might be missing them"
task :create_missing_feed_items => :environment do
  orphan_counter=0
  fixed_counter=0
  require 'find'
  models=[]
  path="#{RAILS_ROOT}/app/models"
  Find.find(path) do |p|
    if FileTest.directory?(p) and p!=path
      Find.prune # dont recurse into directories
      next
    end
    if p =~ /.rb$/
      models << File.basename(p,".*").classify
    end
  end
  models.each do |m|
    c=eval("#{m}")
    if c.respond_to?("new") # is the class an active rcord class that can be instantiated?
      n=c.new
      if n.respond_to?("feed_item") # has the acts_as_feedable plugin been applied to this model?
        c.find(:all,:include=>:feed_item).each do |o|
          if o.feed_item.nil?
            orphan_counter=orphan_counter+1
            if f=FeedItem.attach_to_existing_feedable(o) # try to create a feed item
              puts "#{m} id:#{o.id} no feed_item found, I'm creating one."
              fixed_counter=fixed_counter+1
            else
              puts "#{m} id:#{o.id} no feed_item found, but I failed to create one!"
              puts o.to_yaml
            end
          end
        end
      end
    end
  end
  puts "#{orphan_counter} records were found needing feed items."
  puts "#{fixed_counter} feed items were added to existing records."
end
Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

Comments