Rails autoload don't load module - ruby-on-rails

In my rails app, I have a custom directory in app/entities/keycard_generator/
I have a data_source.rb (a module) and register_keycard.rb (a class)
module DataSource
...
end
class RegisterKeycard
include DataSource
...
end
I have added this line in my application.rb, to autoload the path.
Dir[Rails.root.join('app/entities/keycard_generator/**/*.rb')].each(&method(:require))
But I get this errors (cannot run rails) :
uninitialized constant RegisterKeycard::DataSource (NameError)
Edit
I think that the issue is only with modules

Given that you have:
app/entities/keycard_generator/data_source.rb
Then the content of that file, by convention, should be:
module KeycardGenerator
module DataSource
...
end
end
Because rails essentially ignores the directory name under app (here, entities) in terms of naming:
app/entities/keycard_generator/data_source.rb
^^^^^^^^
But expects the directory name under the directory under app (here, keycard_generator) to be a module name:
app/entities/keycard_generator/data_source.rb
^^^^^^^^^^^^^^^^^
And given that you have:
app/entities/keycard_generator/register_keycard.rb
Then the content of that file, by convention, should be:
module KeycardGenerator
class RegisterKeycard
include DataSource
end
end
When following the convention, you do not need:
Dir[Rails.root.join('app/entities/keycard_generator/**/*.rb')].each(&method(:require))
Personally, I like to append the type (here, indicated by entities) to the files and modules/classes, like:
app/entities/keycard_generator/data_source_entity.rb
module KeycardGenerator
module DataSourceEntity
...
end
end
Which is how rails often (e.g., with controller) but not always (e.g., with model) does it.
In which case you would also do:
app/entities/keycard_generator/register_keycard_entity.rb
module KeycardGenerator
class RegisterKeycardEntity
include DataSourceEntity
end
end

Mark the included module as global by prefixing the name with ::
class RegisterKeycard
include ::DataSource
...
end

Related

How can i put app subdir classess under a namespace

I want to namespace all my classes put in a app/cms folder under Cms module. So, let's say i have a following file:
# /app/cms/types/post.rb
class Cms::Types::Post
end
Rails assumes that class definition of a file put in this dir should be Types::Post instead of Cms::Types::Post. So, when calling Cms::Types::Post.new, rails throws
LoadError (Unable to autoload constant Types::Post, expected /Users/xxx/workspace/personal/xxx/app/cms/types/post.rb to define it)
How can i namespace all these files under Cms?
I'm on rails 5.2.0
What is Post? I mean, what is the nature of Post? For instance, is it a service?
If it were a service (for example), I would put it in:
/app/services/cms/types/post_service.rb
And then define it as:
class Cms::Types::PostService
end
If it were a type (which seems like it might be a problematic name), then I would define it in:
/app/types/cms/post_type.rb
and define it as:
class Cms::PostType
end
In other words, name the directory under /app using the description of what Post is (just like rails does with models, controllers, etc.) and then add the description (in the singular form) to the end of your definition (rails does this with controllers, helpers, and mailers, but not with models).
Normally, when you use namespace you need to have your file structure as below:
module Cms
module Types
class Post
end
end
end
EDIT: Also to make it, autoload the path, you could add the below line in your application.rb:
config.autoload_paths << Rails.root.join('app', 'cms').to_s

Rails app: autoloading classes defined in modules

in Rails 5.0.1 application I have file app/actions/frontend/cart/get_cart_items_summarized.rb with:
module Actions
module Frontend
module Cart
class GetCartItemsSummarized
#content here
end
end
end
end
And in app/helpers/application_helper.rb I call it:
def get_cart_items
#...
items = Actions::Frontend::Cart::GetCartItemsSummarized.new.call
#...
end
But I'm getting:
uninitialized constant ApplicationHelper::Actions
Why? How should I use this class?
Thanks
In rails/autoloading, the first level of directories, the one directly under app, is not considered a part of the name. It is so that your model can be just User and not Models::User, etc.
My solution to this is to put all custom stuff in app/lib. This way, lib eats that non-naming layer and the rest of your folder structure become a name. In your example, put your file to
app/lib/actions/frontend/cart/get_cart_items_summarized.rb
Of course, feel free to replace "lib" with whatever you want ("app/custom", for example). This name doesn't matter.
Either use a full-qualified name:
::Actions::Frontend::Cart::GetCartItemsSummarized.new.call
or just stick to Rails constant lookup (the below should work):
GetCartItemsSummarized.new.call
In "config/application.rb", add "app" to autoload paths, e.g.:
class Application < Rails::Application
config.autoload_paths += Dir[Rails.root.join('app')]
end

