Thibaud Clement
Thibaud Clement

Reputation: 6897

Rails 4 x paper_trail: undefined method for nil:NilClass after destroy

I just installed the paper_trail gem in my Rails 4 app.

So, now I have this in my schema.rb:

create_table "versions", force: :cascade do |t|
  t.string   "item_type",  null: false
  t.integer  "item_id",    null: false
  t.string   "event",      null: false
  t.string   "whodunnit"
  t.text     "object"
  t.datetime "created_at"
end

I added paper_trail to my Post model:

class Post < ActiveRecord::Base
  has_paper_trail
end

A post has_many comments and belongs_to a calendar.

I am trying to display a history of recently modified posts in my Calendars#Index view.

I used the official documentation and this tutorial for inspiration.

So, in calendars_controller.rb, I have:

def index
  @user = current_user
  @calendars = @user.calendars.all
  @comments = @user.calendar_comments.where.not(user_id: @user.id).order "created_at DESC"
  @versions = PaperTrail::Version.order('id desc').limit(20)
end

And in my Calendar index.html.erb view, I have:

<h3 class="main_title">Updates</h3>

    <div id="my_changes">

      <% if @versions.any? %>

        <table id="my_change_table">

          <tr>
            <th><span class="glyphicon glyphicon-calendar" aria-hidden="true"></span> CALENDAR </th>
            <th><span class="glyphicon glyphicon-list" aria-hidden="true"></span> POST </th>
            <th><span class="glyphicon glyphicon-user" aria-hidden="true"></span> AUTHOR </th>
            <th><span class="glyphicon glyphicon-edit" aria-hidden="true"></span> CHANGE </th>
          </tr>

          <% @versions.each do |version| %>

          <% post = Post.find_by_id(version.item_id) %>

            <tr>
              <td><%= Calendar.find_by_id(post.calendar_id).name %></td>
              <td><%= post.subject %></td>
              <td><%= User.find_by_id(version.whodunnit).first_name %></td>
              <td><%= version.event.humanize + "d" %></td>
            </tr>

          <% end %>

        </table>

      <% else %>

        <p>There was no change to your posts yet.</p>

      <% end %>

    </div>

This works actually pretty well when a user updates a post.

However, as soon as a user destroys a post, I get the following error:

NoMethodError in Calendars#index
undefined method `item_id' for #<Post:0x007fff22e28c58>
<% post = Post.find_by_id(version.item_id) %>

In fact, this makes sense, since we destroyed the post, it does not exist any more, so we can retrieve its id.

But I thought this was precisely paper_trail's job.

So, I must be missing something.

I tried to use the version.reify and the version.previous methods, but still ran into the same issue.

Any idea how to make this work?

Upvotes: 0

Views: 1098

Answers (1)

davidrac
davidrac

Reputation: 10738

The problem is that after you destroy an object, reify will rerun a new instance of the object, which is unsaved and has no id.

Since it is deleted anyway you should expect Post.find_by_id(version.item_id) to not find it anyway.

Edit

You should be able to get the properties of the original object from the version (see here)

So you can change your code to something like (assuming all of the versions in your system are calendars) and I think it should work:

      <% @versions.each do |version| %>

      <% post = version.reify %>

        <tr>
          <td><%= Calendar.find_by_id(post.calendar_id).name %></td>
          <td><%= post.subject %></td>
          <td><%= User.find_by_id(version.whodunnit).first_name %></td>
          <td><%= version.event.humanize + "d" %></td>
        </tr>

      <% end %>

Upvotes: 1

Related Questions