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.