Skip to content

Instantly share code, notes, and snippets.

@snuggs
Last active November 18, 2020 22:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save snuggs/f5ab108a8cf596fbf188 to your computer and use it in GitHub Desktop.
Save snuggs/f5ab108a8cf596fbf188 to your computer and use it in GitHub Desktop.
Law of Demeter
def store(model)
# 99 of 100 times we will only need to call #to_h and be done with model
persist model.to_h # Breaks demeter's law
# this usually encourages poking around in the model
# which is no concern of store
end
def store(record) #decoupled
persist record #because that's all we want to do.
# If it doesn't work as expected RTST (Read The Stack Trace)
# will probably say something like "Cannot find keys? in persist:22"
end
store model.to_h #usage
# Now #store() AND model#to_h() can be tested independently of one another.
# with def store(model); model.to_h; end
# an actual model needs to be "let"ed within the context of the store when testing.
# The integration of the two is probably a level up in the abstraction and can be stubbed accordingly.
@tmornini
Copy link

I do not believe line 3 breaks Demeter's law (from Wikipedia)

• 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.

I believe an entity representation qualifies as a friend to a storage engine.

@tmornini
Copy link

As for comment on line 16, with doubles, they can still be tested entirely independently.

@snuggs
Copy link
Author

snuggs commented Mar 17, 2015

True on the line 3 breaking. That's something I added. ;-) An Idea i've been playing with. 6 in one hand and half a dozen in the other. However it does remove the need to pass in a double with the #to_hash method defined when testing. As one can just use the hash they are stubbing. I'm not a fan of CHANGING code just to make the test work (unless it's untestable). Although it does remove one less thing for #store to know about. Nice input Tom. That was more of a passive questioning than a concrete methodology.

@snuggs
Copy link
Author

snuggs commented Mar 17, 2015

I was thinking this. Say you had a toy you wanted a child to play with A toy knife. And we add a feature that childproofs the toy before giving it to the child. Meaning now a toy can have two representations.
usually we’d have:



# what if we want to childproof that toy 
# we’d have to mutate the toy first (which I don’t like)

child.play_with(toy) # calls #to_hash 
toy.childproof! # This is a mutation and now we have to test #childproof! via #to_h. 
# which puts responsibility on #to_h

# or even worse return a boolean.
child.play_with(toy) # then calls to_hash vs.

child.play_with(toy.to_h) # unsafe toy

child.play_with(toy.childproof_to_h) #which needs no "bang" mutator and can be tested directly independently.
# The child has no concern if the toy is safe or unsafe.
# It just wants a representation of the toy to play with.

@tmornini
Copy link

All you need to do is pass in a double that expects #to_h to be called, and returns another double "model_hash"

What does persist do? I assume it calls DB code, which would be doubled-out too.

Here's the method I'm using:

class Store
  def initialize config = { }
    @db = (config[:db_klass] || Pg).new config # allows for double
  end

  def store(model)
    persist model.to_h
  end

  private

  def persist model_hash
    @db.write model_hash
  end
end

@tmornini
Copy link

Human.play child, toy

@tmornini
Copy link

Also, I fucking hate the idea of multiple representations, other than encoding

Better to have a a Toy and a ToyChildProof

Or an attribute, child_proofed (boolean)

@snuggs
Copy link
Author

snuggs commented Mar 17, 2015

def Toybox.return(toy)
   if toy.child_proofed
     return 'OK'
   else
     return 'NOT OK'
  end
end
  # I'd just return toy.status_message and throw the logic in the toy.

@tmornini
Copy link

def Toybox.return(toy)
  fail Exceptions::Forbidden unless toy.child_proofed
end

@tmornini
Copy link

def Toybox.return(toy)
  fail Exceptions::Forbidden unless toy.child_proofed
  @contents << toy
end

@tmornini
Copy link

def handle request
  catch :response do
    dispatch request
  end
rescue
  Responses::ServerErrors::Internal.new
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment