Welcome to End Point’s blog

Ongoing observations by End Point people

Rails Ecommerce Product Optioning in Spree

A couple of months ago, I worked on an project for Survival International that required two-dimensional product optioning for products. The shopping component of the site used Spree, an open source rails ecommerce project that End Point previously sponsored and continues to support. Because the Spree project is quickly evolving, we wanted to implement a custom solution that would "stand the test of time" and work with new releases. I worked with the existing data structures and functionality as much as possible. The product optioning implementation discussed in this article should translate to other ecommerce platforms as well.

Here's what I mean when I say "two dimensional product optioning".

The first step to extending the core ecommerce functionality was to understand the data model. A single product "has many" option types (size, color). An option type "has many" option values (size: small, medium, large). Each product also "has many" variants. Each variant was tied to an option value for each product option type. For example, each variant would requires a corresponding size and color option value in the example above. Ideally, each variant represents a unique size and color combination.

An *awesome* database dependency diagram.

Using the Spree demo data, I set up the Apache Baseball Jersey to have option types "PO_Size" and "PO_Color". PO_Size contains option values Red, Blue, and Green. PO_Color contains option values Small, Medium, and Large.

Variants assigned to the Apache Baseball Jersey

The second step to producing a two dimensional product option table was to generate the required data in a before_filter method in the controller. Below are the contents of the module that generates the hash in the before_filter method with color and size information. The module retrieves active variants first, then verifies that the required option types are tied to the product. Then, size, color, and variant ids are collected from the active variants producing the data structure described above.

  def self.included(target)
    target.class_eval do
      before_filter :define_2d_option_matrix, :only => :show
  def define_2d_option_matrix
    variants = Spree::Config[:show_zero_stock_products] ? { |a| !a.option_values.empty? } : { |a| !a.option_values.empty? && a.in_stock }
    return if variants.empty? || { |a| a.presentation == 'PO_Size' }.empty? || { |a| a.presentation == 'PO_Color' }.empty?
    variant_ids =
    sizes = []
    colors = []
    variants.each do |variant|
      active_size = { |a| a.option_type.presentation == 'PO_Size' }.first
      active_color = { |a| a.option_type.presentation == 'PO_Color' }.first
      variant_ids[ + '_' +] =
      sizes << active_size
      colors << active_color
    size_sort = Hash['S', 0, 'M', 1, 'L', 2]
    @sc_matrix = { 'sizes' => sizes.sort_by { |s| size_sort[s.presentation] }.uniq,
 'colors' => colors.uniq,
 'variant_ids' => variant_ids }

The code above produces a hash with three components:

  • @sc_matrix['variant_ids']: a hash that maps size and color combinations to variant id
  • @sc_matrix['sizes']: an array of sorted unique sizes of product variants
  • @sc_matrix['colors']: an array of unique colors of product variants

In the view, the output of size and color arrays is used to generate a table. In this hardcoded view, sizes are displayed as the horizontal option across the top of the table, and colors as the vertical option along the left side of the table.

<% if @sc_matrix -%>
<p>Choose your colour, size and quantity below.</p>
<table id="option-matrix">
        <% @sc_matrix['sizes'].each do |s| %>
        <th class="size"><%= s.presentation %></th>
        <td class="spacer"></td>
        <% end -%>
    <% @sc_matrix['colors'].each do |c| -%>
        <th class="color"><%= c.presentation %></th>
        <% @sc_matrix['sizes'].each do |s| -%>
            <% if @sc_matrix['variant_ids'][ + '_' +] -%>
            <input type="radio" value="<%= @sc_matrix['variant_ids'][ + '_' +] %>" name="products[<%= %>]" />
            <% else -%>
            <img src="/images/radio-notavailable.png" alt="X" width="20" height="20" />
            <% end -%>
        <td class="spacer"></td>
        <% end -%>
    <% end -%>
<% elsif #check for other stuff

Here is a comparison of the current variant display method versus two dimensional variant display of the same product:

Current variant display method.

Two dimensional variant display method. Two variants shown here are out of stock.

And here is another example of two dimensional optioning in use at Survival International (more glamorous styling):

Spree extensions are similar to WordPress plugins or Drupal modules that do not typically require you to edit core code. The primary components of the extension are a module with the before_filter functionality and a custom view that overrides the core product view. An extension was created for this functionality and it lives at

Possibilities for future work include editing the extension to be more robust by eliminating the use of the hard-coded option types of "PO_Size" and "PO_Color" and removing the hard-coded size ordering hash. It would be ideal to be able to assign the two dimension option types (horizontal axis and vertical axis) in the Spree admin for each product or a set of products. Another option for future work with this extension includes extending the functionality to multi-dimensional product optioning that would allow you to select more than two option types per product (for example: size, color, and material), but this functionality is more complex and may be dependent on JavaScript to hide and show option types and values.

Learn more about End Point's general Rails development and Rails shopping cart development.


Swistak said...

You might want to check

It allows for everything you showed + much more.
- defining order of option types,
- modifiers to price for option values,
- easy generation of multiple variants from that option values (with price beeing sum of base price and all modifiers).
- If some variants are missing (like you don't have Black XXL tshirts) you can remove variant, and option combination will be disabled (when you click XXL black button will become inactive)
- It's tested

Steph Powell said...


As discussed in the spree user group ( thread on option type extensions, my extension is very simple. I provided it to the community to be used as a simple option type variant display or as a good starting block for someone who wanted to implement customized product variant display.

In my extension, if a variant is out of stock, an "X" is shown so that the user cannot select that item instead of disabling the add to cart.

A user has plenty of control to define the order of option types, it's just that the order would be hard-coded in the extension rather than by using the database. This implementation is not robust, but again this simple extension can be used as a starting block for adding variant and option type functionality.

My extension has been tested and is in use on a live site. It also has no known JavaScript problems. The work completed for the client used JavaScript because it was on an older version of Spree with a different add to cart form. It was compatible with IE6, IE7, IE8, Chrome, FF, and Safari. The code provided here and in this version of the extension does not rely on any JavaScript.

The extension I worked on was initially done for a client, and I wanted to contribute the work to the Spree community by providing good documentation and examples of it's use. If you feel like incorporating some of my extension ideas into your extension, feel free. I think my extension serves as a manageable and useful way to make the variant display more elegant while keeping up with the quickly evolving Spree code base.


Anonymous said...

Hello again.

Since I liked Stephanies idea - A LOT.

I've incorportated 2d table as one of select options for Enchanced option types extension

Concept is really similar, only It should work out of box, you don't need to change any code (no hardcoding of option types/option values).

I hope you like it.

mojo said...

Nice post, I have a question in related to OptionType posted on my blog here:

I hope you could help me out