How to split a module over multiple files in Rails?

In my Rails app I have a module, which holds all the classes that make up an API wrapper. Here's the module's structure:
(file: zway.rb)
module Zway
class API
# instantiates API object
end
class Main
# holds top level functions
end
class Controller
# holds controller functions
end
class Error < StandardError
end
class BadRequestError < Error
end
end
Now that module is getting too big to keep it in one file so I want to split it up. As there are several classes in the module, I figured that every big class should be one file. So I didn't think a lot but tried to take out one class to see if it would work. Like so:
(file: zway.rb)
module Zway
class API
# instantiates API object
end
class Controller
# holds controller functions
end
class Error < StandardError
end
class BadRequestError < Error
end
end
(file: main.rb)
module Zway
class Main
end
end
But now I am getting this error which doesn't sound right to me as I do exactly what the error complains about: defining class Main in main.rb :
Unable to autoload constant Main, expected /bla/homer/app/models/main.rb to define it
I've searched on the net how to use modules but mostly what I found was about multiple inheritance and namespacing. But not about the practics of using modules to organize code.
If you put your classes in modules, as per convention you should define them within folders named with the module_name. That's the whole point of organising. You can do something like this and give it a go. First organize your code and file like this ->
models/
zway/
main.rb
zway.rb
And inside your main.rb you namespace it like this ->
class Zway::Main
...
end
and inside your zway.rb you define the module
module Zway
...
end
In this way, when you're trying to access the main class it, since its namespaced, it will look it up inside a folder zway by convention. And all your models are organized neatly within their respective folders.

undefined method in Module

I have a /lib/custom
inside I have custom.rb and custom_page.rb
custom.rb
require 'custom_page.rb'
module Custom
def self.name(params)
# logic
end
end
I've added in application.rb config.autoload_paths += %W(#{config.root}/lib)
I can't seem to call in my controllers to Custom.name(params)
NoMethodError: undefined method `name' for Custom:Module
I've tried with define the method as def Custom.name, using class << self and method_function :name yet nothing helps..
Am I missing something?
It's because of the Rails naming convention. In your rails console, try
irb(main):001:0> Custom::Custom
LoadError: Expected lib/custom/custom.rb to define Custom::Custom
Rais expects you do define module Custom::Custom (not module Custom) in lib/custom/custom.rb.
Rails sees a folder lib/custom and created an empty module Custom (doesn't respond to name method) based on convention, if you want to define module Custom, you have to write a file lib/custom.rb
The convention is
lib/custom.rb #define module Custom
lib/custom/deeper.rb #define module Custom::Deeper
lib/empty_folder/ # rails provides you an empty module EmptyFolder
BTW you don't have to require 'custom_page' in your custom.rb, if Rails sees CustomPage in your code, it will try to load the class definition file based on naming convention, given that your custom_page.rb file path follows the convention.
You can also use ActiveSupport::Concern to extend both class and instance methods. In the module just to this:
module Custom
extend ActiveSupport::Concern
included do
# everything but the class methods go here
end
module ClassMethods
# define class methods here
def name(params)
#logic
end
end
end

Ruby namespaced class confusion

Here's the setup:
# app_controller.rb
class AppController; end
# org/app_controller.rb
module Org
class AppController < ::AppController; end
end
# org/admin/app_controller.rb
module Org
class Admin::AppController < AppController; end
end
Why does Org::Admin::AppController inherit from AppController, and not Org::AppController, considering that the class definition is namespaced?
This is because by the time you opened Org::Admin::AppController, Org::AppController must not have been defined, but ::AppController must have been . Perhaps your files are not being 'required' in the order you assumed them to be? You might solve this by adding a require <file containing base class> in the file where you create your derived class.
(Minor style guideline: Don't use :: to refer to classes and modules that you are opening for definition.)
Edit reason: I ran some tests and I must have been mistaken.

Resources