Building a custom eCommerce application with Spree
Tired of wrestling with Dupal and its evil spawn Ubercart, I recently decided to see what Ruby on Rails has been up to lately. An initial search turned up a few options
Substruct : This looked like it had a lot of potential, but I noticed that it has not had much activity lately. The documentation also was very sparse.
ActiveMerchant : I am definitely going to take a closer look at ActiveMerchant in the future. But for this trek, I wanted something that was more usable off the shelf.
Spree : With an active development lifecycle, and at least some detailed documentation, I decided to see what I could do with Spree.
What I wanted to do was an application that had its own look and feel as well as some custom logic. I began by reading through Spree's extensions tutorial as it was the only document that had some real details to it. After reading the tutorial I had some idea of what to do, but because the tutorial had been written by someone who knew what they were doing already it was missing a lot of the nitty gritty, which I hope to provide here.
Click here for the final product.
Note : A lot of the information here was also reverse engineered from the existing Spree extensions.
Setup : http://spreehq.org/documentation
Step 1 : Create a new extension - a new extension will allow you to override Spree's existing models, views, and controllers or add new ones.
ruby script/generate extension TrcTheme
Step 2 : Create a new extension controller - inside of the new extension I am going to create a new controller that will allow me to override the home page of the site.
ruby script/generate extension_controller TrcTheme newhome index
Step 3 : edit <app name>/vendor/extensions/<extension name>/app/newhome_controller.rb - for my new super cool eCommerce site, I want to feature a single product on the home page. As I was developing I also found it handy to have the web framework automatically update the root /public directory with my custom javascript and stylesheets (something that is normally only done upon startup in Spree). In the code below, the index method picks out a single product to be featured on the home page of the site. The update_assets gives me a url I can load to update the site's /public folder instead of restarting rails. [TODO : have the app automatically watch for changes in the extension's public folder and do the asset update automatically]
class NewhomeController < Spree::BaseController
def index
@products = Product.find(:all)
@featured_product = @products[0];
#flash[:notice] = "A notice!"
#flash[:warning] = "A warning!"
#flash[:error] = "An error!"
end
def update_assets
# copy the assets from extensions public dir into #{RAILS_ROOT}/public
destination = "#{RAILS_ROOT}/public"
paths_to_mirror = Spree::ExtensionLoader.instance.load_extension_roots
@update_ok = true
paths_to_mirror.each do |extension_path|
source = "#{extension_path}/public"
if File.directory?(source)
begin
RAILS_DEFAULT_LOGGER.info "INFO: Mirroring assets from #{source} to #{destination}"
Spree::FileUtilz.mirror_files(source, destination)
rescue LoadError, NameError => e
$stderr.puts "Could not copy extension assets from : #{source}.\n#{e.inspect}"
@update_ok = false
nil
end
end
end
end
end
Step 4 : edit <app name>/vendor/extensions/<extension name>/app/views/newhome/index.html.erb - now I need to create the views to go with the new controller.
<% content_for :title do %>Home<% end %> <%= link_to "<img src=\"" + @featured_product.images.first.attachment.url(:product) + "\" align=\"left\" />", @featured_product %> <%= @featured_product.description %>
Step 5 : edit <app name>/vendor/extensions/<extension name>/app/views/newhome/update_assets.html.erb
<% if @update_ok -%> Asset Update Complete <% else -%> Asset Update Failed <% end -%>
Step 6 : modify<app name>/vendor/extensions/<extension name>/config/routes.rb - now that there is a new controller in the application, we need to map the site's root url "/" and a url for the update_assets function.
map.root :controller => "newhome", :action => "index" map.connect "update_assets", :controller => "newhome", :action => "update_assets"
Step 7 : create <app name>/vendor/extensions/<extension name>/app/views/layouts/application.html.erb - by overriding the application.html.erb file you can give the application its own look and feel.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My Store - <%= yield(:title) || "Spree!" %></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<%= stylesheets %>
<%= stylesheet_link_tag 'trc.css', :media => 'all', :cache => false %>
<%= javascript_include_tag :defaults, :cache => false %>
<%= javascript_include_tag 'jquery-1.3.2.min' %>
<%= javascript_include_tag 'spree' %>
<%= javascript_include_tag 'spree' %>
<%= javascript_include_tag 'curvycorners' %>
<%= javascript_include_tag 'trc' %>
<%= yield :head %>
</head>
<body>
<div id="topbar">
<div id="topbar_left">
<!--elements for the top left of the page could go here-->
</div>
<% if Spree::Config[:allow_locale_switching] %>
<%= render :partial => 'shared/language_bar' %>
<% end %>
</div>
<div id="container">
<div id="header">
<div id="navigation">
<ul>
<li><%= link_to_unless_current "Home", :action => "index" %></li>
<li><%= link_to_unless_current "Products", "/products" %></li>
<% if logged_in? -%>
<li><%= link_to_unless_current t('my_account'), user_path(current_user) %></li>
<li><%= link_to_unless_current t('logout'), logout_path %></li>
<% else %>
<li><%= link_to_unless_current t('log_in'), login_path %></li>
<% end -%>
<% if store_menu? %>
<%= render :partial => 'shared/store_menu' %>
<% end %>
</ul>
</div>
</div>
<div id="wrapper">
<div id="wrap_top_spacer"></div>
<div id="content">
<% if flash[:notice] %>
<p class="notice">
<%= flash[:notice] %>
</p>
<% end %>
<% if flash[:warning] %>
<p class="warning">
<%= flash[:warning] %>
</p>
<% end %>
<% if flash[:error] %>
<p class="error">
<%= flash[:error] %>
</p>
<% end %>
<%= yield %>
</div>
<div id="sidebar">
<div id="extra">
<a href="http://www.theroamingcoder.com/"><img src="/images/TheRoamingCoder_Small.png" /></a><br/><br/>
<%= yield :sidebar %>
</div> <!--/extra-->
</div><!--/sidebar-->
<div id="wrap_bottom_spacer"></div>
</div><!--/wrapper-->
<div id="footer">
<p>This is a demo site for an <a href="http://www.theroamingcoder.com/?q=node/11">article on my blog.</a></p>
</div>
</div><!--/container-->
</body>
</html>Step 8 : I created <app name>/vendor/extensions/<extension name>/public/javascripts/trc.js, but did not wind up using putting any code into the file for the site. Note that it is included via a javascript_include_tag function call in application.html.erb.
Step 9 : create <app name>/vendor/extensions/<extension name>/public/stylesheets/trc.css - note how the stylesheet_link_tag function call is used in application.html.erb to include the stylesheet in the site.
html,body{
margin:0;
padding:0
}
body{
font: 76% arial,sans-serif;
text-align:center
}
a{
color: #981793;
}
div#topbar{
border-width: 0 0 1px 0;
border-style: solid;
border-color: #000000;
height: 20px;
margin-bottom: 40px;
width:100%;
}
div#container{
text-align: left;
width: 800px;
margin: 0 auto;
margin-bottom: 40px;
}
div#header{
color: #000000;
background-color: #CCCCCC;
border-width: 1px 1px 0 1px;
border-style: solid;
border-color: #000000;
height: 22px;
}
div#navigation{
background: #B9CAFF;
padding: 0 0 0 10px;
}
div#navigation ul{
list-style-type: none;
margin: 0;
padding: 0;
}
div#navigation ul li{
display: inline;
line-height: 20px;
}
div#navigation ul li a{
width: 188px;
padding: 4px;
text-decoration: none;
background-color: #003366;
color: #FFFFFF;
}
div#navigation ul li a:hover{
text-decoration: none;
background-color: #336699;
color: #FFFFFF;
}
div#wrapper{
border-width: 1px;
border-style: solid;
border-color: #000000;
}
div#wrap_top_spacer{
}
div#wrap_bottom_spacer{
clear: both;
}
div#content{
float: left;
width: 579px;
padding: 10px;
border-width: 0 1px 0 0;
border-style: solid;
border-color: #000000;
}
div#content p{
line-height: 1.4;
}
div#sidebar{
float: right;
width: 198px;
}
div#extra{
/*background: #B4E7FF;*/
padding: 10px;
}
div#footer{
clear: both;
background-color: #CCCCCC;
border-width: 0 1px 1px 1px;
border-style: solid;
border-color: #000000;
}
div#footer p{
margin: 0;
padding:5px 10px;
}
div#language-bar{
float: right;
}
div#topbar_left{
float: left;
}
Step 8 : modify<app name>/vendor/extensions/<extension name>/config/routes.rb - now that there is a new controller in the application, we need to map the site's root url "/" and a url for the
#language-bar ul {
float: none;
display: table;
list-style-image: none;
list-style-type: none;
margin: 0;
padding: 0;
}
#language-bar li {
display: table-cell;
padding: 2px;
position: relative;
}
.notice{
background-color: #FFFF99;
border-width: 2px;
border-style: solid;
border-color: #FFFF66;
padding: .5em;
}
.warning{
background-color: #FFCC99;
border-width: 2px;
border-style: solid;
border-color: #FFCC66;
padding: .5em;
}
.error{
background-color: #FF0000;
border-width: 2px;
border-style: solid;
border-color: #FF0033;
padding: .5em;
}
Step 10 : For my site to look exactly the way I wanted I needed to override a few of Spree's default views. This was very easy to do by coping the existing view file into my extension's folders, and then editing it. In order to get access to the existing models, views, and controllers for spree you need to find where the Spree gem is installed on your system the "gem environment" command can help you find this folder on your system.
<app name>/vendor/extensions/<extension name>/app/views/shared/_store_menu.html.erb
<li><%= link_to t("cart") , cart_link %></li>
<app name>/vendor/extensions/<extension name>/app/views/shared/_taxonomies.html.erb
<% @taxonomies.each do |taxonomy| %> Shop by <%= taxonomy.name.singularize %> <br/> <% taxonomy.root.children.each do |taxon| %> <%= link_to taxon.name, seo_url(taxon) %><br/> <% end %> <br/> <% end %>
Step 11 : restart app - becuase Spree needs to copy all the files in your extension's /public folder to the public folder for the whole site and it only does this on startup, you need to restart rails. [See the update_assets function above.]
Step 12 : navigate to http://localhost:3000
Click here for the final product. (sorry - removed this link to free up hosting space...)
| Attachment | Size | Hits | Last download |
|---|---|---|---|
| trc_theme.tar_.gz | 17.98 KB | 286 | 20 hours 14 min ago |
Copyright © 2011, Aaron Blondeau
Drupal theme by Kiwi Themes.