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
Related
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.
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
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
How do you monkey patch a method of a class that is used in a rake task? In particular I want to redefine the method Synthesis::AssetPackage#compress_js of the asset:packager plugin. I tried to place the redefinition in the Rakefile in RAILS_ROOT, but that didn't work. I'd rather not change the plugin directly.
You have to redefine it after Synthesis gets loaded. I would guess that your app's rake tasks (Rails.root/lib/tasks/*.rake) get loaded after all the plugins, so you could try moving your monkey-patch into there.
In general, you shouldn't modify a Rails app's Rakefile directly anyway; put any custom rake tasks or other customizations into lib/tasks/*.rake.
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.