Rails has trouble loading module chain - ruby-on-rails

I am building a Rails Engine. I defined a controller like this,
module A::B::C::D::E
extend ActiveSupport::Concern
# module stuff ...
end
class ExamplesController < ApplicationController
include A::B::C::D::E
# controller stuff ...
end
When I start the Rails console, rails console, I get the following error,
uninitialized constant A::B (NameError)
Why do I get this error?

Because the module does not exist at the point you are using it.
Since A is not defined, Ruby doesn't know what it is.
Note that the :: is a scope resolution operator used for look up, not defining a namespace-like hierarchy.
It would work if you defined A first:
module A
end
Then B:
module A::B
end
Then C:
module A::B::C
end
And so on.
Of course you could also do this:
module A
module B
module C
module D
module E
end
end
end
end
end

Related

How to access a class defined in module B from module A

Error message:
NameError: uninitialized constant FinancialFunctions::ComputationSteps::TwoBranchesPerPeriod::Strategy1
I'd like Rails to automatically look through the included Strategies module and see the class defined within it instead of just looking within TwoBranchesPerPeriod.
This is not a one-time problem and is part of a broader architecture restructuring. Solving this problem will help in many places throughout the codebase.
There are two specific solutions I'm not looking for:
Having a require or load statement at the top of the file, as I want Rails autoloading to be the core solution for this problem and not have to require several files.
Explicitly state the scope when initializing the class. I'm aware that simply calling Strategies::Strategy1.new will work, but this solution does not work for me because of the length of several classes. I'd like to simply define the dependencies at the top of the file with include statements, and have access to the methods/classes defined in the included modules.
I'm open to autoload statements in a file such as strategies.rb but from what I've test this does not work either.
app/lib/financial_functions/computation_steps/two_branches_per_period.rb:
module FinancialFunctions
module ComputationSteps
module TwoBranchesPerPeriod
include Strategies
def a_method
#The below line is resulting in the error
# NameError: uninitialized constant FinancialFunctions::ComputationSteps::TwoBranchesPerPeriod::Strategy1
a=Strategy1.new
end
end
end
end
/app/lib/financial_functions/computation_steps/strategies/strategy1.rb:
module FinancialFunctions
module ComputationSteps
module Strategies
class Strategy1
def initialize
puts "this should work"
end
end
end
end
I'd like to simply define the dependencies at the top of the file with
include statements, and have access to the methods/classes defined in
the included modules.
Your expectations are not realistic in Ruby. Ruby does not have a package system and includes is not equivalent to imports in languages such as Python or ES6.
As stated in the comments by #maxpleaner includes only copies the methods of the module.
So to do this you would have to define new constants in the consuming class/module:
module FinancialFunctions
module ComputationSteps
module TwoBranchesPerPeriod
include Strategies
Strategy1 = ComputationSteps::Strategies::Strategy1
def a_method
Strategy1.new
end
end
end
end
You can do this dynamically through the Module#included hook:
module Foo
module Bar
end
module Baz
end
def self.included(base)
self.constants.each do |c|
base.constant_set(c, self.constant_get(c))
end
end
end
module X
includes Foo
end
irb(main):002:0> X::Bar
=> Foo::Bar
But aside from being an fun example of meta-programming I don't really think its that wise as the there is no way to handle namespace conflicts. You could also construct a makeshift equivalent of an imports construct:
module Importer
def import(*constants, from:)
constants.each do |c|
const_set(c, from.const_get(c))
end
end
end
module Foo
module Bar
module A
end
module B
end
end
end
class Thing
extend Importer
import :A, :B, from: Foo::Bar
end
irb(main):001:0> Thing::A
=> Foo::Bar::A
irb(main):002:0> Thing::B
=> Foo::Bar::B
But this also smells like an example of fighting the language and will probably introduce more problems then it solves.

Calling selected methods of a module from another module

I have defined a method in module A. I want to call the same method from the moduleB
module A
included do
def self.some_func
end
end
end
module B
some_func # It raise error (NoMethodError: undefined method). How solve this?
end
module C
include A
include B
end
This doesn't work. Is it possible to call a function that is defined by one module in another module?
That module A should be raising an ArgumentError unless it also has an extends ActiveSupport::Concern at the top. Without ActiveSupport::Concern, you'd be calling the Module#included instance method here:
included do
...
end
but that requires an argument.
If you say this:
module A
extend ActiveSupport::Concern
included do
def self.some_func
end
end
end
then you'll get the included you're trying to use and the module A that you're expecting.
Also, module B doesn't include A so it has nowhere to get some_func from so this:
module B
some_func
end
will give you a NoMethodError. If you include A:
module B
include A
some_func
end
then it will work.

Ruby - Check if controller defined

