Minesweeper: how many mines are near?
If you're using some kind of TDD all classes must be test-covered. Adding classes that are not mentioned in tests at least confusing, at most - leads to unnecessary complexity.

What is the difference between Cell, MineCell, Bordercell and BlankCell? They are storing input/output methods but must be responsible for business logic, I think. They are acting like higher level of Position abstraction. BTW, Position.each - good "trick".

Code is readable and easy to understand.

When I looked through once again, I noticed that this part can be improved: 

def blank?
  false
end
def mine?
  false
end
def border?
  false
end
######
def blank?
  self.is_a?(BlankCell)
end
def mine?
  self.is_a?(MineCell)
end
def border?
  self.is_a?(BorderCell)
end


This methods definitions can be removed from Cell's subclasses.

My way (written in "least resistance way" with tests):

class ValueError &lt; RuntimeError; end

class Board

  def self.transform(input)
    validate(input)
    out = mark_field(substitute(input[1..-2]))
    to_s(out, input)
  end

private

  class &lt;&lt; self
    def mark_field(field)
      field.each.with_index.with_object(field) do |(row, r_indx), obj|
        row.each.with_index do |cell, c_indx|
          increment_cells(r_indx, c_indx, obj) unless cell
        end
      end
    end

    def substitute(input)
      input.map do |line|
        line.gsub(/[^*\s]/, '').chars.map{ |ch| ch == '*' ? nil : 0 }
      end
    end

    def closest_cells(row, col, field)
      arr = (row-1..row+1).flat_map{ |r| [r].product([*(col-1..col+1)])}
      arr.reject{ |r, c| r &lt; 0 || c &lt; 0 || [r, c] == [row, col] || !(field[r] &amp;&amp; field[r][c]) }
    end

    def increment_cells(row, col, arr)
      closest_cells(row, col, arr).each { |r, c| arr[r][c] += 1 }
    end

    def to_s(out, input)
      input[1..-2] = out.map do |line|
        "|#{line.map{ |c| c == 0 ? ' ' : c ? c : '*' }.join}|"
      end
      input
    end

    def validate(input)
      raise ValueError unless input.map(&amp;:size).uniq.count == 1 &amp;&amp;
        input.reject{ |line| line.count('|') == 2 }.size == 2 &amp;&amp;
        !input.join.gsub!(/[^*\s\|\+\-]/, '')
    end
  end

end