Rails app: autoloading classes defined in modules - ruby-on-rails

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

Related

Accessing helpers and models from rails engine initializer

I'm trying to make a Ruby on Rails engine, and I want the initializer to be able to have access to the helpers and models.
I'll write below an example, part of the code, and the error that I have. It may not be the recommended way, because I can see that in some cases I'm repeating myself, but it's the first engine I make.
file lib/my_engine/engine.rb
module MyEngine
require 'my_engine/functions'
class Engine < ::Rails::Engine
isolate_namespace MyEngine
config.autoload_paths += %W( #{config.root}/lib )
end
class GlobalVars
attr_accessor :foo
def initialize
#foo = MyEngine::Functions.new
end
end
class << self
mattr_accessor :GLOBAL
mattr_accessor :USER_CONFIG
self.GLOBAL = MyEngine::GlobalVars.new
# add default values of more config vars here
self.USER_CONFIG = 'default config'
end
def self.setup(&block)
yield self
end
end
file lib/my_engine/functions.rb
module MyEngine
require '../../app/helpers/my_engine/options_helper'
class Functions
include MyEngine::OptionsHelper
attr_accessor :some_link
def initialize
#some_link = get_option('dummy')
end
end
end
There is also a controller named OptionsController in app/controllers/my_engine, and OptionsHelper in app/helpers/my_engine/options_helper.rb:
module MyEngine
module OptionsHelper
def get_option(name)
MyEngine::Option.new
end
end
end
When I try to run the dummy application, this error occurs:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
If I change to just Option.new, I have this error:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::OptionsHelper::Option (NameError)
For ::MyEngine::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
For ::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant Option (NameError)
The dummy application has nothing in it. All helpers and models defined above are in the engine.
Before this, I had other errors because it couldn't access the helper, or the Functions class. I had to add require and include to make it work even if they are placed in the same directory. Also, to work, I had to move GlobalVars from its own file inside engine.rb.
Can somebody show me what I'm doing wrong?
After I used required for every class, I ended with ActiveRecord::ConnectionNotEstablished, and it seems that not everything is loaded and available at that point when the GLOBAL object is created.
So I moved the code that was using the models in a separate init method. Then, I added an after initialize event:
config.after_initialize do
MyEngine.GLOBAL.init
end
I see a possible problem: because you are inside module MyEngine it might be possible that actually rails is looking for MyEngine::MyEngine::Option, so I see two approaches:
just write Option: this will look for MyEngine::Option
write ::MyEngine::Option this will look in the global namespace and find MyEngine::Option
Secondly, if that does not help, even though your path seems correct, but you can always explicitly require "my_engine/option" at the top of the file. I am not entirely sure the autoloading in an engine works in quite the same way, and I tend to, in my engine file, require almost everything (to make sure it works).
In my engine.rb I do
require_relative '../../app/models/my_engine/option'
maybe this will help, but it is not a nice solution.

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

Using Libraries in Rails (4.1.x) Engines?

So the core issue at heart here is the following message:
`<class:ApplicationController>': uninitialized constant Xaaron::Core (NameError)
So I think some of the steps I have done will be redundant, but I am new to trying to add code to my library folder in a rails engine, in rails its rather easy. But here its not so much.
So here is what I have done:
in:
xaaron/
lib/
xaaron/
I have a directory called core/ with a file called loder.rb.
Inside of core is a directory called controllers and in side there is a file called user_controller which looks like:
module Xaaron
module Core
module UserController
def assign_to_member_group(user)
memeber = Xaron::Group.find('member')
user.add_group = memeber.group_name
end
end
end
end
To load this I have a loader file:
module Xaaron
module Core
module Loader
include Xaaron::Core::Controllers::UserController
end
end
end
Which I do not think is needed because in the engine.rb file I do: config.autoload_paths << File.expand_path("../xaaron/core/**", __FILE__) which just goes up one directory to the lib/ directory and loads xaaron/core/ and everything in it (or so I thought).
This loader.rb file is included in the ApplicationController
module Xaaron
class ApplicationController < ActionController::Base
...
include Xaaron::Core::Loader
...
end
end
So:
Whats the proper way to load my "core" library
Why am I getting the error above?
I guess your problem is with config.autoload_paths << File.expand_path("../xaaron/core/**", __FILE__). It expands into smth like Rails.root/lib/engine_name/xaaron/core/** and your lib path should be Rails.root/lib/xaaron/core. So, in your case, lib path should be config.autoload_paths << File.expand_path("../../xaaron/core", __FILE__)
Moreover, beeing within your ApplicationController, it is enough to include include Core::Loader, because you're already within Xaaron namespace.
Before you'll start working with controllers, try just call your module Xaaron::Core::Loader within rails console.

How to refer to an instance of a class in Rails in a module?

I'm trying to dry up some code in my Rails application that allows to set a unique id when the object is creating using a filter. I have it in multiple locations and it seems like it should be in a module instead.
Right now I have something like this in each model.
def set_uid
self.uid = SecureRandom.uuid
end
I have included a new file in my /lib directory at the file uid_generator.rb and included that the module in each of the models.
//model
include UidGenerator
module UidGenerator
def set_uid
self.uid = SecureRandom.uuid
end
end
In my testing however, this yields the error
uninitialized constant MODELNAME::UidGenerator (NameError).
You just need to configure the autoload paths for your rails application. Here is something might be helpful. Best way to load module/class from lib folder in Rails 3?

Rails /lib modules and

I am writing a custom wrapper for open_flash_chart plugin. It's placed in /lib and load it as a module in ApplicationController.
However, I have either a problem with the Class hierarchy or some other problem.
From any controller I can access open_flash_chart functions as OpenFlashChart, Line etc.
However, in a class in a /lib module, it doesnt work!
Any ideas?
There are two ways that files get loaded in Rails:
It is registered in the autoload process, and you reference a constant that corresponds to the file name. For instance, if you have app/controllers/pages_controller.rb and reference PagesController, app/controllers/pages_controller.rb will automatically be loaded. This happens for a preset list of directories in the load path. This is a feature of Rails, and is not part of the normal Ruby load process.
Files are explicitly required. If a file is required, Ruby looks through the entire list of paths in your load paths, and find the first case where the file you required is in the load path. You can see the entire load path by inspecting $LOAD_PATH (an alias for $:).
Since lib is in your load path, you have two options: either name your files with the same names as the constants, so Rails will automatically pick them up when you reference the constant in question, or explicitly require the module.
I also notice that you might be confused about another thing. ApplicationController is not the root object in the system. Observe:
module MyModule
def im_awesome
puts "#{self} is so awesome"
end
end
class ApplicationController < ActionController::Base
include MyModule
end
class AnotherClass
end
AnotherClass.new.im_awesome
# NoMethodError: undefined method `im_awesome' for #<AnotherClass:0x101208ad0>
You will need to include the module into whatever class you want to use it in.
class AnotherClass
include MyModule
end
AnotherClass.new.im_awesome
# AnotherClass is so awesome
Of course, in order to be able to include the module in the first place, you'll need to have it available (using either of the techniques above).
In Rails 3 /lib modules are not loaded automatically.
This is because the line:
# config.autoload_paths += %W(#{config.root}/extras)
inside config/application.rb is commented.
You can try to uncomment this line or, (it worked even better for me), leave this commented (for future reference) and add this two lines:
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
What worked for me, besides uncommenting config.autoload_paths (I’m on Rails 3.1.3), was to create a initializer like this:
#config/initializers/myapp_init.rb
require 'my_module'
include MyModule
This way I can call mymodule methods from anywhere and as class methods Model.mymodule_method or as instance methods mymodel.mymodule_method
Maybe some expert may explain the implications of this. By now, use it at your own risk.
Edit: Afterwards, I think a better approuch would be:
create a initializer like this:
#config/initializers/myapp_init.rb
require ‘my_module’
Include the module where needed, like this:
1) if you want to use it as "Class Methods" use "extend":
class Myclass < ActiveRecord::Base
extend MyModule
def self.method1
Myclass.my_module_method
end
end
2) if you want to use it as "Instance Methods" include it inside Class definition:
class Myclass < ActiveRecord::Base
include MyModule
def method1
self.my_module_method
end
end
3) remember that include MyModule refers to a file my_module.rb in your load path that must be required first
To use the module lib/my_module.rb in your models and controllers:
In config/application.rb:
config.watchable_dirs['lib'] = [:rb]
In your model (similar idea for your controller):
require_dependency 'my_module'
class MyModel < ActiveRecord::Base
include MyModule
MyModule.some_method
end
This method is described in more detail at http://hakunin.com/rails3-load-paths
It might be the case that you want to explicitly load file(s) under lib directory at time of application initialization. In my config/application.rb, I have an entry as, config.autoload_paths += %W(#{config.root}/lib) Also this might be the case that module name/hierarchy is not same as it is in file or location/name of file is not same as that hierarchy, so auto-load of that file is also not possible. So when I added an entry at bottom of config/application.rb as, require "./lib/file_name_without_extention it worked fine.

Resources