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
Related
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
I want my app to execute a task (download CSV files from another website & parse them) just before the server starts.
In which file exactly should I put the code for this?(maybe db/seeds.rb?)
Also, any idea on how to test this with RSpec?
The initializers folder is the proper place to set this tasks.
Just create a file with .rb extension, and everything else will be performed on start.
http://guides.rubyonrails.org/configuring.html#initializers
From the Rails Guides:
In the rare event that your application needs to run some code before Rails itself is loaded, put it above the call to require 'rails/all' in config/application.rb.
Inside initializers folder, can create any .rb file and can write any logical code in that file. It will be executed on start.
But it may slow down server start, because in your case csv download and parsing may take time to execute.
http://guides.rubyonrails.org/configuring.html#running-code-before-rails
One solution, this may be not right
in lib/tasks/say_hello_rake.rake
namespace :sample do
task :your_task_name do
puts 'Hello my task ... '
end
end
before rails s
rake sample:your_task_name
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.
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.
I have seen some apps that have a few rake tasks included to load data. I am not talking about seed data, I know about db/seeds.rb, instead I am referring to data such as default users and basic records that help me fill my application with something to look at. I don't want to use db:fixtures:load because I don't have any control over this...
I would like to have rake tasks like this:
rake myapp:data:delete
rake myapp:data:load
rake myapp:data:reload
Where the 'delete' rake task would delete all data that I specify in the rake task, the 'load' app will load the default data from the task into the app and the 'reload' task will delete all data, then load it in the app. How do I do something like this?
If you could give me an example where I have a model named 'Contact' and a few fields - basically how to add or delete data from those fields in a rake task, I would REALLY appreciate it!
Just to give you an idea, I would mainly use these rake tasks when I move from one computer to another to do development. I don't want to manually go enter default records (such as my user to login with) so I could just do rake myapp:data:reload - this would be after doing rake db:schema:load
Thank you,
BN
Create a file lib/tasks/data.rake and write the following code:
require File.join(File.dirname(__FILE__), '../../config/environment')
require 'database_cleaner'
namespace :myapp do
namespace :data do
task :delete do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean
end
task :load do
require 'db/data.rb'
end
task :reload do
Rake::Task['myapp:data:delete'].invoke
Rake::Task['myapp:data:load'].invoke
end
end
end
So now you have defined your rake tasks. I'm using the gem database_cleaner, so you'll need to install it:
sudo gem install database_cleaner
Now, the rake myapp:data:load is basically loading the data from a file called db/data.rb. You could name it anything you wanted as long as you used the file name in the rake task or you could use more than one file if you wanted... So create the file db/data.rb and put all the code that you need...
User.create(...)