I have been getting a LOT of email from the Substruct mailing list recently from software developers asking how I was able to add product customization to Substruct, the ruby on rails shopping cart. To save myself from repeatedly answering these emails, I thought it would be a great idea to post it here. This way, other fellow ruby-ists can find it via google and the ones that do contact me, I can reply with a URL rather than a solution. Its nice when laziness parallels efficiency.
Obviously, this will vary depending on the application. For the application that I built, the products were displayed with a customize button. If the user did not click the customize button the stock product is submitted for order, if he does however click customize, an ajax drop down is rendered below the product with the customization options. So, I'm sure everyone gets the picture, lets get to the code. I had originally posted the code here, but it seemed like it made the post feel littered. It seems like just linking the files for folks to download is a cleaner approach. So, here ya go:
Inside of my display_product, I have the following code:
display_product.rhtml
These are the link_to_remote statements that set your view up for the customizations functionality. You will of course need to have a div specified for you to ajax your customization options into. Now, create a customize_product.rhtml file and drop in the following code. I will put in some example options to get the point across, but you would most likely want to change this.
customize_product.rhtml
Just for the sake of being complete, I thought I would also include my _cart.rhtml partial. Most of the modifications are pretty simple, and including it is most likely not necessary, but I felt it would be best to include it for clarity sake.
_cart.rhtml
That should finish things off for what should be inside the view.. Yes yes, I know that some of this code needs a bit of polishing to get some of the small things to be done the ruby way, but I've been busy and it will give you the idea.. :) Lets get onto the controller code.
store_controller.rb
And moving right along, lets not forget the models.
cart.rb
order_line_item.rb
I had some people asking about the database, here is my schema file:
schema.rb
So, I most likely overlooked some small details when posting everything as its been a while since I have looked at this project. If anyone has any improvements or such, drop a comment below..
I hope this helps all the others that are getting into a Substruct project and need customization functionality, if I forgot anything post me a comment and I'll get it there.
Displaying articles with tag substruct
Posted by Jason, Fri Jan 12 10:19:00 UTC 2007
Posted by Jason, Thu Jan 11 14:13:00 UTC 2007
Lately it seems like I have been building a lot of applications for various clients which build upon open source projects. When a client comes to me and says that they need an e-commerce solution, being a ruby fan I of course launch into my sales pitch on how Substruct can save the world. When they bring up how they also would like to have a collection of images available for browsing I tend to steer them in the direction of Snaps. Of course RForum and Typo are solutions not to be forgotten. It seems that with a little CSS hackery you can pretty easily mask the front-end of all these applications to look the same, and using webrick its easy to build them in parallel on different ports. When all of the individual applications are finished, you now want to wrap everything together so that it appears to be one application, without the nightmare of actually trying to merge these code bases together. This is how I went about doing so..
Lets start with Mongrel. The day I discovered Mongel was the last time I ever said the "f" word. That being fastcgi of course. With an all http environment, its really easy to get used to not having to deal with that mysterious black box that fastcgi really is. Mongrel makes things easy. You can fire up each application, running in production mode with something like:
This will start three Mongrel processes (default) running on port 4245 for the myapp application. You would want to do this for every application that you are trying to wrap together. If you don't want to run three processes, you can also tell Mongrel to fire up one, or two, or ten.. Then, you would set up the following in your Apache (2.2) virtual container, with the appropriate rewrite rules to forward things accordingly:
In the above example, I am wrapping the Typo blog, these rewrites will set the URL to be something like http://blog.myapp.com. You would do the same thing for all of the other applications that you are wrapping, so you could also end up with a http://forum.myapp.com and maybe a http://gallery.myapp.com, with in my case the Substruct store being http://www.myapp.com. Of course you would set all of these applications to run on separate ports, the above example shows the blog running on port 4245. You could set the gallery on 4240, and maybe the forum on 4242.. You get the idea..
Now that you have all of your pieces "Mongreled" together, you may run into a situation where one of these applications needs to get data from another. What if your client wants to have the last two Typo posts appear on the front page of the store application? I am sure that there are a few ways to approach this problem, but the solution I went with was RSS. Rails makes RSS easy, and the simple-rss plugin makes it even easier. You can install this as a gem, and it will set up everything in the plugins directory for you. So if you wanted to display the last two Typo posts on the Substruct home page, inside the Substruct application type:
This will install the simple-rss plugin, which will allow you to make something like the following in your view to capture the XML stream from Typo and display it on Substructs home page. Your view code or partial may look something like:
Inside the controller, this would get the job done:
You must also require the plugin in your environment.rb file. In Substruct, open up your vendor/plugins/substruct/config_stubs/environment.rb file and add:
On the Typo side of things, you need to define a method in your xml_controller (Typo already conveniently has lots of various XML support) to export the XML for Substruct to read. Something like this should work:
We only want the latest two feeds, so we have our controller conditions like so. This will generate a latest_feeds.xml file inside Typo's app/views/xml directory there for the reading.. Bling! That wasn't so hard.. You could expand upon this a lot, as XML can be used for all sorts of stuff, check out REXML if you want to broaden your horizons a little..
Cool stuff ha?
Lets start with Mongrel. The day I discovered Mongel was the last time I ever said the "f" word. That being fastcgi of course. With an all http environment, its really easy to get used to not having to deal with that mysterious black box that fastcgi really is. Mongrel makes things easy. You can fire up each application, running in production mode with something like:
/usr/bin/ruby1.8 /usr/bin/mongrel_rails start \
/var/www/myapp/blog -d false -e production -P \
/var/www/myapp/blog/tmp/pid.txt -p 4245
This will start three Mongrel processes (default) running on port 4245 for the myapp application. You would want to do this for every application that you are trying to wrap together. If you don't want to run three processes, you can also tell Mongrel to fire up one, or two, or ten.. Then, you would set up the following in your Apache (2.2) virtual container, with the appropriate rewrite rules to forward things accordingly:
ServerName blog.myapp.com
ServerAlias blogs.myapp.com
UseCanonicalName On
ServerAdmin example@example.com
DocumentRoot /var/www/myapp.com/blog/public
SetEnv RAILS_ENV production
Options ExecCGI FollowSymLinks
AllowOverride all
Order allow,deny
Allow from all
ErrorLog /var/www/myapp.com/blog/log/production.log
ServerSignature On
RewriteEngine On
RewriteRule ^/$ /index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ http://localhost:4245%{REQUEST_URI} \
[P,QSA,L]
In the above example, I am wrapping the Typo blog, these rewrites will set the URL to be something like http://blog.myapp.com. You would do the same thing for all of the other applications that you are wrapping, so you could also end up with a http://forum.myapp.com and maybe a http://gallery.myapp.com, with in my case the Substruct store being http://www.myapp.com. Of course you would set all of these applications to run on separate ports, the above example shows the blog running on port 4245. You could set the gallery on 4240, and maybe the forum on 4242.. You get the idea..
Now that you have all of your pieces "Mongreled" together, you may run into a situation where one of these applications needs to get data from another. What if your client wants to have the last two Typo posts appear on the front page of the store application? I am sure that there are a few ways to approach this problem, but the solution I went with was RSS. Rails makes RSS easy, and the simple-rss plugin makes it even easier. You can install this as a gem, and it will set up everything in the plugins directory for you. So if you wanted to display the last two Typo posts on the Substruct home page, inside the Substruct application type:
gem install simple-rss
This will install the simple-rss plugin, which will allow you to make something like the following in your view to capture the XML stream from Typo and display it on Substructs home page. Your view code or partial may look something like:
show_by_name.rhtml
<% if @rss %>
<%= link_to "Latest articles from The Blog",
@rss.channel.link %>
<% if @rss.items[0] %>
<%= link_to
@rss.items[0].title, @rss.items[0].link %>
<%= @rss.items[0].description %>
Posted on: <%= @rss.items[0].updated %>
<% end %>
<% if @rss.items[1] %>
<%= link_to
@rss.items[1].title, @rss.items[1].link %>
<%= @rss.items[1].description %>
Posted on:
<%= @rss.items[1].updated %>
<% end %>
<% end %>
Inside the controller, this would get the job done:
content_nodes_controller.rb
# Shows an entire page of content by name
def show_by_name
### RSS
@blog_rss_url = Setting.find(:first, :conditions =>
["title = ?","blog_rss_url"])
begin
@value_url = open(@blog_rss_url.value)
@rss = SimpleRSS.parse @value_url
rescue
@rss = nil
end
if @content_node == nil then
render :file => "#{RAILS_ROOT}/public/404.html",
:layout => false, :status => 404
return
end
# Set a title
if @content_node.title.blank? then
@title = @content_node.name.capitalize
else
@title = @content_node.title
end
# Render special template for blog posts
if @content_node.is_blog_post? then
render(:template => 'content_nodes/blog_post')
else # Render basic template for regular pages
render(:layout => 'main')
end
end
You must also require the plugin in your environment.rb file. In Substruct, open up your vendor/plugins/substruct/config_stubs/environment.rb file and add:
# For RSS feed reader in main page
require 'simple-rss'
require 'open-uri'
On the Typo side of things, you need to define a method in your xml_controller (Typo already conveniently has lots of various XML support) to export the XML for Substruct to read. Something like this should work:
def latest_feeds
@articles = Article.find(:all, :limit => 2,
:order => "published_at DESC")
end
We only want the latest two feeds, so we have our controller conditions like so. This will generate a latest_feeds.xml file inside Typo's app/views/xml directory there for the reading.. Bling! That wasn't so hard.. You could expand upon this a lot, as XML can be used for all sorts of stuff, check out REXML if you want to broaden your horizons a little..
Cool stuff ha?