Creating a product file for Merchant Center in Rails

How to create a product file for Google's Merchant Center in Rails, without any additional gems

In this guide we will walk through all the steps needed to generate a product file for Google’s Merchant Center using Ruby on Rails, without the need for any additional gems.

There are a few different ways Google allows you to communicate with the merchant center, you can create a tab-delimited spreadsheet, you can use an API, or, and what we are going through, you can submit an XML file.

Both the tab-delimited spreadsheet and XML are what they call the “product file”. You can think of this file as a source of truth for all the products you want to appear within the Merchant Center.

Why pick XML

We decided to use XML because creating XML files is baked right into Rails, it has support out of the box which makes serving up XML files feel very natural.

If you’ve never seen an XML file before, it sort of looks like HTML, here is a random example, note that the elements are specific to the purpose of the file (this is storing information about a note between two people).

<?xml version="1.0" encoding="UTF-8"?>
<note>
    <to>Tina</to>
    <from>John</from>
    <subject>Reminder</subject>
    <body>Don't forget the meeting at 10 AM tomorrow.</body>
</note>

High level actions

Let’s run through some of the things we will be doing;

Find an appropriate controller action

If you are selling products, you hopefully have some sort of product list view, if we assume you have a ProductsController then the index action would be the best place for us to put our code.

We need to tell this action to respond to XML requests, as well as HTML (the default) requests.

For this we use respond_to, which takes a block and a series of things to do based on the format of the request.

class ProductsController < ApplicationController
  def index
    @products = Product.live

    respond_to do |format|
      format.html
      format.xml { render layout: false }
    end
  end
end

What we are saying here is “when HTML, act like normal”, and “when XML, render the view, but without any layout around it”.

We do this because for XML, we don’t want our application.html.erb code to top and tail our code.

It is worth noting that your code might include pagination logic or other filtering which you may need to split out for our XML view (which we don’t need to paginate or limit as much).

Next up, we need to create the view

Create a new controller action

If you don’t have the equivalent of a products#index, you should make one. You could use the above code and just remove the format.html section from respond_to, since we don’t care about showing an HTML version of this feed.

One thing to consider when making a new action, instead of borrowing an existing one, is that you likely have some sort of scope to suggest a product is live or some other type of filtering. We only want to generate the XML for products we eventually wish to appear in Google’s listing.

Create the view

Using Google’s sample XML as a guide, we need to create something XML-shaped in our view.

We want to create a file called index.xml.builder inside app/views/products, or whatever is appropriate for your resource set up.

The end file will look like the below, but don’t worry, we will talk through the main elements of this.

# frozen_string_literal: true

xml.rss 'xmlns:g': 'http://base.google.com/ns/1.0', version: '2.0' do
  xml.channel do
    xml.title 'Our title'
    xml.description 'Our description'
    xml.link products_url

    @products.each do |product|
      xml.item do
        xml.g :id, product.id
        xml.g :title, product.title
        xml.g :description, strip_tags(product.description)
        xml.g :link, product_url(product)
        xml.g :image_link, product.image.url
        xml.g :condition, 'new'
        xml.g :availability, 'in_stock'
        xml.g :price, "#{product.price} #{product.main_currency}"
      end
    end
  end
end

Chained file names

First up, lets talk about the file name, index.xml.builder. This will look familiar if you use something like erb, you might even have a file in the same directory called index.html.erb.

Rails works from right to left when processing view files, passing the file through different formatters in that order.

In our case, our file will get processed by builder first, then xml.

So, what is “builder”?

The Builder gem

Builder is a a gem that is required by ActionView. Builder provides an easy way to build XML documents and fragments.

The structure of the file

We start with a root element;

xml.rss 'xmlns:g': 'http://base.google.com/ns/1.0', version: '2.0'

This tells the file to use the RSS specification version 2.0 and that the namespace to use is “g” which is defined at the URL provided.

If you’re wondering why RSS, it is because this XML document is being treated as a feed of information, which RSS is perfect for.

Next up we need a wrapper for everything, as well as three things common to all RSS type feeds; a title, a description, and a link. The link should be to the HTML version of your shop.

xml.channel do
  xml.title 'Our title'
  xml.description 'Our description'
  xml.link products_url

Now we get into the individual items, so we iterate over our @products collection and for each one put out their properties.

@products.each do |product|
  xml.item do
    xml.g :id, product.id
    xml.g :title, product.title
    xml.g :description, strip_tags(product.description)
    xml.g :link, product_url(product)
    xml.g :image_link, product.image.url
    xml.g :condition, 'new'
    xml.g :availability, 'in_stock'
    xml.g :price, "#{product.price} #{product.main_currency}"
  end
end

Of note here you’ll see xml.g :id, this will generate an XML element that looks like <g:id>.

Read the Product data specification guide to learn more about the specific attributes you should include, and the format expected for them.

Hook up any new routes

If you were able to hook the XML view into an existing controller action, then you shouldn’t need a new route. If your products#index is accessed via mysite.com/shop, then visiting mysite.com/shop.xml should work.

If you needed to create a new controller or action, you will need to create the appropriate route.

resources :products only: :index

Submit your feed

This article doesn’t cover testing this feed, because how you test will depend quite a lot on your setup and I don’t want to make assumptions. At the very least you should check that visiting your development environment can generate the file correctly and with the information you’d expect.

All being well, we should now be able to visit /products.xml, or the route you’ve created, and see a well formatted XML file ready for submission.

Because of how we’ve set this up, any time Google crawls the XML file, it will get the most up to date information.

You can set how frequently this is crawled from within the Merchant Center.


Recent posts View all

Web Dev

Creating draft posts in Jekyll

How to create and develop with draft posts in Jekyll

Ruby

Forcing a Rails database column to be not null

How you can force a table column to always have something in it with Rails