"Private" Rake tasks with the Rails environment - ruby-on-rails

I know I can have the following to call a list of sub-tasks and have each one utilize the Rails environment of my project:
task :main_task => [:sub_task1, :sub_task2] do
end
task :sub_task1 => :environment do
Model1.some_class_method
end
task :sub_task2 => :environment do
Model2.some_class_method
end
My questions are
Is there any way in :main_task to pass the :environment so that I don't have to explicitly put it in each sub-task?
Is there any way to make the sub-tasks be considered "private"? That is, I don't want them to be explicitly called individually. They would only ever execute from :main_task. Basically I need to read data out of one database (SQLServer) and populate another (MySQL - the Rails project's db), but I want to keep the "read" task separate from the "populate" task for good readability.

You can list the :environment task once in the parent task before the other two to have it listed only once.
task :main_task => [:environment, :sub_task1, :sub_task2] do
end
There are no "private" tasks, however you can keep them from being listed by rake -T by not putting a desc line above them. You could enforce them manually by throwing an exception if they are called directly (detecting something the parent does, or some such).
However, you would probably have a better time putting the code in a shared method or class which is not directly exposed as a rake task.

Related

Define a custom rake task for each of the existing rake tasks

I need to create a custom task for each of the existing task I have. For example in /lib/tasks I have two files with tasks: update.rake,service.rake, etc. I have several tasks in each of these files. I have for example: rake update:users. Now I want to have rake custom_behaviour: for each of the tasks I have defined for example: rake custom_behaviour:update:users. I found the following code:
Rake::Task.tasks.each do |task|
task "custom_behaviour:#{task.name}" do
puts "Custom Behaviour Task: rake #{task.name}"
end
end
And this works properly if the file where I'm storing this code starts with "z", this is because it is the last file to be executed, but if I name the file as custom_behaviour.rake all the tasks defined in the files that are after the "c" are not "custom_behavioured" because the tasks are not loaded yet...
So my question is: What's the proper way of doing this? Where should I put the "custom_behaviour" code so when it's executed "all the tasks" are loaded?
I'd use the filename convention. This happens with other stuff that's order dependent, like Rails initializers.
The alternate that might allow you to accomplish what you need (I'm assuming you're looking have some custom behavior before/after the standard task) with is rake_hooks: https://github.com/guillermo/rake-hooks

Custom rake task can't access ActiveRecord data immediately after rake db tasks?

I have a batch of rake tasks that run sequentially:
task :batch_tasks => :environment do
Rake::Task["db:drop"].execute
Rake::Task["db:create"].execute
Rake::Task["db:migrate"].execute
Rake::Task["db:seed"].execute
Rake::Task["db:test:prepare"].execute
Rake::Task["custom_task_1"].execute
end
Here's what's in custom_task_1:
task :custom_task_1 => :environment do
puts "begin custom task"
orders = Order.all #three records
orders.each do |order|
puts "Do something to Order\n"
end
puts "end custom task"
end
When I run the above batch process, here's what happens:
rake batch_tasks
begin custom task
end custom task
But if I run the custom task AFTER the batch process, here's what happens:
rake custom_task_1
begin custom task
Do something to Order
Do something to Order
Do something to Order
end custom task
One thing to note, when I run debugger on rake batch_tasks with a breakpoint after rake db:seed, a check on eval Order.all returns an empty array []. However, Order.all does have data immediately after all of the rake tasks are finished.
What am I missing about rake db:seed and having access to ActiveRecord data in the next task called?
As mu is too short suggested, this is related to the need to reload models before using them in migrations. The reason for this is that all of your rake tasks run in a common environment, ie, it only loads rails once. As such, the table definitions may already be established. You will probably have to use reset_column_information to load the new values.
Alternately, the sequence of tasks you are doing look like they should be run independently, which could be a good use case for capistrano or thor.
So the quick fix was to move the db:test:prepare line to the end of the batch.
I believe the problem stemmed from the environment being switched from development to test and then the custom task was running in the last environment, which then had an empty test database.
The db:seed command probably switches the environment back to dev and the custom task runs against the correct database.
task :batch_tasks => :environment do
Rake::Task["db:drop"].execute
Rake::Task["db:create"].execute
Rake::Task["db:migrate"].execute
Rake::Task["db:seed"].execute
Rake::Task["custom_task_1"].execute
Rake::Task["db:test:prepare"].execute # <-- Moved after all other tasks
end

What's the 'environment' task in Rake?

According to "Custom Rake Tasks":
desc "Pick a random user as the winner"
task :winner => :environment do
puts "Winner: #{pick(User).name}"
end
As far as I know, the :winner => :environment means "do environment before winner". But what's environment? When should I use it?
I tried rake -T, but in the list I couldn't find environment.
You can get access to your models, and in fact, your whole environment by making tasks dependent on the environment task. This lets you do things like run rake RAILS_ENV=staging db:migrate.
See "Custom Rake Tasks".
It loads in your Rails environment so you can actually use your models and what not. Otherwise, it has no idea about those things.
So if you made a task that just did puts "HI!" then you don't need to add the :environment task to the dependencies. But if you wish to do something like User.find(1) well that will need it.
Including => :environment will tell Rake to load full the application environment, giving the relevant task access to things like classes, helpers, etc. Without the :environment, you won't have access to any of those extras.
Also => :environment itself does not make available any environment-related variables, e.g. environment, #environment, RAILS_ENV, etc.

Execute commands from external file in Rails

