Connect With Us

How To Implement Law Of Demeter In Ruby

By Sarbada Nanda Jaiswal on Aug, 11, 2016 in ruby, developers, programmers, rails, jyaasa, best practices, law of demeter, delegate

Most of the developers who are digging deeper into proper way to build software might have had problem in understanding the law of demeter in first go. Here is our attempt to simplify this for the new generation of developers.

You can read the formal definitions here :
C2wiki: http://c2.com/cgi/wiki/LawOfDemeter?LawOfDemeter
Wikipedia: https://en.wikipedia.org/wiki/Law_of_Demeter

How To Implement Law Of Demeter In Ruby

The law of demeter fundamentally describes following things: 

  • Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
  • Each unit should only talk to its friends; don't talk to strangers.
  • Only talk to your immediate friends.

In our context as programmers, an object should avoid calling on methods of a member object returned by another method. For many modern object oriented languages that use a dot as field identifier, the law can be stated simply as “use only one dot”. That is, the code a.b.Method() breaks the law where a.Method() does not.

Ruby on Rails Bad smell code:

class Project < ActiveRecord::Base
  belongs_to :user
end

project.user.name
project.user.address
project.user.cellphone

These lines break law of demeter. To solve this we should use methods like as:

project.user_name
project.user_address
Project.user_cellphone

Now, this follows the law of demeter. But if we use methods for each field then we need to add bunch of methods in the model.

class Project < ActiveRecord::Base
  belongs_to :user
  def user_name
    user.name
    end
  def user_address
    user.address
  end
  def user_cellphone
    user.cellphone
  end
end

class User < ActiveRecord::Base
  has_many :projects
end
 

To remove above bad smell Rails provides a helper method delegate which utilizes the DSL way to generates the wrapper methods. This is the best practice.

delegate:

The concept of delegation is to take some methods and send them off to another object to be processed.

Options:

:to - Specifies the target object
:prefix - Prefixes the new method with the target name or a custom prefix
:allow_nil - if set to true, prevents a NoMethodError to be raised

class Project < ActiveRecord::Base
  belongs_to :user
    delegate :name, :address, :cellphone, to: :user, prefix: true, allow_nil: true
end

class User < ActiveRecord::Base
  has_many :projects
end

project.user_name

project.user_address

This is how the method can now be accessed with delegate enabled.

Now, we do not break the law of demeter. Due to addition of allow_nil if the project  does not have any user then we will just get nil when calling name and no exception!

Now we know what the law of demeter is . But why use it ?. Most of the blog posts out there does not answer this specific question. What are the benefits or advantages of implementing law of demeter in our ruby softwares?.

In general , Law of demeter is an example of loose coupling which states that one component should not heavily rely on other components for getting the job done. 
Not when we decrease this coupling. Things are much much easier to change.

But what about the famous method chaining we are used to ?

But  If we followed it all the time we could never do method chaining

Method chains are what makes ruby well ruby !  As an example, here’s a method which takes a string and generates a “slug” for generating a friendly url:

