This is a companion discussion topic for the original entry at https://thoughtbot.com/upcase/videos/live-coding-session-replace-conditional-with-null-object
Really enjoyed this episode and found it useful, thank you.
Iâve just got one question relating to when you added the last_or_none
method as a private method. Why do you define it as self.last_or_none
, and whatâs the difference between the two in this context? To me it looks like you define a class method, but then you call it on the instance that ActiveRecord returns? If someone wouldnât mind clarifying that for me that would be great!
Thanks,
Jack
@jferris Great stuff as always; I was thinking in particular in the fact that the title of the screencasts says âremove a conditional with a null objectâ which in the end is not exactly what you are doing here, you move the conditional inside a model are there any advantages of moving into that path as far as I can see I see a new abstraction which I now will have to maintain. In another note I have mix feelings to be honest just because now I can see a name thatâs says there is no answer as oppose to a nil traveling around in the stacktrace maybe thatâs exactly the intention of your refactoring; but I rather prefer not to conclude on something and wait for your answer
Thanks
Youâre right - itâs being added as class method. Thatâs what the self.
does in that example.
Class methods are how you generally interact with ActiveRecordâs querying layer. Each ActiveRecord subclass is an API to interact with one SQL table. The most direct way to use that API is to define class methods on ActiveRecord subclasses.
This gets confusing, ActiveRecordâs relation objects - the objects returned by scopes, has_many
associations, and chainable methods like where
- look for class methods. For example, if you call user.posts.published
, this is actually looking for a published
class method on the Post
class.
Thatâs true. In that particular example, weâre just moving a conditional.
However, it also encapsulates the condition, which is useful. This can remove duplication by handling that condition just once. It also can make code easier to understand, because it brings the decision close to the related data.
Using the Null Object pattern is about reducing the number of nil
references being passed around. Because nil
is a failure case and doesnât really follow duck typing or polymorphism, eliminating nil
references will generally eliminate bugs.
Although it does introduce indirection, it also has the effect of reducing the amount of overall noise in code. Passing nil
around results in a lot of extra if
statements, try
calls, and boolean operators like ||
.
I have a general NullObject
question. Iâve been replacing some conditional logic with a NullObject
and am stumped trying to refactor a boolean expression that looks something like this:
@some_var = some_method_that_returns_nil || SomeClass.new
I want it to read more like this:
@some_var = some_method_that_returns_a_null_object || SomeClass.new
But of course that doesnât work, because:
NullObject || SomeClass.new
# => NullObject
Is there any way to override the operator here? Or am I in smell land?
I have the same question.
Youâre in smell land
With null objects, you want to be able to treat them just like real objects. You donât want to have a way to ask them âhey, are you a real object or are you null?â, because that just adds back in the conditional logic that youâre trying to remove.
You had this before:
@some_var = some_method_that_returns_nil || SomeClass.new
I think what you want is this:
@some_var = some_method
def some_method
if something
SomeClass.new
else
NullObject.new
end
end
Do you recommend to always write tests for NullObject type of classes ?
Thanks for this chaps, Iâve implemented null objects in my current project with a NullSubscription
class to make things a little neater when building an account page.
Really cleans things up, cheers for the tip!
Rikki
What about implementing a NullObject that inherits from the base object? Then you would have all methods available, possibly hooking appropriate responses to the ones that prove needed.