is it possible to embed rake into an application? - ruby-on-rails

My app has a complicated set of dependencies -- some information comes from user input, some comes from remote sites via delayed_job -- expressible as a directed acyclic graph. I was all set to write a simple dependency mechanism to traverse the graph, but it occurs to me: Rake does this already, and I shouldn't re-invent the wheel.
Usually rake takes its declarations in a standalone rake file and is run from the command line. Instead, is it possible to write the declarations in a library or model file and run rake from within the app? (To make it clear: I would NOT be using rake here for long-running tasks, I just want it for its ability to define and traverse a dependency graph.)
This loads without error:
require 'rake'
class MyClass
task :clean do
puts 'cleaning'
end
end
... but it's not clear how to invoke it, or create tasks in namespaces, etc.

Depending on your environment:
From Rake:
Rake::Task#execute
http://rake.rubyforge.org/classes/Rake/Task.html
From term or cmd:
Windows:
system('start rake doc:app')
Linux:
system('rake doc:app')
Here's a discussion at ruby-forum:
https://www.ruby-forum.com/topic/196210
Here's a related StackOverflow question / answer using the system call:
http://www.stackoverflow.com/questions/3021805

Related

Why are rake tasks stored in lib/tasks/?

My understanding of the lib/ directory in rails is that it stores non-domain specific code as a best practice.
However, my Rake scripts are very specific to my domain. They do things like create new models.
So is there a better place than lib/tasks/ to store domain-specific rake scripts, or am I missing something here?
I like this idea, and I agree - lib at one point was very much a junk drawer, and as a Rails community we've moved some of the junk away, but yes Rake tasks are usually very specific application logic.
In your Rakefile all you have to do is load your new Rakefiles (exercise for the reader: iterate the files in the folder instead of specifying it explicitly.
Example:
require File.expand_path('../config/application', __FILE__)
Rails.application.load_tasks
load('app/tasks/my_task.rake') # <--- my custom task!!!
The above is correct, but you should add the following to prevent the zeitwerk from creating the Tasks constant. (ignore if not using zeitwerk loader)
Rails.autoloaders.main.ignore('app/tasks');
You may check this in the console by calling Tasks, it should not be defined.
Another option might be to add rake tasks inside /tasks instead of app/tasks

How to design a rake task that calls multiple class methods?

I have a rake task called pull_orders which calls methods of a RemoteDbConnector class to do things like establish a connection to an external db, generate raw SQL queries, execute queries and store records in the local db.
While trying to test it, I stumbled upon this answer which got me thinking whether my design is flawed.
Should rake tasks really be one liners? If so, where should I put all these method calls, given that they need to be called in a particular sequence?
My task looks like this:
namespace :db do
desc 'finds and populates data from remote db'
task pull_orders: :environment do
...
columns = ...
table = ...
options = ...
column_mappings = ...
RemoteDbConnector.generate_query(...)
RemoteDbConnector.execute_query(...)
RemoteDbConnector.map_column_names(...)
Order.create(...) #creates records based on hash generated by RemoteDbConnector
...
end
end
Reasonable people will probably not enforce a strict rule of no more than one line per rake task, but it is definitely nicer to move large blocks of code out of .rake files into classes:
Short rake tasks make the structure of the .rake file clearer.
Large blocks of code should usually be broken up into multiple small methods with names that explain what they do. You could just do that in the .rake file, but then you'd have a mixture of rake tasks and methods, which would not be as easy to read as a uniform set of rake tasks.
Normal classes are easier to test.
So,
Extract classes out of your big rake tasks. You can initially define such a class in the same .rake file as the task you extracted it from as an intermediate step.
Move the classes from .rake files to your project's lib directory, not in lib/tasks (which is for .rake files) but in the root of lib or in a subdirectory of lib corresponding to their namespace if they have one.
require the classes in the rake tasks that use them. (Don't configure your app to autoload lib; that would load unwanted code and its dependencies in your production application.)
Test the classes as you would any classes.
I don't agree with the post you've linked and think your approach is fine. It's not usually possible or practical for rake tasks to be one liners. Often times Rake tasks are used for one-off tasks like bulk migrating or updating your database (by migrating I mean data migrations, not schema migrations, as is probably the case here).
In such cases it wouldn't be reasonable to update the code base to include code that's only intended to be run once, and there's no problem in handling logic in rake tasks.

Rails 3.2: Adding seed tasks from a mountable engine

I have a rails application using rake db:seed too fill in some of the basics in my DB to start working. This works through a little infrastructure and a fixtures.rb someone else wrote to load a YAML into the DB.
What I'm trying to do is take this infrastructure and move it into my own gem so that I can use it again elsewhere, this requires me both to have some of my own gem's models be inserted through the gem's seed task (which I want to have run from calling db:seed or something similar in the main app) and that the main app push some of the gem's models using it's own seed. The second part of this I already have working, it was a simple fix in the fixtures.rb file I was given.
The things I want to do now:
Move fixtures.rb into the gem: I still don't have any of the source running this in the gem. Now to do this I can probably require the file from the [MyGem::Engine.root, 'lib', ...].join then call a method there with a path to load YAML files from into the DB, which I don't see why it shouldn't work.
Get rake db:seed to run a task defined in my gem. I've added .rake files under lib/tasks of my gem (it's an engine) and I can't seem to call them from the main app rakefile though.
To make myself clear, what I want to do is through the gem (not the main app - or with 1 line of code in the main app) add a dependency onto the main apps seed task, so that when someone runs rake db:seed in the main app the gem will run additional seeding without the main app developer even having to know about them.
The dirty solution that I want to avoid is loading the .rake files from the gem inside the main app, or loading a seeds.rb in the gem from the one in the main app.
So what I'm asking is basically how to make the rake db:seed task do things defined within my gemified engine just by having the gem required in the gemfile?
So shortly after asking this I figured it out.
First step was taken from here: How to add a gems tasks to the main app
Then inside a task file
#lib/task/some_task.rake
Rake::Task['db:seed'].enhance ['my_seed_task']
#lib/tasks/my_seed_task.rake
task 'my_seed_task' do
...
end
And now when in the main app I run rake db:seed it runs whatever my_seed_task defines as a perquisite.
Try instead to extend Rails::Generators::Base.
This is the mechanism as given in the docs:
"Each public method in the generator is executed when a generator is invoked"
so
class DbGenerator < Rails::Generators::Base
source_root File.expand_path('../../../../db', __FILE__)
def copy_seeds_file
copy_file 'seeds.rb', 'db/seeds.rb'
end
def copy_seeds_dir
Dir.foreach seeds_dir do |file|
copy_file "seeds/#{file}", "db/seeds/#{file}" unless file.match /^\./
end
end
def seeds_dir
File.expand_path 'seeds', self.class.source_root
end
end
This approach will allow all gem-based seed data to copy into the app dir for the db:seed run

Where to put model “utility” functions in Ruby on Rails, if it is also required in a rake task?

This is a 2nd part to the following question:
Where to put model "utility" functions in Ruby on Rails
Problem is, I need access to these utility functions from a rake task as well. Using the accepted technique in in the other thread, I get an "undefined method" error when accessing my model from a rake task.
What is the best way to fix this?
Thanks
You probably need to define your rake task as dependent on the Rails environment:
task :my_task => :environment do
# Will load Rails stack before executing this block
MyModel.foo
end
The default behavior is to load almost nothing, so you won't have access to your models unless you ask for it.

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