Is there an easy way to have a rails action load up an external file of commands and then execute them?
For example, I'm trying to write a bunch of rails create methods to pre-populate a bunch of tables in a database.
Ideally, I'd like the action to check for the existence of the file, if it exists, run all of the commands, and then delete the file so it doesn't get executed again.
So, the external file would basically look like this:
MyTable.create :name => "New 1"
MyTable.create :name => "New 2"
Is this easy to accomplish in rails?
Some elaboration:
The idea would be that if a certain set up tables need to be touched up after a release, and that you can't do it through a migration script (i.e. you're initializing the database from the schema.rb file), you could:
Create a file called "update_data.rb" for example
Place it in an admin directory
Target some action in the browser (i.e. /admin/update_data)
Rails would then read in the file, executing the commands line-by-line, and then
Delete the file when finished so that the actions weren't accidentally executed again
Does that help? It would be a file for one-time actions that need to be executed after a release. If there is a better method, I'm certainly all ears!
Another option would be rake. You can create a new file in lib/tasks - we'll call yours bootstrap.rake
namespace :db do
desc 'Load an initial set of data'
task :bootstrap => :environment do
if your_file_exists
puts 'Loading data...'
this_is_where_the_magic_happens
end
end
end
Then from the console you can run rake db:bootstrap and schedule it with crontab if you like.
For step 4:
load("update_data.rb")
I believe this would load and execute your script.
Sounds like a job for script/runner.
http://www.ameravant.com/posts/recurring-tasks-in-ruby-on-rails-using-runner-and-cron-jobs

How do I run Ruby tasks that use my Rails models?

I have a Rails app with some basic models. The website displays data retrieved from other sources. So I need to write a Ruby script that creates new instances in my database. I know I can do that with the test hooks, but I'm not sure that makes sense here.
I'm not sure what this task should look like, how I can invoke it, or where it should go in my source tree (lib\tasks?).
For example, here's my first try:
require 'active_record'
require '../app/models/mymodel.rb'
test = MyModel.new
test.name = 'test'
test.save
This fails because it can't get a connection to the database. This makes sense in a vague way to my newbie brain, since presumably Rails is doing all the magic work behind the scenes to set all that stuff up. So how do I set up my little script?
You can load the entire rails environment in any ruby script by simply requiring environment.rb:
require "#{ENV['RAILS_ROOT']}/config/environment"
This assumes the RAILS_ROOT environment variable is set, see my comment for other ways of doing this.
This has the added bonus of giving you all the nice classes and objects that you have in the rest of your rails code.
To kick off your processes it sounds like cron will do what you want, and I would also add a task to your capistrano recipe that would add your script to the crontab to periodically get the data from the external source and update your DB. This can easily be done with the cronedit gem.
The cron approach does have some drawbacks, mostly overhead and control, for other more sophisticated options see HowToRunBackgroundJobsInRails from the rails wiki.
I agree with the answer above but you have to include => :environment in your task or it will not load the Rails environment.
e.g.,
namespace :send do
namespace :trial do
namespace :expiry do
desc "Sends out emails to people who's accounts are about to expire"
task :warnings => :environment do
User.trial_about_to_expire.has_not_been_notified_of_trial_expiry.each do |user|
UserMailer.deliver_trial_expiring_warning(user)
user.notified_of_trial_expiry = true
user.save
end
end
end
end
end
I'd suggest creating custom rake tasks (lib/task/foo.rake). This give you easy access to most of the functionality of your rails app.
namespace :foo do
desc 'do something cool'
def something_cool
test = MyModel.new
test.name = 'test'
test.save
end
end
Then:
$ rake -T foo
rake foo:something_cool # do something cool
You can even run the tasks via a cronjob.
I wrote up a post about this a while back.
http://www.rawblock.com/2007/06/14/ruby-oracle-mac-os-x-pain-jruby-and-activerecord-jdbc-to-the-rescue/
You can open a connection in your scripts as such:
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:username => "root",
:host => "localhost",
:password => "******",
:database => "******"
)
I'm sure there is a more elegant way to do it, so that it grabs the info from your database.yml.
There are few steps to this and more details needed to really answer well.
You say your site retrieves data from other sources? How often? If it is semi-regularly you definitely want to look into background processing/messaging. If it is frequently you really want to avoid loading your rails environment every time your script runs since you will be paying too high a startup tax each time.
There are a multitude of options out there you will want to research. Reading about each of them, particularly reviews from people who post about why they made the choice they did, will give you a good feel for what questions you need to ask yourself before you make your choice. How big a job is loading the data? etc...
Off the top of my head these are some of the things you may want to look into
Script/Runner & Cron
Background/RB
Starling
Workling
MemcacheQ
Beanstalk
Background Job (Bj)
delayed_job (Dj)
Daemon Generator
Check out my answer in "A cron job for rails: best practices?".
It contains two examples for using cron to run Rake tasks and class methods (via script/runner). In both cases, Rails is loaded and you can use your models.
Nice Joyent write up of using rake to run rails tasks from a cron job - http://wiki.joyent.com/accelerators:kb:rails:cron
Easiest way to run ruby tasks that interact with rails app/models is to make Rails generate Rake tasks for you!! :)
Here's an example
run rails g task my_namespace my_task
This will generate a file called lib/tasks/my_namespace.rake which looks like:
namespace :my_namespace do
desc "TODO: Describe your task here"
task :my_task1 => :environment do
#write any ruby code here and also work with your models
puts User.find(1).name
end
end
Run this task with rake my_namespace:my_task
Watch your ruby code task that interacts with rails modal run!

Resources