I'm trying to add install task to my mountable engine units.
The task loads seed, and inside it clears some table:
# lib/tasks/units_tasks.rake
namespace :units
task :install do
Units::Engine.load_seed
end
end
# db/seeds.rb
Units::Item.delete_all
...
When I call the task from a command line
$ bundle exec rake units:install
# => NameError: uninitialized constant Units::Item
The engine is required as usual (and the gem itself works fine with all its
dependencies except for the case above).
# lib/units.rb
require 'units/engine'
module Units
end
# lib/units/engine.rb
module Units
class Engine < ::Rails::Engine
isolate_namespace Units
end
end
Obviously it is loaded without files, that should be eagerly loaded. But why?
In your seed method, you will need
require_relative '../lib/units'
or potentially
require_relative '../lib/units/engine'
Then you should be able to namespace as you have been previously.
I believe this has something to do with the threadsafe nature of rails, but a more technical reason is beyond me.
Related
I am building two separate gems, using Railties to bring in rake tasks from each gem. The tasks are in different namespaces, and when I bring in each separately in a Gemfile, the task shows up in rake -T, but when both gems are in the same Gemfile, only the second gem's task shows up. It's as if the second gem's task stomps the first, despite namespacing.
class AfsTest::Railtie < Rails::Railtie
railtie_name :afs_test
rake_tasks do
load 'tasks/config.rake'
end
end
namespace :afs do
namespace :test do
desc 'Configure afs_test gems'
task :config do
puts 'placeholder'
end
end
end
and
class AfsDevTest::Railtie < Rails::Railtie
railtie_name :afs_dev_test
rake_tasks do
load 'tasks/config.rake'
end
end
namespace :afs do
namespace :dev_test do
desc 'Configure afs_dev_test gems'
task :config do
Rails::Generators.invoke('rspec:install')
end
end
end
(Yes, I know the gems don't do much - that's not the point here. The point is that one stomps the other.)
For a Rails project I'm working on, I'm having an issue with loading Sidekiq and having nested modules in the lib directory.
my lib/scraper/v2.rb looks like this:
require 'scraper/v2/client'
module Scraper
module V2
end
end
my lib/scraper/v2/client.rb looks like this:
module Scraper
module V2
class Client
def initialize
...
end
end
end
end
I then have a Sidekiq job in the jobs directory that looks like this:
class RefreshTokenJob < ApplicationJob
queue_as :default
def perform
client = Scraper::V2::Client.new
...
end
end
If I run bundle exec sidekiq with this configuration, Sidekiq starts, but running Scraper::V2::Client.new form the Rails console returns:
NameError: uninitialized constant Scraper::V2
If I add config.autoload_paths += %W(#{config.root}/lib) to my application.rb file, I can run Scraper::V2::Client.new, but starting Sidekiq gives me and uninitialized constant error from a completely different file (within app/jobs/concerns/).
Any help with this would be much appreciated!
You must follow Rails' conventions for naming files if you want Rails autoloading to work correctly.
For a module named Scraper::V2, it should be in a file named scraper/v2.rb, not scraper_v2.rb.
I have a rake task test that I setup following the only examples I could find online.
It looks like this:
require 'test_helper'
require 'minitest/mock'
require 'rake'
class TestScrapeWelcome < ActiveSupport::TestCase
def setup
Rake.application.init
Rake.application.load_rakefile
#task = Rake::Task['scrape:scrape']
#task.reenable
end
def teardown
Rake::Task.clear
end
test "scraping text and sending to elasticsearch" do
mocked_client = Minitest::Mock.new
get_fixtures.each_with_index do |arg,i|
mocked_client.expect :index, :return_value, [index: "test", type: 'welcome', id: i, body: arg]
end
Elasticsearch::Model.stub :client, mocked_client do
#task.invoke
end
assert mocked_client.verify
end
private
def get_fixtures
(0..11).map { |i|
File.read("test/fixtures/scrape/index_#{i}.json")
}
end
end
But after the task runs once it starts running again without me doing anything (puts prints before and after #task.invoke show that the task is only run the once).
Turns out that rake is already required and initialized when the test runs so all of the following lines need to be removed or the task gets defined twice and runs twice even if you only invoke it once.
require 'minitest/mock'
require 'rake'
...
Rake.application.init
Rake.application.load_rakefile
Updated answer for rails 5.1 (using minitest):
I found I needed the following to load tasks once and only once:
MyAppName::Application.load_tasks if Rake::Task.tasks.empty?
Alternatively add MyAppName::Application.load_tasks to your test_helper, if you don't mind tasks being loaded even when running individual tests that don't need them.
(Replace MyAppName with your application name)
I've tried #iheggie answer but it worked in a way that indeed tests were run once but any other task was breaking with Don't know how to build task '<task_name_like_db_migrate>'.
I'm on Rails 3.2 still. It turned out that there were couple tasks loaded beforehand so the Rake::Task.tasks.empty? was never true and all other useful tasks were not loaded. I've fiddled with it and this version of it works for me right now:
Rake::Task.clear if Rails.env.test?
MyAppName::Application.load_tasks
Hope this helps anyone.
A solution that works for testing the tasks of a Gem that has been made a Railtie so it can add tasks to the Rails app:
Don't define the Railtie in test mode when you're also defining a Rails::Application class in spec_helper.rb (which allows your tests to call Rails.application.load_tasks). Otherwise the Rake file will be loaded once as a Railtie and once as an Engine:
class Railtie < Rails::Railtie
rake_tasks do
load 'tasks/mygem.rake'
end
end unless Rails.env.test? # Without this condition tasks under test are run twice
Another solution would be to put a condition in the Rake file to skip the task definitions if the file has already been loaded.
I have a problem creating a Rails plugin, lets call it Mplug. The plugin is pretty much only a rake task, but with a library that the rake task uses.
The problem is to require files. Lets say that this is the rake task:
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
This will not recognize the constant Mplug. So I thought I needed to require it.
require 'mplug'
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
But then I get this message.
no such file to load -- mplug
So, ok. Lets try to give the path to the plugin then.
require 'vendor/plugins/mplug/lib/mplug'
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
This actually works. However, except that I guess that this is a bad way to do it, I now have to require the files in my plugin as if I was in the rails root. For example:
module Mplug
end
require 'mplug/indexer'
Now has to be:
module Mplug
end
require 'vendor/plugins/mplug/lib/mplug/indexer'
Which I do not want to do of course.
Is there any neat way to solve this?
Thanks!
The easiest solution to this problem would be to register the rake task using the Rails::Railtie API. In lib/mplug.rb, define your Railtie:
module Mplug
class Railtie < ::Rails::Railtie
rake_tasks do
load "mplug/rails.rake"
end
end
end
Then, in lib/mplug/rails.rake:
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
Then, make sure your plugin is defined in your Gemfile. If your plugin is in vendor/plugins, add this line to your Gemfile:
gem "mplug", :path => "vendor/plugins/mplug"
If you push the plugin to a git repo, use :git.
Now, rake mplug:create will be available! If you want it to show up in rake -T, make sure you add a description:
namespace :mplug do
desc "creating an mplug"
task :create do
Mplug::Indexer.new
end
end
One option is to use the FILE constant, and then provide the rest of the path relative to the current file:
require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'mplug')
(if your rake task file is in your plugin_root/tasks...)
I have a gem that requires a 'Cms' namespace to be present when running.
However, when running rake tasks, nothing works as this Cms namespace isn't present. How do I get my rake tasks to work?
You can either, load your project source into the Rakefile (like Rails would do) or define a dummy module with the name Cms on your project.
# Rakefile
module Cms; end
task :my_task do
# ..
end
If you are on rails, and this gem is a dependency, you just have to make your task dependent of the :environment rails' task.
# some_task.rake
task :my_task => :environment do
# ..
end
Hope this helps.