How to Render Plain Text Templates in Ruby on Rails
Tags: rails, ruby, webdev, tutorial
Published: 2022-10-29 15:30:01

If you're trying to get your Rails application to return a plain text file, like `robots.txt` for search engines, here's how you can do it.


If you want a quick solution this will work but I'll cover everything more in-depth and give you more customization below.

---

## Quickest Way to Render a Text File

__1.__ Place your text file in your views directory: 

`app/views/example/robots.txt`

__2.__ Create a route to map the request to your text file: 

`get '/robots.txt', to: 'example#robots'`

__3.__ Add an action to your controller to render this text file:

```
# app/controllers/example_controller.rb
class ExampleController < ApplicationController
    def robots
        render 'example/robots.txt', layout: false, content_type: 'text/plain'
    end
end
```

And you're done, this will map any request to `example.com/robots.txt` to your text file.

Now, if you want more control such as handling other formats or avoiding 404s, read on.

---

## Control How Your Site Handles TXT Files and Formats

## 1. Create the TXT File

First, you need to add the file to your views directory.

You can change the extension of your file from `robots.txt` to `robots.text.erb` in order to avoid having to explicitly render the `robots.txt`. Rails looks for <a href="https://testsuite.io/ruby-templating-engines">ERB templates</a> by default.

Depending on the name of your controller, the file should be located at `app/views/example/robots.text.erb`.

This is where we'll route the request to.

---

## 2. Create the Controller Action

If you're new to Rails, the controller is the actual code your server calls when it gets a request.

By default, it runs any code you specify and renders a view with a matching name.

So if we have a controller we named ExampleController, it should be located at `app/views/controllers/example_controller.rb`. This will ensure it uses our views at `app/views/example/...`.

If all of our files are in the right places and named correctly, this step is really easy.

We just need to create a method in this controller to point to our text file. It can be empty. The name of the method needs to match the name of the file in order to make things easy.

```ruby
class ExampleController < ApplicationController
    def robots
    end
end
```

We set up the controller method (also known as an action) so that Rails knows the file to render.

And that's it for the controller. We don't need to run any code so our method can be empty.

---

## 3. Create a Route For Your Text File

Lastly, routing. You have a few choices here depending on what behavior you want.

### Option 1: Only Support the TXT Extension

If you only want your server to respond to the `txt` format, you can specify that specifically in your `config/routes.rb`.

```ruby
get '/robots.txt', to: 'example#robots'
```

If you attempt a different format, like _example.com/robots_, or _example.com/robots.json_, your server will return a 404 because those aren't defined in your routes.

Requests would have to be exactly _example.com/robots.txt_.

### Options 2: Respond With TXT Regardless of the Requested Format

Another option is to allow all formats but instead of responding with a 404 for unknown formats, we can respond with our TXT anyway.

```ruby
# config/routes.rb
get '/robots', to: 'example#robots'
```

With this approach, there is one additional change we need to make to our controller given it now needs to convert all requested formats to respond with our txt file.

```ruby
# app/controllers/example_controller.rb
class ExampleController < ApplicationController
    def robots
        # Add the line below
        render 'example/robots.text.erb', layout: false, content_type: 'text/plain'
    end
end
```

This controller action will now render our `robots.text.erb` file regardless of the format requested. We specify `layout: false` to avoid rendering the default layout of our other Rails views. And we pass along the <a href="https://testsuite.io/content-type-header">content type</a> of `text/plain` to specify that this is a text file.

### Option 3: Handling Multiple Formats

The last option is to support multiple formats, not just return a text file for everything.

_For example many people do this with their sitemap, you can view it in HTML, XML, anything._

We can easily support multiple formats, but you must remember to create the template for each supported format in your views directory.

That means if you want to support XML, you need a `robots.xml.erb` too. Same for HTML, `robots.html.erb`. (<a href="#mimetypes">See the full list of supported formats.</a>)

If you set up routing for a format you don't have a template for, you will encounter an error like `ActionController::UnknownFormat in Example#robots`.

It will tell you this:

> Example#robots is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: []

Once we have our templates created for each format, we need to make sure our controller maps each requested format to the template we created.

We do this by calling `respond_to` and listing each format we want to support.

```ruby
class ExampleController < ApplicationController
    def robots
        respond_to do |format|
            format.text
            format.html
            format.any { redirect_to '/robots.txt' }
        end
    end
end
```

__Note:__ The `format.any` acts as a catchall, like an "else" statement if none of the previous formats matched.

If you don't want this route to return a 404 for any format you can include it and redirect those bad formats to a format you do expect.

In summary, this controller now responds to any format, `example.com/robots.txt` maps to your `robots.text.erb` file, `example.com/robots` maps to the HTML variant at `robots.html.erb`, and anything else gets redirected to the first `example.com/robots.txt` endpoint.


<a id="mimetypes" name="mimetypes"></a>

---

## All Mimetypes You Can Respond to With Rails

```
"*/*"                      => :all
"text/plain"               => :text
"text/html"                => :html 
"application/xhtml+xml"    => :html
"text/javascript"          => :js 
"application/javascript"   => :js 
"application/x-javascript" => :js 
"text/calendar"            => :ics   
"text/csv"                 => :csv   
"application/xml"          => :xml 
"text/xml"                 => :xml 
"application/x-xml"        => :xml 
"text/yaml"                => :yaml 
"application/x-yaml"       => :yaml 
"application/rss+xml"      => :rss   
"application/atom+xml"     => :atom  
"application/json"         => :json 
"text/x-json"              => :json
```


---

## Errors and Troubleshooting

Common errors and the reasons they come up.

### Missing Template

```
ActionView::MissingTemplate (Missing template /robots.text.erb with {:locale=>[:en], :formats=>[:text], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee]}. Searched in:
```

This means your controller specified a format but you didn't create a template for it. Specifically if you attempted to go to `/robots.txt` this means you don't have a view for that response at `app/views/example/robots.text.erb`.

Create the template and it should work. (But scan for any other templates you might have missed while you're at it.)


### Unknown Format

```
ActionController::UnknownFormat (ExampleController#robots is missing a template for this request format and variant.

request.formats: ["text/plain"]
request.variant: []):
```

This means you received a request with a format that wasn't specified in your controller.

You would get this error if your controller action supports xml and html, but you receive a request with an txt format.

```ruby
    def robots
        respond_to do |format|
            format.xml
            format.html
        end
    end
```

Find your controller action and either add the format to your list or specify a catchall with `format.any`.