So I know of a few different approaches that I am aware of and I want explore the advantages and disadvantages of the various ways for various criteria which are:
- readability
- performance
- ease of debugging
- OO principles (low coupling and high cohesion)
Explicitly using the try method from active support
person.try(:pet).try(:name).try(:upcase)
Using a rescue nil
person.pet.name.upcase rescue nil
Using an && operator chain
person && person.pet && person.pet.name && person.pet.name.upcase
Monkey patching the Object class, see https://gist.github.com/thegrubbsian/3499234 for the original gist
class Object
def try_all(*methods)
values = [self]
methods.each do |method|
value = values.last.try(method)
return nil if value.nil?
values << value
end
values.last
end
end
person.try_all(:pet, :name, :upcase)
Don't have nil safe code, instead validate the data before you call the code
#not a good implementation by any means
def person_has_pet_with_name? person
begin
return true if !person.pet.name.nil?
rescue
return false
end
end
person.pet.name.upcase if person_has_pet_with_name?(person)
My personal opinion about monkey patching in general is do NOT do it unless there is no other way (and even than I think twice if I really want to monkey patch).
Besides Rails already bloated objects a lot. So I'd not suggest custom bloating objects even more.
Why not avoiding to break the law of Demeter by the classic delegator approach:
class Person
attr_accessor :pet
delegate :name_upcased, to: :pet,
prefix: true, allow_nil: true
end
class Pet
attr_accessor :name
def name_upcased
@name.upcase if @name
end
end
@person.try :pet_name_upcased
You can also read about the law of Demeter at Do not break the law of Demeter! and Module#delegate.
At least I would not stick to Object#try as long as a simple condition solves it, because looking at the source of 'try' it is more costly than the condition.
I'd avoid the long call chains as they're a clear violation of the Law of Demeter:
person.try(:pet).try(:name).try(:upcase)
If you'd want to test code like that and somehow stub person, your tests will become extremely complex. I would suggest a solution like the following, where the complexity of this chain is divided between the classes involved. This is the Object-Oriented way. Here, none of the classes knows too much about the other.
class Person
def upcased_pet_name
pet.try(:upcased_name)
end
end
class Pet
def upcased_name
name.try(:upcase)
end
end
# Simple method call. nil checks are handled elsewhere
person.try(:upcased_pet_name)
In your tests, it's now a lot easier to stub person
, and your code is a lot easier to read.
The problem with the Law of Demeter is that your classes know too much about each other, making them more tightly coupled, which in turn makes them more buggy and harder to test.
In my experience, the most frequent violations of the Law of Demeter are in views. Since you are trying to capitalise the name, I'm guessing that's the case here. Soooo....
Can you use a view helper? Sorry, not much good at Rails, so this is pseudocodish:
def upcased_name(entity_with_name)
if entity_with_name != nil
entity_with_name.name.try(:upcase)
end
end
Then in your view, you just call
<% upcased_name(person.pet) %>
You can test the upcased_pet_name
by injecting different values to the viewhelper.
Now:
- Your view knows only that is has access to a 'Person', and access to a viewhelper called
upcased_name
. - Your
Person
model knows only that it has aPet
method. - Your viewhelper knows only that it can receive an entity with a name method, or a
nil
.
Boom! Your classes know only about their friends, and nothing about the friends of their friends.
来源:https://stackoverflow.com/questions/25758830/long-nil-safe-method-chains