Working on an initial Rails project, and using Rubocop to analyze code style. It led me to question exactly how Ruby's nested classes work in the context of Rails. For example, in my engine, I have a model:
# app/models/app_core/tenant.rb
module AppCore
class Tenant < ActiveRecord::Base
end
end
and a controller:
# app/controllers/app_core/tenant/members_controller.rb
module AppCore
class Tenant::MembersController < ApplicationController
end
end
In the model's case, the module is the same as the path and the class name is the same as the file name. In the controllers case, the second part of the path, "tenant" is part of the class name.
Rubocop tells me that I should "Use nested class definitions instead of compact style" in the Tenant::MembersController
line, so if I understand correctly...
module AppCore
class Tenant
class MembersController < ApplicationController
end
end
end
...this shouldn't make a difference.
Now, my question is I have AppCore::Tenant as a model, but then AppCore::Tenant looks to be reopened and the MembersController class is added to it as a nested class. Does this mean that my Tenant class will always have that nested class in it? Do I need to name my models and controller routes something differently? Is this totally fine and nothing to worry about? Not exactly sure what this means.
Nested Class can be used whenever you want to create more than once instance of the class or whenever you want to make that type more available. Nested Class increases the encapsulations as well as it will lead to more readable and maintainable code.
The scope of a nested class is bounded by the scope of its enclosing class. Thus in below example, class NestedClass does not exist independently of class OuterClass. A nested class has access to the members, including private members, of the class in which it is nested.
One subtle difference is that your scope is different, and this can cause errors. In the first case constants will be looked up in AppCore
, whereas in the second case constants will be looked up in AppCore::Tenant
. If you fully qualify constant names then it doesn't make a difference.
Foo = :problem
module A
Foo = 42
# looks up A::Foo because of lexical scope
module B
def self.foo
Foo
end
end
end
# looks up ::Foo because of lexical scope
module A::C
def self.foo
Foo
end
end
# Looks up A::Foo, fully qualified ... ok technically ::A::Foo is fully qualified, but meh.
module A::D
def self.foo
A::Foo
end
end
A::B.foo # => 42
A::C.foo # => :problem
A::D.foo # => 42
If you are referring to constants defined in AppCore::Tenant
from within MembersController
then it might make a difference for you. Subtle but possibly important, and good to be aware of. I've hit this in real life when I had a Util
module with a String
submodule. I moved a method into Util
and it broke because String
inside that method now referred to Util::String
. I changed some naming conventions after that.
Your Tenant
module will always have MembersController
as a nested class. Anywhere else in your codebase you can refer to AppCore::Tenant::MembersController
. If you want better separation then you should name your model classes differently, or put them inside a module such as AppCore::Model
or similar. If you're using Rails you'll have to buck some conventions, but the configuration required for that is not too bad.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With