Custom folder and namespace - ruby-on-rails

I'm trying to use a custom folder with a namespace for certain classes in my rails project.
Essentially, what I'm building is a giant service, so technically I can move it there if I need to (though I would like to not do that). But either way, my classes aren't autoloading.
my file structure is:
app/
namespace/
namespace.rb
features/
main_feature.rb # class Namespace::MainFeature
some_feature.rb # class Namespace::SomeFeature < Namespace::MainFeature
workers/
a_worker.rb
I also load the files in my application:
config.paths.add File.join('app', 'namespace'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'namespace', '*')]
config.autoload_paths << Rails.root.join('app/namespace/**/')
But I'm still getting NameError: uninitialized constant
What's the best way to achieve this?
EDIT:
My issue is actually that the class is only found the second time I call it in rails console
So
[1] pry(main)> MyNamespace::Feature
LoadError: Unable to autoload constant Feature, expected /Users/.../app/my_namespace/features/feature.rb to define it
from /.rvm/gems/ruby-2.4.1#pop2/gems/activesupport-5.1.3/lib/active_support/dependencies.rb:511:in `load_missing_constant'
[2] pry(main)> MyNamespace::Feature
=> MyNamespace::Feature

You'll have the easiest time if your module structure will reflect folder structure (excluding the first level of folder, the one directly under /app).
app/
namespace/
namespace.rb # class Namespace
features/
main_feature.rb # class Features::MainFeature
some_feature.rb # class Features::SomeFeature < Features::MainFeature
With this, you don't need to do any load path customization (which can certainly backfire).
Because I would like everything to be in the Namespace::
Easy, just add another folder level
app/
namespace/
namespace.rb # class Namespace
namespace/
features/
main_feature.rb # class Namespace::Features::MainFeature
some_feature.rb # class Namespace::Features::SomeFeature < Namespace::Features::MainFeature

Related

undefined local variable or method for method located in lib directory file

