Enforcing module contract with self.included?

Hey,

What is the corresponding code in the AgeBasedApprovable module to beget
the behavior in the test below? If it is use of self.included, I’ve not
been able to get it to work…

describe AgeBasedApprovable do
it “requires host object to provide a ‘birthdate’ method” do
host = Object.new
expect do
host.extend(AgeBasedApprovable)
end.to raise_error(/Objects that extend AgeBasedApprovable must
provide a ‘birthdate’ method/)
end
end

Thanks,

Grar

Try self.extended. For instance, the following code should print ‘PASS’
along with the error message:


module AgeBasedApprovable
def self.extended(base)
unless base.respond_to?(:birthdate)
raise ‘Objects that extend AgeBasedApprovable must provide a
birthdate method’
end
end
end

o = Object.new

monkey patch o for testing purposes

def o.birthdate
Time.now
end

this should not raise an error

o.extend(AgeBasedApprovable)

o = Object.new

begin
o.extend(AgeBasedApprovable)
puts ‘FAIL’
rescue => e
puts 'PASS: ', e.message
end


On Dec 11, 2011, at 8:43 PM, Grary S. wrote:

puts ‘FAIL’
rescue => e
puts 'PASS: ', e.message
end

How do I cover this case?

In this case, the proper hook method is Module.included (as you
suggested in the beginning). In order to DRY you should consider moving
the check to a separate method that you call in both self.included and
self.extended.

OK, right.

I still can’t get the test result I expect in this case…

But as a general matter, I have noted the general disdain Rubyists have
for abstract class-like enforcement mechanisms. And it’s true that if I
want to rule out runtime errors by enforcing the presence of certain
parent class methods in any implementing children, then I’ve got to put
all this stuff in the parent to ensure that, as I have been trying to do
with the Module hooks.

ALTERNATIVE APPROACH – which I take to be more Ruby – is to trust the
implementer to follow the documentation for the parent – taking
responsibility for runtime error stuff, themselves. I might try to help
them by adding test examples that they can take from my parent module
and run against their child implementations.

Is my alternative approach the way to go, or do good Rubyists enforce
parent module contracts to avoid runtime errors in the implementing
children?

Grar

Sylvester,

Thanks, that works, but not in another important manner of use…

class SomeImplementingClass
include AgeBasedApprovable
end

begin
SomeImplementingClass.new
puts ‘FAIL’
rescue => e
puts 'PASS: ', e.message
end

How do I cover this case?

Grar

Yes, I understand your proposed approach.

I think your approach is better than the one I was playing with, because
it’s a
bit lazier and more permissive, for example, in the case the child does
not override every method ‘required’ by the parent, they could still use
it to some desired effect.

Yet, under the alternative approach I propose, not even that is required
– the documentation suggests it or shared tests require it.

I don’t know, though, I think it best to let the code express the full
intention of the programmer and not augment with optional struts like
documentation or even tests…

Grar

That’s an interesting question and I’d be interested to read more
opinions on this.

What I’ve done in the past, is to add the required methods to the module
itself. For example, in your case you could add

def birthdate
raise ‘base classes should redefine this method’
end

to your module.

I think the general disdain against strictly type-based enforcements
stems from the fact that Ruby is extremely dynamic; by restricting
behaviour to certain types you’re making it harder (not impossible) to
extend the behaviour or to use it in new or novel ways.

On Dec 11, 2011, at 11:17 PM, Grary S. wrote:

Yes, I understand your proposed approach.

I think your approach is better than the one I was playing with, because
it’s a
bit lazier and more permissive, for example, in the case the child does
not override every method ‘required’ by the parent, they could still use
it to some desired effect.

Yes. In fact, in this case the example didn’t make sense, because I just
exchanged one runtime error for the other. But in many cases you
actually have some benign good value which you could use (in addition
you could print a warning as a reminder that the method should be
redefined) in that method ‘stub’.

Yet, under the alternative approach I propose, not even that is required
– the documentation suggests it or shared tests require it.

Testing is a good point, actually. I find that when you require mocks or
stubs in your tests those instances often arise exactly when there are
requirements that are difficult to express, or are expressed only by
programming-by-contract type of comments/documentation (e.g., base
classes must implement this method, passed in argument must provide a
certain property etc.).

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs