I have a namespace issue. MyModel is just a normal Rails Model. However, my code seems to be namespacing it under the namespace I created, I need a way to reference it directly without the AppName::Loader::ModelImport namespace.
Error:
NameError: uninitialized constant AppName::Loader::ModelImport::MyModel
Rake task:
require 'csv'
require_relative '../appname/loader/model_import'
namespace :app_name do
namespace :loader do
desc "Loads data into Database"
task model_import: :environment do
include AppName::Loader::ModelImport
end
end
end
Service Object
./appname/loader/model_import.rb
module AppName
module Loader
module ModelImport
record_set = []
file_name = File.join(Rails.root, 'lib','appname','loader','data' ,'data.txt')
CSV.open(file_name, "r", { :col_sep => "\t", quote_char: nil, row_sep: "\r\r\n" }).each do |row|
record_set << MyModel.new(
company_name: row[1],
address1: row[2],
address2: row[3],
city: row[4],
state_code: row[5],
zip_code: row[6]
)
end
MyModel.import record_set
end
end
end
I tried:
::MyModel.new()
and also got the >>NameError: uninitialized constant MyModel, so I wonder if rails is not loading properly. However, I thought task model_import: :environment do loads Rails.
My application.rb file has..
config.eager_load_paths << Rails.root.join("lib")
If I use Pry, I can see the 'Rails' constant is loaded. However, I can not access any of my models. For example, User does not load, nor any other.
Solution per comments by Tom Lord:
require needed to be evaluated after the environment has been loaded.
If you write a module the code inside it may automagically get executed just because you required the module. For example:
module AppName
module Loader
module ModelImport
puts 'wtf' # this may get run but you won't see it
end
end
end
However if you have at least method defined it can be called
module AppName
module Loader
module ModelImport
def self.wtf
puts 'wtf'
end
end
end
end
Now if this file is saved in lib/app_name/loder/model_import.rb you can run it like this:
require_or_load 'lib/app_name/loader/model_import'
# and now you can do
AppName::Loader::ModelImport.wtf
=>wtf
So you need to first define a usable module, then you can require it, then you can call methods on it.
Related
I have a simple rails application where I import data from csv into my rails app which is functioning properly, but I have no idea where to start with testing this rake task, as well as where in a modular rails app. Any help would be appreciated. Thanks!
Hint
My Rails structure is a little different from traditional rails structures, as I have written a Modular Rails App. My structure is in the picture below:
engines/csv_importer/lib/tasks/web_import.rake
The rake task that imports from csv..
require 'open-uri'
require 'csv'
namespace :web_import do
desc 'Import users from csv'
task users: :environment do
url = 'http://blablabla.com/content/people.csv'
# I forced encoding so avoid UndefinedConversionError "\xC3" from ASCII-8BIT to UTF-8
csv_string = open(url).read.force_encoding('UTF-8')
counter = 0
duplicate_counter = 0
user = []
CSV.parse(csv_string, headers: true, header_converters: :symbol) do |row|
next unless row[:name].present? && row[:email_address].present?
user = CsvImporter::User.create row.to_h
if user.persisted?
counter += 1
else
duplicate_counter += 1
end
end
p "Email duplicate record: #{user.email_address} - #{user.errors.full_messages.join(',')}" if user.errors.any?
p "Imported #{counter} users, #{duplicate_counter} duplicate rows ain't added in total"
end
end
Mounted csv_importer in my parent structure
This makes the csv_importer engine available in the root of the application.
Rails.application.routes.draw do
mount CsvImporter::Engine => '/', as: 'csv_importer'
end
To correctly migrate in the root of the application, I added initializer
/engines/csv_importer/lib/csv_importer/engine.rb
module CsvImporter
class Engine < ::Rails::Engine
isolate_namespace CsvImporter
# This enables me to be able to correctly migrate the database from the parent application.
initializer :append_migrations do |app|
unless app.root.to_s.match(root.to_s)
config.paths['db/migrate'].expanded.each do |p|
app.config.paths['db/migrate'] << p
end
end
end
end
end
So with this explanation am able to run rails app like every other rails application. I explained this so anyone who will help will understand what to help me with as regards writing test for the rake task inside the engine.
What I have done as regards writing TEST
task import: [:environment] do
desc 'Import CSV file'
task test: :environment do
# CSV.import 'people.csv'
Rake::Task['app:test:db'].invoke
end
end
How do someone write test for a rake task in a modular app? Thanks!
I haven't worked with engines, but is there a way to just put the CSV importing logic into it's own class?
namespace :web_import do
desc 'Import users from csv'
task users: :environment do
WebImport.new(url: 'http://blablabla.com/content/people.csv').call
end
end
class WebImport # (or whatever name you want)
def initialize(url) ... end
def call
counter, CSV parse, etc...
end
end
That way you can bump into the Rails console to do the WebImport and you can also do a test isolating WebImport. When you do Rake tasks and Jobs (Sidekiq etc), you want to make the Rake task act as as thin a wrapper as possible around the actual meat of the code (which is in this case CSV parsing). Separate the "trigger the csv parse" code from the "actually parse the csv" code into their own classes or files.
I have some data, which I want to pre-load in my add on booting. I've made an rake task and it works good. I tried to put code in config/initializers but it starts too early (I need all models to be loaded). after_initialize is not good too for me. I place code example lower
require 'rake'
load File.join(Rails.root, 'lib', 'tasks', 'rights.rake')
Rake::Task['dev:create_rights'].invoke
So, where is good place to put this code? Of course I can put it in AR::Base or so on, but it is ugly.
Here is the task, if It will help.
namespace :dev do
desc "Creation of the minimal rights"
task :create_rights => :environment do
klasses = ActiveSupport::DescendantsTracker.descendants(AbstractModel)
default_rights = RightsList.default_rights
Role.includes(:rights).all.each do |role|
klasses.reject{|klass| role.rights.pluck(:klass_name).include? klass.underscore }.each do |klass|
Right.create role: role, klass_name: klass.underscore, rights_per_class: default_rights
end
end
end
end
Thank you
UPDATE
Got dirty solution with adding in config/application.rb
config.after_initialize do
require 'rake'
load File.join(Rails.root, 'lib', 'tasks', 'rights.rake')
Rails.application.eager_load!
Rake::Task['dev:create_rights'].invoke
end
And I understand that it is still wrong way. Is here good way?
I'm very new to the concept of importing data into a SQL database with CSV. I've followed some stackoverflow posts but I'm getting an error. The error states, Errno::ENOENT: No such file or directory # rb_sysopen - products.csv after running rake import:data. I have csv required in my application.rb as well as I have created a csv file and placed it in TMP. Here is my code so far. I understand I may be asking for a lot from the community but if someone were to answer this question, can you provide some more insight into CSV and rake functions. Thanks so much!!!
<b>import.rake</b>
namespace :import do
desc "imports data from a csv file"
task :data => :environment do
require 'csv'
CSV.foreach('tmp/products.csv') do |row|
name = row[0]
price = row[1].to_i
Product.create( name: name, price: price )
end
end
end
Specify the full path to the CSV file.
For example, if the file is in /tmp/ use:
CSV.foreach('/tmp/products.csv') do |row|
If the products.csv file is in your application's tmp directory use:
CSV.foreach(Rails.root.join('tmp', 'products.csv')) do |row|
I ran into something similar, it was forgetting to put both parenthesis with the braces so you might want to try going from:
Product.create( name: name, price: price )
to:
Product.create({ name: name, price: price })
Check out the smarter_csv Gem.
In it's simplest form you can do this:
SmarterCSV.process('tmp/products.csv').each do |hash|
Product.create( hash )
end
Add smarter_csv to your Gemfile, so it's auto-loaded when you require the environment in your Rake task
This gives you:
namespace :import do
desc 'imports data from given csv file'
task :data, [:filename] => :environment do |t, args|
fail "File not found" unless File.exists? args[:filename]
options = {} # add options if needed
SmarterCSV.process( args[:filename], options).each do |hash|
Product.create( hash )
end
end
end
Call it like this:
rake import:data['/tmp/products.csv']
See also: https://github.com/tilo/smarter_csv
I tried putting my script in a class that inherited from my model, like so:
class ScriptName < MyModel
But when I ran rake my_script at the command-line, I got this error:
rake aborted!
uninitialized constant MyModel
What am I doing wrong?
Also, should I name my file my_script.rb or my_script.rake?
Just require the file. I do this in one of my rake tasks (which I name my_script.rake)
require "#{Rails.root.to_s}/app/models/my_model.rb"
Here's a full example
# lib/tasks/my_script.rake
require "#{Rails.root.to_s}/app/models/video.rb"
class Vid2 < Video
def self.say_hello
"Hello I am vid2"
end
end
namespace :stuff do
desc "hello"
task :hello => :environment do
puts "saying hello..."
puts Vid2.say_hello
puts "Finished!"
end
end
But a better design is to have the rake task simply call a helper method. The benefits are that it's easier to scan the available rake tasks, easier to debug, and the code the rake task runs becomes very testable. You could add a rake_helper_spec.rb file for example.
# /lib/rake_helper.rb
class Vid2 < Video
def self.say_hello
"Hello I am vid2"
end
end
# lib/tasks/myscript.rake
namespace :stuff do
desc "hello"
task :hello => :environment do
Vid2.say_hello
end
end
All I had to do to get this to work was put my requires above the task specification, and then just declare the :environment flag like so:
task :my_script => :environment do
#some code here
end
Just by doing that, gave me access to all my models. I didn't need to require 'active_record' or even require my model.
Just specified environment and all my models were accessible.
I was also having a problem with Nokogiri, all I did was removed it from the top of my file as a require and added it to my Gemfile.
I have a class which is NOT an ActiveRecord.
the class is located under lib/room/
lib/room/car_painter.rb
class ROOM::CarPainter
def paint_car
...
end
end
Then, I have a rake task:
under /lib/tasks/
/lib/tasks/new_car_painting.rake
namespace :new_car do
desc "Paint new cars"
task :paint => :environment do
painter = ROOM::CarPainter.new #ERROR HERE- uninitialized constant
painter.paint_car
end
end
When I run rake new_car:paint, I got the error message "uninitialized constant ROOM::CarPainter", Why??
--EDIT---
I also tried to use class function instead of instance function, like following:
class ROOM::CarPainter
def self.paint_car
...
end
end
and
namespace :new_car do
desc "Paint new cars"
task :paint => :environment do
ROOM::CarPainter.paint_car #ERROR HERE- uninitialized constant
end
end
But I get the same error message...why again
This is rake file.
desc 'This is just a testing rake task'
task :update_ts => :environment do |t,args|
puts 'ashish is great'
include TestLib
print_sm
end
This is lib/test_lib.rb file.
module TestLib
def print_sm
puts "Hello World in Lib Directory"
end
end
You just need to include that module.
Edited:
I guess problem is your lib/* folder loading.
Try with this in your application.rb file:
config.autoload_paths += Dir["#{config.root}/lib/**/"]
In rails you need to require from the root and rails 3 practice is the following
require Rails.root.join('path')