I have some code i've inherited and am in the process of upgrading it to Rails 3.1. I'm suuuuper close to done but I got a bug.
In Rails Console I run User.first and I get this error
undefined local variable or method `acts_as_userstamp' for #<Class:0x000000046bef50>
Now acts_as_userstamp is a method located on line two inside my User model
class User < ActiveRecord::Base
#TODO /lib is not loading??? or is it??? why this method not work in browser?
acts_as_userstamp
And is defined in a file called app/lib/model_modifications.rb.
Now I recently discovered that my app/lib folder was not being autoloaded in my application.rb file and I think that's been fixed...or has it? Is this file correct? Or no?
require File.expand_path('../boot', __FILE__)
require 'rails/all'
# evil outdated soap middleware, TODO: kill it with fire
# Does this have to be loaded BEFORE the first line???
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', "vendor", "soap4r"))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', "vendor", "plugins", "soap4r-middleware", "lib"))
# evil outdated soap middleware, TODO: kill it with fire
require 'soap4r-middleware'
require File.join(File.dirname(__FILE__), '..', 'app', 'lib', 'soap.rb')
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require *Rails.groups(:assets => %w(development test))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end
module MyappDev
class Application < Rails::Application
# startup the lib directory goodies <-- IS THIS CORRECT???
# config.autoload_paths << "#{Rails.root}/lib"
# config.autoload_paths += %W( lib/ )
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
config.middleware.use MyAPIMiddleware
end
end
I'm trying to debug this file as I post this now. Here is a peak at it's internal structure...(i've just included the overall structure for the sake of brevity)
app/lib/model_modificatons.rb
class Bignum
...
end
class Fixnum
...
end
class ProcessorDaemon
...
end
module ActiveRecord
module UserMonitor
...
end
module MyLogger
...
end
end
class Object
...
end
class Struct
...
end
class String
...
end
class Fixnum
...
end
class OpenStruct
...
end
class ActiveRecord::Base
def self.visible_columns
...
end
...
def self.acts_as_userstamp
logger.info "HI fonso - acts_as_userstamp is called"
include ActiveRecord::UserMonitor
end
...
protected
def self.range_math(*ranges)
...
end
end
class Array
...
end
class DB
...
end
If you can spot a problem with the overall structure or anywhere else please let me know.
So why is this method not found? I'm trying to debug it as I'm posting this and I'm getting nothing.
I suspect the file app/lib/model_modifications.rb is not being loading. That nothing in the /lib directory is being loaded..but how do I confirm this?
Thank you for reading this far, I hope I've not rambled on too much.
autoload_path configuration does not load all the given files on the boot but defines folders where rails will be searching for defined constants.
When your application is loaded, most of the constants in your application are not there. Rails have a "clever" way of delaying loading the files by using a constant_missing method on Module. Basically, when Ruby encounters a constant in the code and fails to resolve it, it executes said method. THe sntandard implementation of this method is to raise UndefinedConstant exception, but rails overrides it to search all of its autoload_paths for a file with a name matching the missing constant, require it and then check again if the missing constant is now present.
So, in your code everything works as expected and you need to load this extension file manually. If you want to have some code that executes on the application boot, put your file within config/initializers folder.
Aside: Try avoiding monkey patching whenever possible. It might be looking clever, but adding more methods to already overpopulated classes will not make them easier to use.

Using Packwerk with ActiveAdmin (unitialized constant after moving model folder)

I'm using packwerk in my rails 6 application to enforce boundaries between new components (but I think the problem is more related to moving a model being used by active admin out of the app/models folder).
Old setup of rails project:
app/
...
models/
subscription.rb
...
services/
subscription_service.rb
bin
config
...
Becomes
app
bin
components/
subscription/
app/
public/
subscription_service.rb
spec/
subscription_service_spec.rb
package.yml
config
...
I then autoload the public files in my config/application.rb
class Application < Rails::Application
...
config.autoload_paths += Dir[Rails.root / "components/*/app/public"]
...
end
Now when I run bundle exec rspec components/subscriptions/spec/subscription_service_spec.rb the tests pass and all seems to work fine.
When I add the subscription model though so that structure will look like this:
app
bin
components/
subscription/
app/
public/
subscription_service.rb
models/
subscription.rb # no longer in app/models
spec/
subscription_service_spec.rb
package.yml
config
...
I get this error:
An error occurred while loading ./components/subscriptions/spec/subscription_service_spec.rb.
Failure/Error:
ActiveAdmin.register Subscription do
menu false
includes :pauses, :invoices
config.remove_action_item(:edit)
config.remove_action_item(:destroy)
filter :id
filter :status
NameError:
uninitialized constant Subscription
I think this has something to do with how ActiveAdmin autoloads perhaps, but I'm struggling to find an answer - so any help/clues would be much appreciated
Ah I figured it out, I forgot to import the models folder in application.rb which should now look like:
class Application < Rails::Application
...
config.autoload_paths += Dir[Rails.root / "components/*/app/public"]
config.autoload_paths += Dir[Rails.root / "components/*/models"]
...
end

What is the difference between Rails.application.config.autoload_paths and standard Ruby require/require_relative?

I see that the following configuration in application.rb:
config.autoload_paths += %W(#{config.root}/app/models/custom_pack/base)
config.autoload_paths += Dir["#{config.root}/app/models/custom_pack/custom_container/**/"]
config.autoload_paths += Dir["#{config.root}/app/models/custom_pack/helpers/**/"]
config.autoload_paths += Dir["#{config.root}/app/models/custom_pack/extensions/**/"]
is in the autoload_paths:
Rails.application.config.autoload_paths.grep /custom/
=> ["/Users/dviglione/projects/core/app/models/custom_pack/base", "/Users/dviglione/projects/core/app/models/custom_pack/custom_container/", "/Users/dviglione/projects/core/app/models/custom_pack/helpers/", "/Users/dviglione/projects/core/app/models/custom_pack/extensions/"]
But these files are not being loaded. Because I get an error:
`method_missing': undefined method `create_element' for #<MyElement:0x007f82eca39898>
That create_element method is defined in one of the files that should have been loaded.
However, when I use require/require_relative, it does work:
# /initializers/my_initializer.rb
require "custom_pack/base"
# models/custom_pack/base.rb
require_relative 'custom_container/base'
require_relative 'custom_container/parent'
require_relative 'custom_container/child'
Dir[File.join(File.expand_path("../helpers", __FILE__), "*_helper.rb")].each { |file| require file }
Dir[File.join(File.expand_path("../extensions", __FILE__), "*_adapter.rb")].each { |file| require file }
From what I read from the documentation, when you use require 'erb', Ruby looks for the file in the directories listed in $LOAD_PATH. That is, Ruby iterates over all its directories and for each one of them checks whether they have a file called "erb.rb". If it finds any of them, the interpreter loads it and ends the search. Otherwise, it tries again in the next directory of the list. If the list gets exhausted, LoadError is raised. For autoloading, the idea is that when a constant like Post is hit and missing, if there's a post.rb file for example in app/models Rails is going to find it, evaluate it, and have Post defined as a side-effect. Rails has a collection of directories similar to $LOAD_PATH in which to look up post.rb. That collection is called autoload_paths.
So why does require/require_relative work, but autoload_paths does not?
There's a number of things that could be going on here.
1.If a file is following the path: lib/foo/bar.rb the class needs to be defined like:
class Foo::Bar
end
2.Autoload also lazy loads files, which means your models are only loaded when you call them. For example:
puts "I was loaded!"
class MyLibrary
end
irb(main):001:0> require 'mylibrary'
I was loaded!
=> true
irb(main):001:0> autoload :MyLibrary, 'mylibrary'
=> nil
irb(main):002:0> MyLibrary.new
I was loaded!
=> #<MyLibrary:0x0b1jef>
As for the actual usage of autoload, it's recommended to start using require instead. In theory autoload sounds nice but it can cause problems when certain classes are dependent on other modules. Because of this autoload is in the process of being deprecated.

