Ruby (And Rails) Nested Module Syntax

Ruby (and Rails) nested module syntax

In the first example, it defines the Parent module and then the Child module. The second example, as you say yourself, must have the Parent module defined before hand. At the expense of one more line of code, you ensure that the module that you're nesting under by using your first example is always going to be defined.

For a Rails example let's look into the railties/lib/rails/engine.rb file which re-opens the Rails module and then defines an Engine class inside of it. This could have been done with simply:

class Rails::Engine

But instead perhaps for the reasons stated above and perhaps also for clarity's sake, the module was defined first, then the class inside.

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

Nested modules in Rails breaking table_name_prefix

One solution, which doesn't rely on autoloading, is to set the models to inherit from the following, instead of from ActiveRecord::Base directly:

class CustomActiveRecordBase < ActiveRecord::Base
self.abstract_class = true

# If no table name prefix has been defined, include the namespace/module as
# table name prefix, e.g., Blog:: -> blog_
def self.table_name
# If a table_name_prefix has been defined, follow default behaviour
return super if full_table_name_prefix.present?

# Find the prefix, e.g., Blog::Post -> 'blog', User -> ''
prefix = model_name.name.deconstantize.underscore

# If no prefix, follow default behaviour
return super unless prefix.present?

# Otherwise add the prefix with an underscore
"#{prefix}_#{super}"
end
end

Then there is no need to define self.table_name_prefix in blog.rb.

These can be set as defaults for future models through appropriate files in lib/templates/active_record/model/model.rb and module.rb, based on the default active record templates.

This could all be done by monkey-patching ActiveRecord::Base, but this interferes with other classes, such as ActiveRecord::SchemaMigration, which doesn't have a table prefix.

Ruby/Rails nested module with same name

You can extend top level module Bar and use :: constant prefix to start looking in the global namespace:

module Bar
def hello
puts('hello from top level Bar')
end
end

module Foo
module Bar
extend ::Bar

def self.run
hello
end
end
end

Foo::Bar.run # >> hello from top level Bar

Or without extend:

module Bar
def self.hello
puts('hello from top level Bar')
end
end

module Foo
module Bar

def self.run
::Bar.hello
end
end
end

Foo::Bar.run # >> hello from top level Bar

Why do people use nested notation for modules rather than :: syntax?

I agree and I sometimes use the :: syntax. Note however that the semantics for constant resolution are different for the two syntaxes so they aren't exactly equivalent:

module A; module B; module C; end ;end ;end
module A; A_CONST=42; end

module A::B::C; puts A_CONST; end
#^ NameError: uninitialized constant A::B::C::A_CONST

module A
module B
module C
puts A_CONST
end
end
end
#^ prints 42

How to access a top-level module from deep within nested modules in Ruby?

I worked out the answer to this. You need to prepend A with :: to access the top level explicitly.

module ::A
class ClassA
def MethodA
puts "You have been overridden!"
end
end
end

Now, MethodA has truly been overriden and will output "You have been overridden!"

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.

Preference of code style in module nesting

These two methods of writing get confused quite often.

First is to say, that to my best knowledge there is no measurable performance difference. (one constant look-up in the below written example)

The most obvious difference, probably the most known, is that your second example (module A::B) requires for the module A to exist in the time of the definition.

Otherwise than than that most people think they are interchangeable. That is not true.

Modules are simply constants in ruby and so regular constant look-up applies.

Let me show it on an example:

module A
class Test
end

module B
class Show
p Module.nesting # =>[A::B::Show, A::B, A]

def show_action
Test.respond_with(%q(I'm here!))
end
end
end
end

On the other hand if you call it via the A::B look what happens:

module A
class Test
end
end

module A::B
class Show
p Module.nesting # => [A::B::Show, A::B]

def show_action
Test.respond_with(%q(I'm here!))
end
end
end

The difference is that .nesting produces:

1) in the first case: [A::B::Show, A::B, A] (you are nested in module A)

2) in the second case: [A::B::Show, A::B] (here not)

Re-opened nested module anomaly in Ruby

The module keyword sets a namespace context that is checked for references to existing names of Modules. These namespaces are then searched inner-to-outer to resolve references to Module (and Class) names.

In your first example, it looks like you may need to define E.e inside module E block, but in fact you don't:

module A
module E
end
end
module A
def E.e
end
end

What happens in both your examples is that Ruby looks at the current namespace, and tries <namespace>::E as a module name. So in both examples, the first thing it checks is in fact A::E::E which does not exist. Then it falls back to the next context. Which is where the examples differ: In the first example it is A::E which is valid, in the second example, it is just E which is not. The error that it then throws relates to the first name it checked.



Related Topics



Leave a reply



Submit