diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 495a432347e3e9f3df69e4c0025625e6f397f6bc..cd8b2911674b78cac69bdee0835f5da98e0f5a22 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -87,7 +87,7 @@ class Projects::BlobController < Projects::ApplicationController
   private
 
   def blob
-    @blob ||= @repository.blob_at(@commit.id, @path)
+    @blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path))
 
     if @blob
       @blob
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 169679279226266f7617eae4698034fa7b68d70b..7143a7448699fd9fafa49507b25f211cb5aaf584 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -127,10 +127,6 @@ module BlobHelper
     end
   end
 
-  def blob_svg?(blob)
-    blob.language && blob.language.name == 'SVG'
-  end
-
   # SVGs can contain malicious JavaScript; only include whitelisted
   # elements and attributes. Note that this whitelist is by no means complete
   # and may omit some elements.
diff --git a/app/models/blob.rb b/app/models/blob.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8ee9f3006b2b1c329fcc0576f2609a1b302bfbc5
--- /dev/null
+++ b/app/models/blob.rb
@@ -0,0 +1,34 @@
+# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
+class Blob < SimpleDelegator
+  # Wrap a Gitlab::Git::Blob object, or return nil when given nil
+  #
+  # This method prevents the decorated object from evaluating to "truthy" when
+  # given a nil value. For example:
+  #
+  #     blob = Blob.new(nil)
+  #     puts "truthy" if blob # => "truthy"
+  #
+  #     blob = Blob.decorate(nil)
+  #     puts "truthy" if blob # No output
+  def self.decorate(blob)
+    return if blob.nil?
+
+    new(blob)
+  end
+
+  def svg?
+    text? && language && language.name == 'SVG'
+  end
+
+  def to_partial_path
+    if lfs_pointer?
+      'download'
+    elsif image? || svg?
+      'image'
+    elsif text?
+      'text'
+    else
+      'download'
+    end
+  end
+end
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index f3bfe0a18b059781d9460ec65af9a87aee196164..3ffc3fcb7ac3e2c91ed6efba29b1ef16ba221076 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -32,14 +32,4 @@
         = number_to_human_size(blob_size(blob))
       .file-actions.hidden-xs
         = render "actions"
-    - if blob.lfs_pointer?
-      = render "download", blob: blob
-    - elsif blob.text?
-      - if blob_svg?(blob)
-        = render "image", blob: blob
-      - else
-        = render "text", blob: blob
-    - elsif blob.image?
-      = render "image", blob: blob
-    - else
-      = render "download", blob: blob
+    = render blob, blob: blob
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index 113dba5d83264714895175c54cf9088364d93258..3c11b97921f5a95ca90ceeea83ab786885f01006 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,5 +1,5 @@
 .file-content.image_file
-  - if blob_svg?(blob)
+  - if blob.svg?
     - # We need to scrub SVG but we cannot do so in the RawController: it would
     - # be wrong/strange if RawController modified the data.
     - blob.load_all_data!(@repository)
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..78e95c8fac51cd4075e9f82e09b38788b904a385
--- /dev/null
+++ b/spec/models/blob_spec.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+describe Blob do
+  describe '.decorate' do
+    it 'returns NilClass when given nil' do
+      expect(described_class.decorate(nil)).to be_nil
+    end
+  end
+
+  describe '#svg?' do
+    it 'is falsey when not text' do
+      git_blob = double(text?: false)
+
+      expect(described_class.decorate(git_blob)).not_to be_svg
+    end
+
+    it 'is falsey when no language is detected' do
+      git_blob = double(text?: true, language: nil)
+
+      expect(described_class.decorate(git_blob)).not_to be_svg
+    end
+
+    it' is falsey when language is not SVG' do
+      git_blob = double(text?: true, language: double(name: 'XML'))
+
+      expect(described_class.decorate(git_blob)).not_to be_svg
+    end
+
+    it 'is truthy when language is SVG' do
+      git_blob = double(text?: true, language: double(name: 'SVG'))
+
+      expect(described_class.decorate(git_blob)).to be_svg
+    end
+  end
+
+  describe '#to_partial_path' do
+    def stubbed_blob(overrides = {})
+      overrides.reverse_merge!(
+        image?: false,
+        language: nil,
+        lfs_pointer?: false,
+        svg?: false,
+        text?: false
+      )
+
+      described_class.decorate(double).tap do |blob|
+        allow(blob).to receive_messages(overrides)
+      end
+    end
+
+    it 'handles LFS pointers' do
+      blob = stubbed_blob(lfs_pointer?: true)
+
+      expect(blob.to_partial_path).to eq 'download'
+    end
+
+    it 'handles SVGs' do
+      blob = stubbed_blob(text?: true, svg?: true)
+
+      expect(blob.to_partial_path).to eq 'image'
+    end
+
+    it 'handles images' do
+      blob = stubbed_blob(image?: true)
+
+      expect(blob.to_partial_path).to eq 'image'
+    end
+
+    it 'handles text' do
+      blob = stubbed_blob(text?: true)
+
+      expect(blob.to_partial_path).to eq 'text'
+    end
+
+    it 'defaults to download' do
+      blob = stubbed_blob
+
+      expect(blob.to_partial_path).to eq 'download'
+    end
+  end
+end