Unable to autoload constant in factory girl association - ruby-on-rails

I have a class like this
class SMTPProvider < ActiveRecord::Base
self.table_name = 'smtp_providers'
And the corresponding factory for this is
FactoryGirl.define do
factory :smtp_provider, class: 'SMTPProvider' do
end
end
FactoryGirl.create(:smtp_provider) works fine. But the problem is when using this in another factory
FactoryGirl.define do
factory :based_on, class: 'AttackPackage' do
smtp_provider_id { FactoryGirl.create(:smtp_provider) }
end
end
Here is the error
Unable to autoload constant SmtpProvider, expected
/home/vamsi/code/scope2/app/models/smtp_provider.rb to define it
from /home/vamsi/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activesupport-5.1.1/lib/active_support/dependencies.rb:511:in `load_missing_constant'
It's trying to load SmtpProvider, but my class name is SMTPProvider, which I have mentioned in the smtp_provider factory

Due to the way RoR autoloads classes, the names you're using misalign with the autoloader. The autoloader expects there to be a _ before every capital letter (except the first).
Right now you have a file name smtp_provider.rb and a class name SMTPProvider which mismatch. The filename for SMTPProvider is s_m_t_p_controller.rb and the class name for smtp_provider.rb is SmtpProvider
Here's a class to file name method I made to show this:
class String
def upper?
self == self.upcase
end
end
module ClassFileName
def self.to_file_name(class_name)
class_name = class_name.chars
.inject do |file_name, char|
char = "_#{char}" if char.upper?
file_name << char
end.downcase
"#{class_name}.rb"
end
end
You can run it like this:
ClassFileName.to_file_name('SMTPProvider')
Which outputs: s_m_t_p_provider.rb
You have a few options to get them to line up:
create a s_m_t_p_controller.rb symlink with ln -s app/models/smtp_provider.rb app/models/s_m_t_p_provider.rb
mv app/models/smtp_provider.rb app/models/s_m_t_p_provider.rb move the file entirely
Rename class to SmtpProvider so it autoloads properly (Note: will have to update the code anywhere SMTPProvider is currently used)
I like option 3 the most since both the class and file name look "clean" to me.

Related

Accessing a module/class with same name in a different file in ruby on rails

I am having 2 different file test1.rb and Report.rb as follows :
test1.rb
module List
module Report
constants = Report::Category.constants.collect{|c| c.to_s}
end
end
In Report.rb
Class Report < ApplicationRecord
module Category
CONSTANT1 = 1
CONSTANT2 = 2
end
end
So I get an error in test1.rb saying undifined method category for list::Report. It is accessing the module list::Report insted for Report class from Report.rb.
Is there a way to access another class/module with same name as the current file module?
Try
constants = ::Report::Category.constants.collect{|c| c.to_s}
(note :: before Report that causes constant's lookup to start looking from the outmost context)

uninitialized constant name error

I have model as follows
app/models/views/def_usage.rb
class Abc
class Def < ActiveRecord::Base
self.table_name = 'vSomeview'
end
end
I am trying to create a factory girl for this
spec/factories/views/def_usage.rb
FactoryGirl.define do
factory :def_usage, class: Abc::DefUsage do
......
end
end
I am getting error uninitialized constant Abc::DefUsage (NameError)
I tried changing class: Views::Abc::DefUsage or Views::DefUsage but no luck. i am getting that error when i am trying to do rails console. why i am getting that error?
Your path needs to match your module/class hierarchy.
If you want your class to be in app/models/views/def.rb, then your class needs to be Views::Def.
If you want your class to be Abc::Def, your path needs to be app/models/abc/def.rb.
If you want your class name to be DefUsage, your file name needs to be def_usage.rb.
You can't use arbitrary paths and class names. They need to match if you want Rails to automatically load constants for you.

grape each entity in a single file

I want to have multiple classes inside grape entity file, this is the folder structure app/api/proj/api/v2/entities/committees.rb
module PROJ::API::V2::Entities
class Committee < Grape::Entity
expose :id
expose :name, :full_name, :email, :tag, :parent_id
expose :country do |entity, option|
entity.parent.name if entity.parent.present?
end
# include Urls
private
def self.namespace_path
"committees"
end
end
class CommitteeWithSubcommittees < CommitteeBase
# include ProfilePhoto
expose :suboffices, with: 'PROJ::API::V2::Entities::CommitteeBase'
end
and inside the Grape API
present #committees, with: PROJ::API::V2::Entities::Committee
is working. but if I present with
present #committees, with: PROJ::API::V2::Entities::CommitteeList
It is not working. But it works when I move it to a new file named committee_list.rb inside entities.
You seem to be missing some key information from your post because you have not defined a class named CommitteeList or CommitteeBase anywhere. I assume that you have defined them and that you did not supply that code.
The problem that you're running into has to do with how Rails autoloads classes. There is more information available elsewhere on this, but essentially you should ensure that your class names, modules names, directory names, and file names all match up. The reason that it works when you move your CommitteeList class to its own file is because Rails is able to find the class dynamically.
I've had to do some guess-work based on what you provided, but you want something that looks like this:
# app/api/proj/api/v2/entities/committee.rb
module PROJ::API::V2::Entities
class Committee < Grape::Entity; end
end
# app/api/proj/api/v2/entities/committee_base.rb
module PROJ::API::V2::Entities
class CommitteeBase; end
end
# app/api/proj/api/v2/entities/committee_with_subcommittee.rb
module PROJ::API::V2::Entities
class CommitteeWithSubcommittee < CommitteeBase; end
end
# app/api/proj/api/v2/entities/committee_list.rb
module PROJ::API::V2::Entities
class CommitteeList < CommitteeBase; end
end
Note that in this example I have renamed some things; your class names should be singular (committee not committees) and the filenames should match them, but making that change may cause other issues in your app. Generally, you should use singular and not plural.
I recommend reading the Rails guide entry on constants and autoloading for more detail.
Updated:
In your gist you say that you get Uninitialized constant PROJ::API::V2::Entities::CommitteeOffice when you run present #committees, with: PROJ::API::V2::Entities::CommitteeOffice with the following code:
# app/api/proj/api/v2/entities/committee_base.rb
module PROJ::API::V2::Entities
class CommitteeBase < Grape::Entity;
expose :id
end
class CommitteeOffice < CommitteeBase;
expose :name
end
end
You get this error because Rails will only look for the class named PROJ::API::V2::Entities::CommitteeBase in the file entities/committee_base.rb. If you prefer to use a single monolithic file for your entity classes, then you must name the above file app/api/proj/api/v2/entities.rb.
By naming the file app/api/proj/api/v2/entities.rb, it tells Rails "This file contains the module Entities and all its classes."

How to place a bunch of plain old Ruby objects into a models subdirectory?

In my Rails 3.2 app I have a bunch of plain old ruby objects in the /app/models/ directory. I'd like to move some of these into a separate folder, say /app/models/data_presenter/. For one of the objects,
# /app/models/data_presenter.rb
class DataPresenter
# ...
end
I've tried the following
# /app/models/data_presenter/data_presenter.rb
class DataPresenter::DataPresenter
# ...
end
however, I got the TypeError (wrong argument type Module (expected Class)) error. Any suggestions to overcome this (with or without namespaces)? Do I also need to change the corresponding models' tests names and locations?
As #BroiSatse pointed out, the problem was that I had a bunch of subclasses that were inheriting from the base class DataPresenter. For those subclasses I forgot about the namespacing, i.e.
# /app/models/data_presenter/color_data_presenter.rb
class ColorDataPresenter < DataPresenter
# ...
end
should have been
# /app/models/data_presenter/color_data_presenter.rb
class DataPresenter::ColorDataPresenter < DataPresenter::DataPresenter
# ...
end
or similarly
module DataPresenter
class ColorDataPresenter < DataPresenter
# ...
end
end
For the tests, I couldn't find a magick solution so I just wrote
# /test/unit/answers_presenter/color_data_presenter_test.rb
require 'test_helper'
class ColorDataPresenterTest < ActiveSupport:TestCase
should 'do something cool' do
presenter = DataPresenter::ColorDataPresenter.new
assert presenter.do_something_cool
end
end

Use a model concern

Ripping my hair out on this one:
app/models/concerns/soft_delete.rb:
module SoftDelete
extend ActiveSupport::Concern
module ClassMethods
def testing_a_class
pp "Ima class method"
end
end
def testing_an_instance
pp "Ima instance method"
end
end
class User < ActiveRecord::Base
include SoftDelete
end
app/models/user.rb:
class User < ActiveRecord::Base
testing_a_class
end
Now, in the rails console:
x = User.first # I expect "Ima class method" to be printed to the screen
NameError: undefined local variable or method `testing_a_class' for User(no database connection):Class
I don't know where you saw this idea of including a module in the same file where it's defined, but you must not do it (in rails), because of how rails works (auto-lazy-loading).
Rails doesn't load all classes on startup. Instead when you reference a class that doesn't yet exist, rails attempts to guess where it might be located and loads it from there.
x = User.first
Before this line, constant User does not exist (assuming it wasn't referenced before). When trying to resolve this name, rails will look for file user.rb in each of autoload_paths (google it up). It will find one at app/models/user.rb. Next time you reference User, it will just use this constant and will not look it up in the filesystem.
# app/models/user.rb:
class User < ActiveRecord::Base
testing_a_class
end
The definition that was found contains only an invocation of some unknown method (hence the error). Code in your concern file was not loaded and will never be loaded. To fix this, include the concern in the model file.
# app/models/user.rb:
class User < ActiveRecord::Base
include SoftDelete
testing_a_class
end
Now it should work.

Resources