When to use nested classes and classes nested in modules?
Other OOP languages have inner classes which cannot be instantiated without being bound to an upper level class. For instance, in Java,
class Car {
class Wheel { }
}
only methods in the Car
class can create Wheel
s.
Ruby doesn’t have that behaviour.
In Ruby,
class Car
class Wheel
end
end
differs from
class Car
end
class Wheel
end
only in the name of the class Wheel
vs. Car::Wheel
. This difference in name can make explicit to programmers that the Car::Wheel
class can only represent a car wheel, as opposed to a general wheel. Nesting class definitions in Ruby is a matter of preference, but it serves a purpose in the sense that it more strongly enforces a contract between the two classes and in doing so conveys more information about them and their uses.
But to the Ruby interpreter, it’s only a difference in name.
As for your second observation, classes nested inside of modules are generally used to namespace the classes. For instance:
module ActiveRecord
class Base
end
end
differs from
module ActionMailer
class Base
end
end
Although this is not the only use of classes nested inside of modules, it is generally the most common.
Why can't ruby classes inside modules have the same scope as regular classes?
I have a module with a class inside,
No, you don't. You have a module definition with a class definition inside, but that does not make the class a nested class. Ruby does not have nested classes.
Ruby is not Beta, Scala, or Newspeak, there are no nested classes in Ruby.
Nesting a module or class definition inside another module or class definition does not create nesting relationship between the two classes / modules. It only makes the constant which references the class / module part of the outer class' / module's namespace.
In other words, there is no difference between
module Foo
class Bar
end
end
and
class Quux
end
module Foo
Bar = Quux
end
Only the constant is nested, but not the object that is referenced by the constant.
but I find that the class inside can't reach any of the methods in the enclosing module without specifying the module path.
That is precisely because there is no "enclosing module". There is a lexically enclosing module definition but that does not create any form of relationship whatsoever between the Foo
class and the MyMod
module.
Another way to look at it is that the module_function doesn't seem to carry into the class.
I honestly don't understand what you mean by that, what it means for a method to "carry into a class", but Module#module_function
is not magic. It does exactly what the documentation says it does: it takes an instance method of the module, copies it as an instance method of the singleton class of the module, and makes the original instance method private
.
You can read its specification in the Ruby/Spec, it is fairly simple. Also, the Rubinius source code, both the basic version for booting the Rubinius kernel and the full version are fairly readable.
In the end, Module#module_function
really does not do much more than
class Module
def module_function(*meths)
meths.each do |meth|
define_singleton_method(meth, &instance_method(meth).bind(self))
private meth
end
self
end
end
If you run this, you get an error on the "But I can't..." line of:
undefined local variable or method `meaning'
The reason is simple: neither the class Foo
nor any of its superclasses has any method of that name, so of course you get an exception.
But if you remove the MyMod bits around the whole file, it has no problem accessing the outer method.
There is no "outer method". Ruby does not have Beta-like nested classes. That is really the fundamental cause of your misunderstanding. You expect Ruby to behave like Beta, but it just doesn't. Ruby takes inspiration from any languages, most notably (in rough order of importance) Smalltalk, Lisp, Perl, and Clu, but Beta is not among them.
This here works for a completely different reason:
def meaning
42
end
class Foo
def initialize
meaning
end
end
Methods that are defined at the top-level are implicitly defined as private instance methods of Object
. This is because the default definee at the top-level is ::Object
. Since Foo
inherits from Object
, method lookup will eventually find the meaning
method defined in Object
.
Is there an easy way to make these accessible without having to give the full path?
Inheritance. For example, Module#append_features
, which is called by Module#include
, makes the module the superclass of the including class, and thus all instance methods of the module become part of the method lookup ancestry chain.
An aside: if there is no nesting, then what does Module::nesting
do? Well, yeah, that is an unfortunately named method. The term "nested class" or "nested module" has a well-defined meaning in OO going all the way back to Beta. But this method is about a completely different kind of nesting:
It refers to the lexical nesting of module definitions, and not to nesting of modules themselves.
For example, these module definitions all define the exact same module, but the definition text has different nesting:
module Foo
module Bar
module Baz
module Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo::Bar, Foo]
end
end
end
end
module Foo
module Bar
module Baz::Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar, Foo]
end
end
end
module Foo
module Bar::Baz
module Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo]
end
end
end
module Foo::Bar
module Baz
module Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo::Bar]
end
end
end
module Foo
module Bar::Baz::Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo]
end
end
module Foo::Bar::Baz
module Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz]
end
end
module Foo::Bar
module Baz::Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar]
end
end
module Foo::Bar::Baz::Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux]
end
Again, this is purely lexical nesting of the module definition. The module itself is not nested; the module itself is the same in all of these cases. This nesting only affects constant lookup.
Constants are looked up first lexically outwards in enclosing module definitions, then upwards the inheritance chain.
There is another instance where things can be nested: blocks create nested lexical scopes, whereas all other lexical scopes (script, module / class definition, and method definition) don't nest. In other words, blocks and only blocks have access to the local variables (and self
) of their enclosing lexical scopes.
Ruby: nesting classes in classes same as nesting classes in modules?
Yes, it's essentially the same as nesting in module. Nested class does not have any kind of special relationship to the class it's nested in.
Nesting modules in classes and directly calling module functions
There are some fundamental misconceptions of how Ruby OOP works in your example, and without a full code sample and the opportunity to interrogate you about what you're trying to accomplish it's hard to guide you to what might be the most appropriate answer. Any answer I give will be based partly on experience and partly on opinion, so you may see other answers as well.
At a high level, you should have classes in modules and not modules in classes. Although you can put modules in classes you better have a good understanding of why you're doing that before doing it.
Next, the modules and methods you've defined in them do not automatically become accessible to instances of the parent class, so client.Bikes
will never work because Ruby expects to find an instance method named Bikes
inside the Api
class; it won't look for a module with that name.
The only way to access the modules and module methods that you have defined is to use them at the class/module level. So if you have this:
class Foo
module Bar
def baz
puts 'foobarbaz'
end
end
end
You can do this at the class/module level:
Foo::Bar.baz
foobarbaz
=> nil
But you can't do anything at the instance level:
Foo.new::Bar.baz
TypeError: #<Foo:0x00007fa037d39260> is not a class/module
Foo.new.Bar.baz
NoMethodError: undefined method `Bar' for #<Foo:0x00007fa037162e28>
So if you understand so far why the structure of your example doesn't work, then you can work on building something a little more sensible. Let's start with naming and the class/module structure.
First, Api
is a poor name here because you'll typically use Api
for something that provides an API, not connects to one, so I would recommend making the name a bit more descriptive and using a module to indicate that you are encapsulating one or more related classes:
module MonthyApiClient
end
Next, I'd recommend adding a Client
class to encapsulate everything related to instantiating a client used to connect to the API:
module MonthyApiClient
class Client
def initialize
@client = nil # insert your logic here
@connection = nil # insert your logic here
end
end
end
The relationship between client
and connection
in your code example isn't clear, so for simplicity I am going to pretend that they can be combined into a single class (Client
) and that we are dropping the module Authentication
entirely.
Next, we need a reasonable way to integrate module Bikes
and module Phones
into this code. It doesn't make sense to convert these to classes because there's no need to instantiate them. These are purely helper functions that do something for an instance of Client
, so they should be instance methods within that class:
module MonthyApiClient
class Client
def initialize
# insert your logic here
@client = nil
@connection = nil
end
def create_bike
# insert your logic here
# e.g., @connection.post(something)
end
def delete_bike
# insert your logic here
# e.g., @connection.delete(something)
end
def create_phone
# insert your logic here
# e.g., @connection.post(something)
end
end
end
Note that we've swapped new
for create
; you don't want to name a method new
in Ruby, and in the context we're using this new
would mean instantiate but do not save a new object whereas create
would mean instantiate and save a new object.
And now that we're here, and now that we've eliminated all the nested modules by moving their logic elsewhere, we can see that the parent module we set up originally is unnecessarily redundant, and can eliminate it:
class MonthyApiClient
def initialize
# insert your logic here
@client = nil
@connection = nil
end
def create_bike
# insert your logic here
# e.g., @connection.post(something)
end
def delete_bike
# insert your logic here
# e.g., @connection.delete(something)
end
def create_phone
# insert your logic here
# e.g., @connection.post(something)
end
end
Then you can accomplish your original goal:
client_one = MonthyApiClient.new
client_one.create_bike
client_two = MonthyApiClient.new
client_two.create_phone
Having worked through this explanation, I think your original code is an example of spending a lot of time trying to over-optimize prematurely. It's better to plan out your business logic and make it as simple as possible first. There's some good information at https://softwareengineering.stackexchange.com/a/80094 that may help explain this concept.
I've even skipped trying to optimize the code I've shown here because I don't know exactly how much commonality there is between creating and deleting bikes and phones. With this functional class, and with a better understanding of other code within this app, I might try to DRY it up (and that might mean going back to having a module with a Client
class and either module methods or other classes to encapsulate the DRY logic), but it would be premature to try.
Your last question was about how to structure files and directories for modules and classes, and I would refer you to Ideal ruby project structure (among many other questions on this site) for more information.
Understanding Module.nesting in Ruby and the types of Module Nestings
In Ruby, nesting is a syntactic construct. In other words, it has to do with how the code is actually written out in the script. The difference in nesting is obvious if you ignore the semantics by removing the names, and just look at the syntax:
module ...
class ...
...
end
end
# as opposed to
class ...
...
end
One is a class inside a module, the other one is just a class. Semantically, both can refer to the same object XML::SAXParser
, but nesting doesn't care about that.
This matters in Ruby because constant lookup is performed using nesting, as opposed to method lookup which uses the semantic object relationships.
A = "top level"
module XML
A = "module level"
class SAXParser
puts A # module level
def self.a
"defined in a class in a module"
end
end
end
class XML::SAXParser
puts A # top level
puts a # defined in a class in a module
end
Modularity in Java: top level vs. nested classes
a non-static nested class has an implicit reference to the creator instance of the enclosing class, and also it can access every member of the enclosing class (even private members). You lose this if you make the nested class top-level:
public class Outer {
private String s;
public void setS(String s) {
this.s = s;
}
public class Inner {
public String getOuterS() {
// This is legal only if Inner is
// non-static and nested in Outer
return s;
}
}
}
public class Main {
public static void main(String[] args) {
Outer o = new Outer();
o.setS("Hello world!!!");
// i now has access to every o member
Outer.Inner i = o.new Inner();
// Prints "Hello world!!!"
System.out.println(i.getOuterS());
}
}
Nested classes versus compact in Ruby
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.
Related Topics
Safe Navigation Operator (&.) for Nil
Ruby Regex Error: Incompatible Encoding Regexp Match (Ascii-8Bit Regexp with Utf-8 String)
How Does Require Rubygems Help Find Rubygem Files
Heroku: Gemfile.Lock Is Required Issue
What Does &: Mean in Ruby, Is It a Block Mixed with a Symbol
How to Improve Jruby Load Time
Why Does Array.Each Behavior Depend on Array.New Syntax
New Rails Project: 'Bundle Install' Can't Install Rails in Gemfile
Ruby-Debug19 on Ruby-1.9.3-Preview1
Problems Setting a Custom Primary Key in a Rails 4 Migration
Deleting a Modified Object from a Set in a No-Op
How to Push to Faye Server from Rails Controller
Escape Single Quote in Xpath with Nokogiri
Ruby: Combine Date and Time Objects into a Datetime
Setting Path for Whenever in Cron So It Can Find Ruby
Keep Getting Oauth::Unauthorized Error When Using Oauth and Twitter Ruby Gems