How do I run Ruby tasks that use my Rails models? - ruby-on-rails

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!

Related

Make custom commands for ruby on rails

I want make new command line program for my ruby on rails application, but I can't found how I can make new command.
I mean, this functional analogue in RoR.
Help me please, and tell me please how I can fix my problem.
Thanks a lot.
P.S. sorry about my English.
ADD1: I want create backend script for parse xmls and save its in database. I think, will be very good if I can use AR classes from my RoR application.
Create you rake task file in lib/tasks/my_self.rake
and write something like:
namespace :my_commands do
task :this_command do
puts "this is My Command"
end
end
If you want access to your Applications Models you must initialize rails for you applications with your task including environment.rb:
# write task name with :environment if you use task with AR
namespace :my_commands do
task this_command: :environment do
puts "this is My Command"
user = User.first
puts user.name
end
end
Now you can run you task code in terminal:
$ rails my_commands:this_command
For command line prog just use rune as follows :
rails runner "model.method"
as in case of console and server you can also mention the environment for running.
For parsing xml there a lot of options like LibXML.

Looking for way to load just selected models instead of all Rails environment in rake task

in classic rake example we can load all rails environment with all models in taks with:
desc "Pick a last user name"
task :winner => :environment do
puts "Last user: #{User.last.name}"
end
I have very large rails project so it takes a lot of time to load all models and dependencies.
Is any way to load rails with just specific model or set of models?
I'm about to write custom rails loader but don't want to invent bycicle ;)
Maybe some gem or good custom loader example already exist?
I think it would be useful for some cases of resque workers as well.
Thanks!
You could do task :winner do and then require the modules/models in our outside the task.

How do I redirect the sql statements in Rails to other than log/development.log?

Something in Rails (ActiveRecord::Base.logger ?) puts all executed SQL into log/development.log.
I have a rails app, whose data is populated by several rake tasks. Often in development, I want to run the web app, and run several of these rake tasks simultaneously (they are long running tasks that talk to other systems and create data in a local database).
Annoyingly, they all log to the same file at the same time.
How/where should I change this? Can I do it from the command line? Where (if in a rake file) should I do it? Or should I create new environments for each rake task?
Is there documentation i should have read to answer this, where is it?
Thanks a bunch.
Depends on what you are really trying to log. In Rails 3.+ you can use the ActiveSupport::LogSubscriber mechanism to hook into Rails internals and log certain interests to other files.
If you really want to hijack the core Rails.logger instance for each of your rake tasks, than do just that.
# lib/tasks/foo.rake
desc "Prints Hello World"
task :helloworld do
Rails.logger = Logger.new("/path/to/hello-world.log")
# do something in your Rails stack that would write to "Rails.logger"
end
Not tested, but I think that might work.
That being said, I think subscribing to interesting events in ActiveRecord via ActiveSupport::LogSubscriber might be a cleaner approach
My current solution is
dec "update statistics"
task :update_stats => :environment do
root_path = Rails.configuration.root_path
env = Rails.configuration.environment
ActiveRecord::Base.logger = Logger.new(File.join(root_path, "log", "#{env}-stats.log"))
#code ...
end
It feels a bit hacky, but maybe that's just the way to do it.

Ruby scripts with access to Rails Models

Where and how do I run a simple script that uses my rails environment. Specifically I have one column that holds multiple pieces of information, I've added columns now for each piece of information and need to run a ruby script that can run to call a method on each row of the database to extrapolate data and save it to the new column.
Using a migration sounds like the right way to go if I am understanding your use case.
However, if you really do want to write a standalone script that needs access to your Rails application's models, you can require the environment.rb file from inside your standalone script.
Example:
#!/bin/env ruby
ENV['RAILS_ENV'] = "production" # Set to your desired Rails environment name
require '/path/to/railsapp/config/environment.rb'
# After this point you have access to your models and other classes from your Rails application
model_instance = MyModel.find(7)
model_instance.some_attribute = "new value"
model_instance.save
I have to agree with David here. Use a migration for this. I'm not sure what you want to do, but running it from inside your environment is much, much more efficient then loading up the app environment manually. And since your initial post suggests you're only doing this once, a migration is the way to go:
rails g migration MigrateData
.. generates:
class MigrateData < ActiveRecord::Migration
def self.up
# Your migration code here
end
def self.down
# Rollback scenario
end
end
Of course, you will always want to perform this locally first, using some test data.
Agree with everyone, for this specific case it sounds like migration will be way to go, however, to do this regularly, or write some other task/script that interacts rails app environment make rails generate a rake task for you! This gets saved with your rails app, and can be run again and again :)
Easiest way to generate a rake task 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!
Seeding data:
http://railscasts.com/episodes/179-seed-data
Adding data with migrations
http://railscasts.com/episodes/23-counter-cache-column
Working with Rake Tasks
http://railscasts.com/episodes/66-custom-rake-tasks
I prefer to use migrations for adding some data in your case.
If it's a one-time thing, use a migration.
If this is something that needs to be done multiple times, use a rake task for it.

Access a class method from a model in the Rakefile / Ruby on Rails 3

I have a model, let's call it Foobar. I want to be able to run a cron job to update an attribute of all objects that are instances of Foobar. So, in pseudocode, it might be something like this:
Foobar.all.each do |foobar|
foobar.update_attributes({:my_attribute => 'updated'});
end
Now, let's say I wrap that in a class method called Foobar.run_update().
Calling Foobar.run_update() would work fine from the controller, or even from a view. But, what I want to do is run run_update() from the Rakefile so that I can tie it into a cron run. But, the Foobar class is not available to Rake when it is called from crontab.
How can I resolve that? How can I access the class methods of Foobars from Rake, when Rake is called from cron?
Thank you very much for your help.
By rake, if you mean a rake task then adding => :environment loads the rails environment for the task and you be able to call the Foobar.run_update method there. Like,
namespace :foobar do
task :update => :environment do
Foobar.run_update
end
end
And you should just be able to call rake foobar:update from the console and have it scheduled as a cronjob.
You can load up the Rails environment by requiring config/environment.rb:
ENV["RAILS_ENV"] ||= "production"
require '/where/your/rails/project/is/config/environment.rb'
In my Sinatra apps I typically have the file models/init.rb which requires Sequel, sets up my DB connection, and then uses require_relative to require all my model files. My main application then does require_relative "models/init".
With this setup any other script (including IRB) all I have to do is require the models/init.rb file myself, and I have full access to the same models and DB connection that the application has.

Resources