This post is an attempt to get to grips with the different ways to structure Ruby code. It’s very different to the ways to structure different classes because that has already been covered in depth elsewhere. It also is a recap of the basic syntax of modules, so it should have some in-depth opinions for all who are interested in Ruby.
Recently there was a fundamental shift in thinking of talking about how Ruby code is structured, this was away from what you could call classical object oriented thinking into message-based thinking. This was a really big change and it was thanks to Sandi Metz who wrote a a very important book, Practical Object Oriented Design in Ruby. At the same time people like Uncle Bob were talking about the responsibility that programmers have and that nearly all of the time we should now be doing Test-Driven Development. With these two changes in working style it has been easy to overlook some of the patterns that we can use in Rails code, Ruby Gems and in stand-alone Ruby projects. I think it is particularly important not to lose sight of some of these changes. In particular you could think about method nameing conventions all the way to design patterns. Recently DHH reminded us that the MVC pattern was based on designs from the 90’s, this was also true for the ActiveRecord pattern and the work of people like Martin Fowler.
Let’s give a quick recap of changes in thinking in OOP; we went from thinking about how classes were related and making them as loosely coupled as possible to how we can depend on private interfaces because they are reliable and will not change. Why is this important? To me it’s important because when starting out trying to make designs flexible as programmers we usually talk about how if you are trying to test the private methods in a class then it’s a sign, or a smell, that the code should be moved into a different class. Although this code could be tested by using send e.g. @object.send(some_message)
, it’s definitely a code smell because send is from metaprogramming.
I find this quite interesting because it’s a bottom up way of looking at building out classes, in POODR, you get a lot about when thinking about sending messages you can check things like Demeter Violoations (message chaining length), and then creating new interface classes on top of the classes you have. Before moving on it’s important to say that you really should have dependency injection well understood, which is passing an instance of a class in an initializer so that the class being created has access to methods on the class being passed in.
Dependecy inversion in SOLID means changing the order that the classes are dependent in the inheritance structure. (e.g. Triangle sends messages to Shape swapped around to Shape sending messages to Triangle, which conceptually makes less sense but is still possible).
This could also be confused with dependencies in general, for example the code base depends on a Rails version, which depends on Gems (there was a move to make more libraries into a few different Gems), which depend on a Ruby version or a Gem which depends on a native extension such as Nokogiri and libxml2.
Understanding the super keyword also helps, because around the time these changes became more mainstream there was a lot of people saying that super was an anti-pattern, I think they were referring to the use of super everywhere. I would say that this could be true if it’s misused with what we are about to see later on with classes in modules. I think super can and should be used when appropriate and there isn’t any other logic going on in your initializer, using super in a method is possible but I’ve never seen it used.
So what are some of the original language features we had and how are they affected by newer ones. Well, delegators were all the rage about the time that Rails got scopes, but since then we’ve learned that delgators are just hiding the problem. We’ve seen that attr_reader was a way to get access to data in a class, but we actually want to encapsulate that so instead we could declare our attr_readers after the private keyword, we’ve also got mattr_accessor and cattr_accessor which are useful in Rails. We’ve even seen that it can be an ok pattern in some cases to use instance_variable_get and to set and modify instance variables in a class, notably when using other metaprogramming techniques. We can also discover a classes interface by calling .methods
on an Object, which is used in debugging.
Let’s get to the other main tool we can use, modules. Let’s start with mixins, what is a mixin and why is it useful? First off let’s try and pick apart single table inheritance and single inheritance. Ruby as a language does not support inheritance from more than one type of class, you inherit just one class with the <
symbol. Single table inheritance is a Rails feature that allows a model class to inherit from a parent model and have a type. So how do we compose Ruby objects that have multiple inheritance; we can use mixins. We do this by defining a module and then include
methods from a module, we say that these methods have been mixed in and this is how Ruby exhibits features of multiple inheritance, if you include
methods they become instance methods and if you extend
methods they become class methods. There is also a self.included callback which can be used to add behaviour.
The newer features of modules have added prepend
which if you use will overwrite the methods in the class that you have added them to, not the other way round.
ActiveSupport::Concerns are a common way to give additional behaviour to a class in Rails. In concerns we can use included with a block and class_methods with a block to add extra behaviour.