I am using Solidus with Ruby on Rails to create a webshop and I have multiple modules for that webshop.
So, I defined a me controller into an module called 'solidus_jwt_auth' with the followin code:
module Spree
module Api
class MeController < Spree::Api::BaseController
def index
...
end
def orders
...
end
def addresses
...
end
end
end
end
I want to extend this in another module called 'solidus_prescriptions' so I created a decorator for this with the following code me_decorator:
if defined? Spree::Api::MeController.class
Spree::Api::MeController.class_eval do
def prescriptions
...
end
def create_prescription
...
end
private
def prescription_params
params.require(:prescription).permit(
*Spree::CustomerPrescription.permitted_attributes
)
end
end
end
And for this I wrote unit tests in solidus_prescription module and integration tests in webshop. The unit tests are working fine, but the integration tests are giving the following error:
Error:
MeEndpointsTest#test_me/prescriptions_post_endpoint_throws_an_error_when_wrong_params:
AbstractController::ActionNotFound: The action 'create_prescription' could not be found for Spree::Api::MeController
test/integration/me_endpoints_test.rb:68:in `block in '
Which means that he can not find the MeController defined in another module. How can I make the check if the MeController is defined since the code bellow does not help me with anything:
if defined? Spree::Api::MeController.class
end
This worked in the end:
def class_defined?(klass)
Object.const_get(klass)
rescue
false
end
if class_defined? 'Spree::Api::MeController'
....
end
if defined? should do exactly what you want it to do in theory. The problem is you're checking if defined? Spree::Api::MeController.class. The #class of your class is Class. So what you're really getting is if defined? Class which will always be true!
This issue is most likely not that the conditional is failing but that it's never getting read. Rails lazy loads most of the code you write, meaning the file is not read until it's called somewhere in execution.
The decorator module should just contain the methods you want to add, without the conditionals or the use of class_eval. Then in the original class you can include it.
module Spree
module Api
class MeController < Spree::Api::BaseController
include MeDecorator
end
end
end
If for any reason you're not certain MeDecorator will be defined, don't use defined?, because defined? MeDecorator will not actually go looking for it if it's not defined and load the necessary file. It will return nil if the constant has no value. Just rescue a NameError
module Spree
module Api
class MeController < Spree::Api::BaseController
begin
include MeDecorator
rescue NameError => e
logger.error e
end
end
end
end

Rails mistakenly nesting called namespace into caller namespace

Bar calls Foo and Rails thinks that Foo must be within the Bar namespace
module Foo
class Lofatook
def oops
puts 'buckets of fun'
end
end
end
module Bar
class Thedoor
def theyhaveacavetroll
Foo::Lofatook.new.oops
end
end
end
Bar::Thedoor.new.theyhaveacavetroll
This works fine as raw ruby. But when I put Foo in lib/foo/lofatook.rb and Bar in app/models/bar/thedoor.rb it throws this error:
uninitialized constant Bar::Foo
We are using the golden lib loading hammer of
config.autoload_paths += Dir["#{config.root}/lib/"]
config.autoload_paths += Dir["#{config.root}/lib/**/"]
with all its glorious drawbacks.
But this should not mean it mistakenly assumes Foo must be part of Bar?
Problem was its not enough to have
module Foo
class Lofatook
end
end
in lib/foo/lofatook.rb. You must also have lib/foo.rb
module Foo
end
Thanks to #ma_il, using ::Foo::Lofatook raised the error uninitialized constant Foo which pointed me in the right direction

Rails: Is that possible to define named scope in a module?

Say there are 3 models: A, B, and C. Each of these models has the x attribute.
Is that possible to define a named scope in a module and include this module in A, B, and C ?
I tried to do so and got an error message saying that scope is not recognized...
Yes it is
module Foo
def self.included(base)
base.class_eval do
scope :your_scope, lambda {}
end
end
end
As of Rails 3.1 the syntax is simplified a little by ActiveSupport::Concern:
Now you can do
require 'active_support/concern'
module M
extend ActiveSupport::Concern
included do
scope :disabled, where(:disabled => true)
end
module ClassMethods
...
end
end
ActiveSupport::Concern also sweeps in the dependencies of the included module, here is the documentation
[update, addressing aceofbassgreg's comment]
The Rails 3.1 and later ActiveSupport::Concern allows an include module's instance methods to be included directly, so that it's not necessary to create an InstanceMethods module inside the included module. Also it's no longer necessary in Rails 3.1 and later to include M::InstanceMethods and extend M::ClassMethods. So we can have simpler code like this:
require 'active_support/concern'
module M
extend ActiveSupport::Concern
# foo will be an instance method when M is "include"'d in another class
def foo
"bar"
end
module ClassMethods
# the baz method will be included as a class method on any class that "include"s M
def baz
"qux"
end
end
end
class Test
# this is all that is required! It's a beautiful thing!
include M
end
Test.new.foo # ->"bar"
Test.baz # -> "qux"
As for Rails 4.x you can use gem scopes_rails
It can generate scopes file and include it to your model.
Also, it can automatically generate scopes for state_machines states.

Resources