4 simple rules are pretty easy to remember, but a bit harder to understand and apply.
A key concept of East-oriented Code is to enforce the use of commands by returning the object receiving a message.
Here's a simple example of what that looks like:
def do_something # logic for doing something omitted... self end
It's incredibly simple to follow.
Here are the rules I set forth in my presentation at RubyConf:
- Always return self
- Objects may query themselves
- Factories are exempt
- Break the rules sparingly
The first three are hard rules. The fourth, obviously, is more lenient. We'll get to some guidance on breaking the rules in the future but for now let's look at applying this to your code.
Rule 1: Always return self
Although this rule is simple at first, it inevitably leads to the queston of getter methods.
What if your objects had no getters? What if an object's
name attribute simply was inaccessible to an external object?
You can make your data private by either marking your
attr_accessors as private:
attr_accessor :name private :name
Or you can use the
private method to mark all of the following defined methods to be private:
private attr_accessor :name
How you choose to do it will depend upon your code, but this would help you remove any getter methods.
Now this leaves you with a conundrum. How do you use the information?
If you have a need for that
name, what can you do?
The only answer is to create a command which will apply the data to the thing you need.
def apply_name_to(form) form.name = name self end
The restricitions we put in our code are often self-imposed.
We can make whatever we want, so what's to stop us from putting Rails model data manipulation in it's view template? Nothing concrete stops us from doing so.
The same goes for getter methods like
name. If it is publicly accessible by external objects, then we can create whatever
case statements we want. We can put logic wherever we want.
If we create our own restrictions, we can guide ourselves and other programmers to the direction we intend for our application's structure.
I've written about the Forwardable library in the past not only because of it's usefulness, but because we can copy the same pattern to create our own DSL.
Forwardable provides methods which create getter methods for related objects. But what if we created our own DSL for commands to related objects? What if we could pass the messages on, but allow the related object to handle the values?
Here's what that could look like:
class Person command :send_email => :emailer end person = Person.find(1) # get some record person.emailer = Emailer.get # get some object to handle the emailing person.send_email
That's a lot of pseudo-code but the parts we care about are sending the command to a related object. Commands return the receiving object, queries will return a value.
Here's what that code would look like without our (yet unimplemented)
class Person def send_email emailer.send_email self end end
Any code which uses a
Person will have to rely on the command to do its own thing. This prevents a programmer from leaking logic out of the person.
What should happen when the email is sent? With the structure above, this code, can't make decisions:
if person.send_email # do one thing else # this will never work now end
If you find that you often write code like the
if statement above, you might wonder "where does that logic go now?" Now, you'll be forced to write this code:
And this means that your
send_email now has the job of handling what to do:
class Person def send_email emailer.send_email # do some other things... self end end
That might provide you with better cohesion; the related behaviors remain together.
Getting back to that
command DSL we used above...
This was the final point of my presentation at RubyConf: you can build guidlines like this for yourself.
I created a gem called
direction to handle enforcing this East-oriented approach. I'll write more about that later, but it shows that I can create signals to other developers on my team. I can take a simple concept like a command and simplify my code to show other developers what's happening:
class Person command :send_email => :emailer end
Building a DSL can aid in communication. The language and terminology we use can compress ideas into easily digestible parts.
If you like this, check out my new book: Ruby DSL Handbook designed to be a guide to help you build your own compressions of concepts.