def slug(string) 

  string.strip.downcase.tr_s('^[a-z0-9]', '-‘) 

end

That’s three levels of method call chaining. That does not follow the Law of Demeter. But the law never says anything about the number of methods called, or the number of objects a method uses. It is strictly concerned with the number of types a method deals with.

The #slug method expects a String, and calls three methods, each one returning… another String. In fact, because it only calls methods for the type of object (String) passed into it as parameters, we can conclude that it follows  the Law of Demeter.

References and Bibliography 

  • http://rails-bestpractices.com/posts/2010/07/24/the-law-of-demeter/
  • http://haacked.com/archive/2009/07/14/law-of-demeter-dot-counting.aspx/
  • http://www.virtuouscode.com/2011/07/05/demeter-its-not-just-a-good-idea-its-the-law/

Share This:

Comments

Jeff Dickey

The Law of Demeter (it's a name, so always capitalised) will save your bacon when applied to your own code, or to third-party code you use in your app (e.g., Gems). Less so for language standard libraries. As (I believe) "Uncle" Bob Martin phrased it, LoD applies to code which is subject to change; when was the last time you ran across a breaking change in the standard library from one version of Ruby to the next?

In my experience, force-applying LoD to standard-library code such as your example

def slug(string)
  string.strip.downcase.tr_s('^[a-z0-9]', '-‘) 
end

is mostly a mistake. I say "mostly" as a too-quick read of code can hide things that may plausibly change over time. Going back to the #slug example: is this the only place this logic will be applied? (If not, we likely want to DRY things up by Extracting a Method or Extracting a Class to handle the details).

Let's look at the different steps involved and see what's subject to change?

  1. #strip: Nope; the standard method is very stable and every single "slug" implementation I've ever seen requires that leading/trailing whitespace be trimmed;
  2. #downcase: Similar to #strip in this context; forcing consistent case reduces the possibility of false negatives through typos;
  3. #tr_s. While the standard method is very stable, the regex being passed in makes the reader stop and mentally parse it, and could conceivably change (that's a pretty draconian limitation IME).

How would I do something like that (or "how have I done something like that") when given that legacy code to maintain? After satisfying myself that it is covered by tests sufficient to catch any breakage (such as characters being improperly replaced or not), I'd do one of two things.

If the existing code is largely procedural, not making use of simple utility classes or objects, I'd probably write

def slug(string)
  _cleanup_slug(_slugify(string: string))
end

def _cleanup_slug(slug_string)
  slug_string.strip.downcase
end

def _slugify(string:, unacceptable: '^[a-z0-9]', replacement: '-')
  string.tr_s(unacceptable, replacement)
end

If the existing code is relatively modern, making use of numerous small utility classes and objects, I'd probably do it this way:

# Get "slug" for string, with all characters matching an "unacceptable" pattern
# being replaced by a "replacement" character.
class Slugify
  def self.call(string:, unacceptable: '^[a-z0-9]', replacement: '-')
    _slugify(_normalise(string), unacceptable, replacement[0])
  end

  def self._normalise(string)
    string.strip.downcase
  end

  def self._slugify(string, unacceptable, replacement)
    string.tr_s(unacceptable, replacement)
  end
end

def slug(string)
  Slugify.(string: string)
end

Note that these refactorings aren't primarily concerned with the Law of Demeter; it's more concerned with leaving clues to the next maintainer about what's being done and why. Here, we inject values for replacement and unacceptable with for-now-sensible defaults; those can be overridden if necessary without touching the code. Similarly, the ._normalise method does some basic "cleanup" that's intrinsic to our understanding of the concept of a "slug", while the ._slugify method is the "main logic" that simply uses values that are passed into it. Easy to understand; easy to test; easy to maintain. What's not to like?

 August 12, 2016
kuber aaganja
Awesome blog learn many things
 August 24, 2016
Anonymous
I just couldn't go away your web site prior to suggesting that I really loved the usual information an individual provide on your visitors? Is gonna be again incessantly in order to check up on new posts http://yahoo.co.uk
 August 25, 2016
I’ll right away seize your rss feed as I can not in finding your email subscription hyperlink or newsletter service. Do you have any? Kindly let me recognise so that I could subscribe. Thanks.
I’ll right away seize your rss feed as I can not in finding your email subscription hyperlink or newsletter service. Do you have any? Kindly let me recognise so that I could subscribe. Thanks. http://Yahoo.Co.uk/
 September 14, 2016

Add a new comment

Latest Posts


Design Sprint By Neha Suwal on Aug, 14, 2017

3 years of helping startups and entrepreneurs with technology By Neha Suwal on Aug, 04, 2017

Digital Marketing for Startups By Neha Suwal on Jun, 30, 2017

Jyaasa in Prestige Talks By Neha Suwal on Jun, 16, 2017

Helping digital era entrepreneurs: How Jyaasa helped create a tech startup success story in Sydney. By Kapil Raj Nakhwa on May, 29, 2017