Reputation: 3568
I'm writing a module to handle payment within our website.
# lib/payment/payment.rb
module Payment
class Payment
# some payment handling
end
end
And I want to separate out the logic that determines the amount due. We'll just say that we're paying for a Car
as an example.
Now, I was under the impression that this would work:
# lib/payment/car.rb
module Payment
class Car < Payment
# builds amount for cars
end
end
But even in the Payment module, with Payment::Payment defined, it is is throwing:
TypeError: superclass must be a Class (Module given)
I'm know that this resolves the issue:
class Payment::Car < Payment::Payment
# stuff
end
But I'm curious as to why my solution wasn't working.
Upvotes: 1
Views: 171
Reputation: 20263
I think you exactly nailed it in your comment. Naming both the module and class Payment
is the problem.
When you have
module Payment
class Car < Payment
end
end
autoload tries (when loading Car
) to load Payment
. The first Constant it comes across in the tree with the name Payment
is a module, not a class. It doesn't know you're trying to load a class called Payment
- so it tries to use the module Payment
. That's why you get the error you get.
When you do
Payment::Car < Payment::Payment
You're explicitley saying you want a class Payment
located in a module Payment
, so now autoload is able to load the correct Constant.
As a follow-on thought, it seems conceptually weird to have Car
inherit from Payment
. Technically, it works. But it doesn't reflect the real world. To enable classes with payment behaviors, you may want to go the composition route. So, make a module something like:
module Payments
module PaymentClassMethods
def some_method
end
def some_other_method
end
end
end
Then do
module Payments
class Car
extend PaymentClassMethods
end
end
Now, Car
doesn't inherit from Payment
, but still has the payment behaviors.
If you also want Car
to get instance methods, you could do something like:
module Payments
module PaymentClassMethods
def some_class_method
end
def some_other_class_method
end
end
module PaymentInstanceMethods
def some_instance_method
end
def some_other_instance_method
end
end
end
module Payments
class Car
include PaymentInstanceMethods
extend PaymentClassMethods
end
end
If you're doing this a lot, you may want to re-open the Car
class so you could just say include PaymentMethods
and get both the class and instance methods. But, that's a bit of a different topic.
Also, if you're including (or extending) methods that interact with models other than Car
, then I'd suggest you consider creating a service object. IMO, models should know very little about one another. If you're Car
model ends up knowing a lot about other models, then you could end up with some tight coupling - which can be a problem if you start monkeying with your Car
model. Again, topic for a separate discussion. But, hopefully the thought helps.
Upvotes: 2
Reputation: 3568
Okay, so it seems like naming a both a module and a class the same thing complicates things.
module A
class B
end
class C < B
end
end
A::C.superclass
=> A::B
But if we name the class the same as the module:
module A
class A
end
class B < A
end
end
TypeError: superclass mismatch for class B
If anyone has faced this sort of thing or has any comments or suggestions, feel free to share!
Upvotes: 0