Ruby on Rails Diff Text to HTML <ins> and <del>

This code is perfect if you have 2 text objects in your Rails application and you want to compare their differences in one of your HTML views. It’s 99% pure Ruby too, so if you alter the first line, you can use it for other purposes.

Only one thing to note: you must have diff installed. I’m using: diff (GNU diffutils) 2.8.1.

  1. #set up some variables to reference later
  2. temporary_directory = File.join(Rails.root, "tmp")
  3. max_lines = 9999999 #needs to be larger than the most lines you'll consider
  4. diff_header_length = 3
  5.  
  6. # text_old and text_new should be the values of the string objects to compare
  7. # these are just example strings to show it works
  8. text_old      = "line1\ndeleted line2\nline3\n\nline4\nline5"
  9. text_new      = "line1\ninserted line2\nline3\n\nline4\nline5"
  10.  
  11. # since we're using diff on the file system, we'll save the text we want to compare
  12. # and then run diff against the two files
  13. file_old_name = File.join(temporary_directory,"file_old"+rand(1000000).to_s)
  14. file_new_name = File.join(temporary_directory,"file_new"+rand(1000000).to_s)
  15. file_old      = File.new(file_old_name, "w+")
  16. file_new      = File.new(file_new_name, "w+")
  17. file_old.write(text_old+"\n")
  18. file_new.write(text_new+"\n")
  19. file_old.close
  20. file_new.close
  21.  
  22. # diff will give provide a string showing insertions and deletions.  We will
  23. # split this string out by newlines if there are difference, and mark it up
  24. # accordingly with html
  25. lines = %x(diff -­-­­­­­­unified=#{max_lines} #{file_old_name} #{file_new_name})
  26. if lines.empty?
  27.   lines = text_new.split(/\n/)
  28. else
  29.   lines = lines.split(/\n/)[diff_header_length..max_lines].
  30.   collect do |i|
  31.     if i.empty?  
  32.       ""
  33.     else
  34.       case i[0,1]
  35.       when "+"; then "<ins>"+i[1..i.length-1]+"</ins>"
  36.       when "-"; then "<del>"+i[1..i.length-1]+"</del>"
  37.       else; i[1..i.length-1]
  38.       end
  39.     end
  40.   end
  41. end
  42.  
  43. #clean up the temporary diff files we created
  44. File.delete(file_new_name)
  45. File.delete(file_old_name)
  46.  
  47. #return marked up text
  48. lines.join("\n")

If you fire up RAILS_ROOT/script/console and paste that code in, it will return a nicely marked up string like this:

  1. line1
  2. <del>deleted line2</del>
  3. <ins>inserted line2</ins>
  4. line3
  5.  
  6. line4
  7. line5

Use CSS to make your ins and del tags render however you like.

Tags: , , , ,

8 Responses to “Ruby on Rails Diff Text to HTML <ins> and <del>”

  1. Melvin R. Says:

    Looks pretty cool. If it works as advertised, this is will fantastic. In combination with the plugin that lets you save multiple variations, it will be very useful. Thank you. I’ll turn it into a plugin when I get to using it in my plugin, unless you’ve already done that… have you?

  2. markmcb Says:

    No, I’ve not made it a plugin. Please drop a link to yours though if you make it publicly available. Glad this helped!

  3. tkramar Says:

    Good idea. It might be better to append hash of the file name + current date time than to rely on randomly generated file names. Plus, on my system, a unified diff requires two hyphens

    diff –unified instead of diff -unified

  4. markmcb Says:

    @tkramar, thanks. It is two hyphens … that’s just WordPress thinking for me and deciding that when I type two hyphens that I really want a really long hyphen. I corrected it.

    And yeah, the file name could be anything. I’m just a random sort of guy. And I generally avoid time-stamping files that don’t need to be moved around, archived, etc., as the OS does that for me, i.e., it’s redundant info.

  5. Fabrice Says:

    Hi Mark,

    Just a message to say thanks! Works like a charm :-)

    Fabrice.

  6. MySchizoBuddy Says:

    can it be modified to show visual diff like this image
    http://tiny.cc/fnbiz

  7. Anonymous Says:

    hi, how do this differ from the differ gem on github?

    http://github.com/pvande/differ

  8. B Candler Says:

    Simpler than i[1..i.length-1] is i[1..-1]

    Also: it is safer to use the block form of File so that in the case of exceptions, the file is closed. Even better would be to use Tempfile, and/or to put File.delete in an ‘ensure’ block. Tempfile would also reduce the possibility of the same name being used twice concurrently.

Leave a Reply