Add a "filter" on the ERB <%= ... %>

I have a site with a lot of view code like this:

<p>   <%=@post.user.name%> </p>

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.

Thanks

- Carsten

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.

I have a site with a lot of view code like this:

<p> <%=@post.user.name%> </p>

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%>

<%= @post.user.name if @post.user %>

Colin

Michael Pavling wrote:

Does that make sense?

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.

- Carsten

class NilClass def method_missing(method_name, *args) '' end end

Overloading method_missing is probable to cause more problems than it fixes :slight_smile:

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.

Michael Pavling wrote:

Overloading method_missing is probable to cause more problems than it fixes :slight_smile:

I know - it is a very VERY temporary solution to get peace to do real fixing. :slight_smile:

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

- Carsten

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....

-casey

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.