In Progress
Unit 1, Lesson 1
In Progress

# Uniform Abstraction Level Part 2: Blank

Video transcript & code

In the last episode, we were working on this `Course` class.

``````Course = Struct.new(:name, :duration) do
def name=(new_name)
self[:name] = Name(new_name)
end

def duration=(new_duration)
self[:duration] = Duration(new_duration)
end
end
``````

Both attributes of this domain model now automatically filter user input to convert it into Whole Value. A Whole Value, as you may recall from episode #401, is a value object that in the words of Ward Cunningam, "captures the whole quantity with all of its implications".

By way of demonstration: when we assign a new name to a course object, it gets auto-converted to a `Name` object.

One of the special abilities of this Whole Value object is that it is capable of telling us whether it is an exceptional value. This is something we make use of in our views and controller code.

Likewise, we can assign a duration, and see it converted to a Whole Value that also knows it is un-exceptional.

And if we provide input that can't be parsed into a known type of duration, we get an `ExceptionalValue` that holds onto the original user input, but also lets view code know there's a problem with this field.

``````require "./models_uniform"

c = Course.new
c.name = "Coffee Quaffing 101"
c.name                          # => Name(Coffee Quaffing 101)
c.name.exceptional?             # => false
c.duration = "20 days"
c.duration                      # => Days(20)
c.duration = "a few weeks"
c.duration                      # => #<ExceptionalValue:0x00555b5db96370 @raw="a few weeks", @reason="Unrecognized format">
c.duration.to_s                 # => "a few weeks"
c.duration.exceptional?         # => true
c.duration.reason               # => "Unrecognized format"
``````

So at this point we've raised all of the possible values this model can contain to an equivalent, high level of abstraction. We're no longer dealing in primitives like strings and integers. These are rich, semantically meaningful objects that carry useful information that view code can make good use of.

Or, at least, that's the theory. The truth is, we've missed something. Can you guess what it is?

There's one other possible type for `Course` attributes. When we first instantiate a `Course`, both its `name` and `duration` are `nil`.

And since, in episode #432, we removed the global `Object` monkey-patch that used to kludge support for this method onto every object, `nil` no longer responds to `exceptional?`.

``````require "./models_uniform"
require "./course_noblank"

c = Course.new
c.name      # => nil
c.duration  # => nil
c.name.exceptional?             # => NoMethodError: undefined method `exceptional?' for nil:NilClass

# ~> NoMethodError
# ~> undefined method `exceptional?' for nil:NilClass
# ~>
# ~> xmptmp-in160262Bv.rb:7:in `<main>'
``````

This is going to cause problems when the form is showing a brand-new, blank course.

So what do we do now? I mean, we could monkey-patch just `NilClass`, but we just got rid of a monkey-patching solution.

Let's talk about what the `nil` value means here. Does it simply meanâ€¦ `nil`? Of course not. It has a specific meaning in this context.

As Sandi Metz is fond of saying, in object-oriented design, nothing is something. What does this nothing mean? I'd say it's a flag or placeholder indicating a particular state of the `name` or `duration` fields.

What state is that, exactly? Well, what do we call it when a physical, paper form has yet to be filled out?

"Blank", exactly! The `nil` stands in for a blank field. Now that we've identified the implicit concept, let's create an object for it!

We'll go ahead and derive it from our `WholeValue` base class.

Remember, a blank value should not be considered exceptional. It may not be valid for a finalized `Course` to have blank fields. But `ExceptionalValue` objects are only for representing user input values that the system was unable to interpret or coerce. So it's appropriate that this class inherit the `WholeValue` implementation of `#exceptional?`.

We also add a `to_s` converter that just returns an empty string.

``````class Blank < WholeValue
def to_s
""
end
end
``````

Now we need to make sure that a new `Course` has `Blank` fields instead of `nil` fields. We do this with a customized initializer, being careful to call through to the `super` inherited from `Struct`.

Then, after the `super` initializer has had a chance to initialize fields, we use struct's square-bracket assignment to default the fields to `Blank` values.

(If this square bracket business looks weird to you, you might want to watch the episode on `Struct`, #20.)

``````Course = Struct.new(:name, :duration) do
def initialize(*)
super
self[:name]     ||= Blank.new
self[:duration] ||= Blank.new
end

def name=(new_name)
self[:name] = Name(new_name)
end

def duration=(new_duration)
self[:duration] = Duration(new_duration)
end
end
``````

And now we've finally arrived at our goal: a `Course` class that deals entirely in domain-level concepts. A brand new `Course` has blank fields.

After assignment, the name and duration fields are filled with Whole Value objects that encapsulate the semantics of their respective contents.

And all of these values can be queried for whether they are exceptional.

``````require "./models_uniform"

c = Course.new
c.name      # => #<Blank:0x0056361d1c7150>
c.duration  # => #<Blank:0x0056361d1c7100>

c.name = "Thumbtwiddling 400"
c.name                          # => Name(Thumbtwiddling 400)
c.duration = "2 months"
c.duration                      # => Months(2)

c.name.exceptional?             # => false
c.duration.exceptional?         # => false
Course.new.name.exceptional?    # => false
``````

At this point, let's fire up our server.

We click to add a new course.

Just as in previous episodes, the fields show up blank. But this time, we know they are backed by objects that explicitly represent the `Blank` status.

We fill in a name, and an invalid duration, and submit.

We get the same form back again, with an problem report.

This time we enter a recognizable duration, and successfully submit the form.

Let's take one last look at the model class powering this interaction.

In a conventional ActiveRecord-style model, this model would contain validation declarations in order to verify the format of the `duration` field and report any problems to the view layer. But this model has remained blissfully free of any validation code so far, because it delegates the interpretation of valid input values to Whole Value collaborators.

That's not to say that we won't ever have to deal with validations at the business model level. There are some validations which are specific to the model and don't make sense to push down to the field value level. But for right now, it's interesting to see how far we've gotten without any kind of traditional validation machinery.

Now, it's possible that you're staring at this and saying: "that just looks weird and different from any domain modeling I've ever done". Unfortunately, the domain layer in a lot of modern application frameworks is so infected by nitty-gritty database semantics, that talking in terms of "strings" and "integers" and other primitive types just seems like the "normal" thing to do.

What I've tried to give you a taste of in the last few episodes is classic OO domain modeling as elucidated by Ward Cunningham, one of the great minds in object-oriented design or indeed in programming in general. I hope it inspires you to revisit how you model domain concepts in your applications.

If you're intrigued by this approach, I know that there's a good chance you're now wondering how to pragmatically bring it into the Ruby on Rails world. That's a topic I hope to tackle in some future episodes. But for now, happy hacking!