Step through each model inside a rake task
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
models=[]
path="/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
models=[]
path="/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("")
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 " id: no feed_item found, I'm creating one."
fixed_counter=fixed_counter+1
else
puts " id: no feed_item found, but I failed to create one!"
puts o.to_yaml
end
end
end
end
end
end
puts " records were found needing feed items."
puts " feed items were added to existing records."
end
Trackbacks
Use this link to trackback from your own site.
