Unfortunately a lot of old posts have had their users deleted, so the
code above breaks the entire view. I am working on a larger cleanup on
the code, but in the meantime I would like to make a quickfix to get the
views working.
What I would like is to convert
<%=@post.user.name%>
to
<%=(@post.user.name) rescue ''%>
Is there any way to override <%= to do the above? Thus fixing all errors
on the spot.
What I would like is to convert
<%=@post.user.name%>
to
<%=(@post.user.name) rescue ''%>
No it's not. You shouldn't use exceptions to handle non-exceptional
behaviour - and if you know that some posts have had their user
deleted, then it's not exceptional
Besides, this all needs to be done in the model or controller, as the
view should be blissfully ignorant about any data integrity problems
you might have.
Is there any way to override <%= to do the above? Thus fixing all errors
on the spot.
I use a method of substituting a mock object (I'd call it MissingUser
in this instance) which has accessors for each of the properties that
I need from the real AR object that's missing. I then instanciate one
of the mock objects if I request a child object that's missing, by
overloading the call to the associated object, and adding a check that
it exists.
# missing_user.rb
# just a normal Ruby model, but not AR-backed
class MissingUser
attr_accessor :name, :dob # ...etc (any fields you know you need)
# as well as accessors you can set default values for parameters,
so every new instance gets populated with some values
def name
@name || "Missing User"
end
end
# post.rb
class Post < ActiveRecord::Base
has_one :user
alias_method :ar_user, :user # alias the ActiveRecord call
def user
@user || = (ar_user || MissingUser.new) # memoized to save
creating a new MissingUser for every call to the method
# you could also pass in default values as parameters for the
"new" if you wanted - just overload MissingUser's initialize method
end
end
Does that make sense?
I apologise for any typos or mistakes in the code above, as I've just
written it in the email and haven't run it to test. But I hope you can
get the gist from what's there.
Unfortunately a lot of old posts have had their users deleted, so the
code above breaks the entire view. I am working on a larger cleanup on
the code, but in the meantime I would like to make a quickfix to get the
views working.
It does make sense, and this is a process that I am currently working
through. My example was just a single extract from the application,
though, and there are many of these scattered all over the code.
So what I really wants is a quick-fix that makes the presentation-code
"work" until I've fixed the errors the Right Way(tm).
For now I've resorted to
class NilClass
def method_missing(method_name, *args)
''
end
end
That works - if not pretty, then at least it holds my view code together
while I fix it.
class NilClass
def method_missing(method_name, *args)
''
end
end
Overloading method_missing is probable to cause more problems than it fixes
That works - if not pretty, then at least it holds my view code together
while I fix it.
You only need to fix the missing associations one at a time; get a
MissingUser in there and all the nil users will disappear. Then if you
discover another nil association, create the MissingModel for that.
Overloading method_missing is probable to cause more problems than it
fixes
I know - it is a very VERY temporary solution to get peace to do real
fixing.
You only need to fix the missing associations one at a time; get a
MissingUser in there and all the nil users will disappear. Then if you
discover another nil association, create the MissingModel for that.
Yup it is a very neat solution, that I will adapt to. Thanks
No worries. I got it from the "Refactoring: Ruby Edition" book - they
call it the "introduce null object" pattern.
Well worth being familiar with the book for loads of other very good
little patterns.
There is a much easier solution to all of this. Utilize the try()
method that comes with ActiveSupport:
<p>
<%= @post.try(:user).try(:name) %>
</p>
try() calls a method and catches exceptions, returning nil instead.
Try is added to the main Object class, so even nil objects have the
method, hence your ability to chain them together....
I don't see how that's easier than the guard condition that Colin
suggested - but either way it ignores that the OP wanted to deal with
all the occurrences of the problem in one place, rather than go
tweaking loads of views.