Namespaced modules and Rails 3.1.3 autoload_path

I'm having some trouble to namespace a module that I include in a model.
in /app/models/car.rb
class Car
include Search::Car
end
in /lib/search/car.rb
module Search
module Car
include ActiveSupport::Concern
# methods in here
end
end
in /config/application.rb
config.autoload_paths += Dir["#{config.root}/lib/**/"]
config.autoload_paths += Dir["#{config.root}/lib/search/*"]
The weird thing is that I don't get any errors directly when I fire up the server.
But if I refresh the browser after a while I get this error:
Expected #{Rails.root}/lib/search/car.rb to define Car
The nature of the problem indicates that it has something to do with:
/config/environments/development.rb
config.cache_classes = false
I also tried put a file search.rb directly in /lib where I define Search:
module Search
# Put shared methods here
end
What am I doing wrong?
UPDATE:
Ok, turns out that if I rename Search::Car to Search::CarSearch it works.
Is it not possible to have Modules/Classes of the same name in a different scope?
The error is coming from your autoload_paths. config.autoload_paths += Dir["#{config.root}/lib/**/"] will add all directories and their subdirectories under lib directory. meaning that you are telling rails to autoload lib/search/ directory, therefore car.rb under that directory is expected to define Car and not Search::Car. In order for rails to expect lib/search/car.rb to define Search::Car, you need to autoload lib/ directory and not lib/search. if you change you autoload to config.autoload_paths += Dir["#{config.root}/lib/"] and put search.rb in lib/ with following code:
module Search
require 'search/car'
end
then rails will understand and expect lib/search/car.rb do define Search::Car and referencing Car module/class in other places of your code will not reference to this car.rb.
You should remove this line (you should only have autoload for lib directory):
config.autoload_paths += Dir["#{config.root}/lib/search/*"]

cant load lib directory modules - uninitialized constant - rails 2 to rails 3 upgrade

I'm currently migrating an application in rails v2 to v3
In my lib/ i've some modules in subdirectories, for example,
i've the lib/search/host_search.rb
with a
module HostSearch
def do_search(args)
#...
end
end
then I need to use it in a controller named Discovery::HostController < ApplicationController :
def search_results
output = HostSearch.do_search(:search_string => #search_string,
:page => params[:page],
:user => #current_user)
#...
end
But have I get:
uninitialized constant Discovery::HostController::HostSearch
..I tried to put this lines in application.rb but it doesn't work..
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
I found that moving the module to the lib folder or explicitly including the folder to load worked, in your case
config.autoload_paths += %W(#{config.root}/lib/search)
I think there's something syntaxical that we are missing. Another thing is that if you don't want to mess with the application.rb file, require the file, which if I remember, takes the file path from the lib folder eg: search/host_search <- check that.
I think if you put the HostSearch module under a search subdir, (ie in lib/search/host_search.rb), then you need to namespace it:
module Search
module HostSearch
end
end
If you don't want to namespace it, you can should move the file into the lib root: lib/host_search.rb.
See also: https://stackoverflow.com/a/19650564/514483

Resources