I'm making a method inside a Ruby on Rails app called "print" that can take any string and converts it into a png. I've been told it's not good to make class methods for base ruby classes like String or Array or Hash, etc. so "some string to print".print
is probably not something I should do.
I was thinking about making a subclass of String called Print (class Print < String
) and storing it in my lib/assets folder. So it would look like: Print.new("some string to print")
. So my question is, am I on the right track by 1) creating a sub-class from String and 2) storing it in lib/assets?
Any guidance would be greatly appreciated!
Answers to your question will necessarily be subjective because there are always be many answers to "where should I put functionality?", according to preference, principle, habit, customs, etc. I'll list a few and describe them, maybe add some of my personal opinions, but you'll ultimately have to choose and accept the consequences.
Note: I'll commonly refer to the common degenerate case of "losing namespacing scope" or "as bad as having global methods".
Convenient and very "OO-message-passing" style at the cost of globally affecting all String
in your application. That cost can be large because doing so breaks an implicit boundary between Ruby core and your application and it also scatters a component of "your application" in an external place. The functionality will have global scope and at worst will unintentionally interact with other things it shouldn't.
Worthy mention: Ruby has a Refinements feature that allows you to do do "scoped monkeypatching".
Worthy mention 2: Ruby also lets you includes
modules into existing classes, like String.class_eval { include MyCustomization }
which is slightly better because it's easier to tell a customization has been made and where it was introduced: "foo".method(:custom_method).owner
will reveal it. Normal Monkeypatching will make it as if the method was defined on String
itself.
Commonly done in all programming languages, a Util
module is simply a single namespace where class methods/static methods are dumped. This is always an option to avoid the global pollution, but if Util
ends up getting used everywhere anyways and it gets filled to the brim with unrelated methods, then the value of namespacing is lost. Having a method in a Util
module tends to signify not enough thought was put into organizing code, since without maintenance, at it's worst, it's not much better than having global methods.
Suppose you only need it in one class -- then it's easy to just put it into one private method. What if you need it in many classes? Should you make it a private method in a base class? If the functionality is inherent to the class, something associated with the class's identity, then Yes. Used correctly, the fact that this message exists is made invisible to components outside of that class.
However, this has the same downfall as the Rails Helper module when used incorrectly. If the next added feature requires that functionality, you'll be tempted to add the new feature to the class in order to have access to it. In this way the class's scope grows over time, eventually becoming near-global in your application.
Many Rails devs would suggest to put almost all of these utility methods inside rails Helper modules. Helper modules are kind of in between Utils Module and Private Method options. Helpers are included and have access to private members like Private Methods, and they suggest independence like Utils Modules (but do not guarantee it). Because of these properties, they tend to end up appearing everywhere, losing namespacing, and they end up accessing each other's private members, losing independence. This means it's more powerful, but can easily become much worse than either free-standing class/static methods or private methods.
If all the cases above degenerate into a "global scope", what if we forcibly create a new, smaller scope by way of a new class? The new class's purpose will be only to take data in and transform it on request on the way out. This is the common wisdom of "creating many, small classes", as small classes will have smaller scopes and will be easier to handle.
Unfortunately, taking this strategy too far will result in having too many tiny components, each of which do almost nothing useful by themselves. You avoid the ball of mud, but you end up with a chunky soup where every tiny thing is connected to every other tiny thing. It's just as complicated as having global methods all interconnected with each other, and you're not much better off.
Given the options above all have the same degenerate case, you may think there's no hope and everything will always eventually become horribly global -- Not True! It's important to understand they all degenerate in different ways.
Perhaps functionality 1, 2, 3, 4... 20 as Util methods are a complete mess, but they work cohesively as functionality A.1 ~ A.20 within the single class A. Perhaps class B is a complete mess and works better broken apart into one Util method and two private methods in class C.
Your lofty goal as an engineer will be to organize your application in a configuration that avoids all these degenerate cases for every bit of functionality in the system, making the system as a whole only as complex as necessary.
I don't have full context of your domain, and you probably won't be able to communicate that easily in a SO question anyways, so I can't be certain what'll work best for you.
However, I'll point out that it's generally easier to combine things than it is to break them apart. I generally advise starting with class/static methods. Put it in Util
and move it to a better namespace later (Printer
?). Perhaps in the future you'll discover many of these individual methods frequently operate on the same inputs, passing the same data back and forth between them -- this may be a good candidate for a class. This is often easier than starting off with a class or inheriting other class and trying to break functionality apart, later.