How Spree handles overriding on the frontend and the backend
A couple months ago, after signing a contract to build an unusual e-commerce site, we were forced to make a choice. We’d build the site either from scratch or on top of existing solutions. After a few days of getting our heads around the topic, we decided to take on the latter option. Since we cherish Ruby, we chose the most popular Ruby based e-commerce engine: Spree.
Couple months ago, after signing a contract to build an unusual e-commerce site, we were forced to make a choice. We’d build the site either from scratch or on top of existing solution. After a few days of getting our heads around the topic, we decided to take on the latter option.
Since we cherish Ruby, we chose the most popular Ruby based e-commerce engine: Spree.
We’ve downloaded the code and within minutes the app was up and running. Here is where we’ve spotted first rocks on the road...
Overriding controller actions
Spree provides tools for tweaking controller’s action behaviour. You can change its response using respond_override
method. However it affects only methods which use respond_with
and yes, not everywhere respond_with
is used. Since it is basically designed to change the output of an action, we can do something like:
Spree::ProductsController.class_eval do
respond_override index: { html: { success: lambda { redirect_to spree.root_path } } }
end
It’s fine if that’s all you need. If, however, you’d like to apply some changes to the object/collection, doing it within the method’s body is not the best idea.
Salvation comes with callbacks. Before and after each database interaction (in controllers) you can invoke callbacks to adjust the object you are working with. All you need to do is extend the controller with decorators (more on that later), add create.after :some_method
and some_method
will be called. It's self explanatory, ain’t it? Strange thing is, there’s no mention about this feature in official Spree docs.
Let me show a quick example that will help you understand what I mean. If for whatever reason, you’d have a has_many :users
relation to Product model and would like to initiate that relation instead of copying the whole action you’d have to do something like this:
Spree::Admin::ProductsController.class_eval do
create.before :product_users
private
def product_users
@object.users.build params[object_name]
end
end
This is elegant enough and can be used in various contexts, e.g. before or after creating an object.
Hint: object_name
is a function inherited from ResourceController. It returns a string containing current object name, which, in this case, will be “product”.
More on decorators
Decorators can be actually used to adjust any method, no matter if it’s in a model or controller. They are easy to use, all you’ve got to do is:
go to a directory where the file we want to extend is placed, e.g.
app/models
create a file matching the following pattern:
#{file_name}_decorator.rb
put a piece of code inside:
Spree::Product.class_eval do # code end
and you are ready to rock this house.
By using this piece of code we make Spree::Product
model and all its methods and associations available for tweaking. We can add new methods and override existing ones. Removing is not allowed, but you can always override a method with an empty one, right?
Anyway, overriding whole methods may be easy, adjusting their behaviour is less elegant though. You need to copy method’s body and make the change inside. Luckily Spree’s methods are usually small, but once in a while you may come across a whale-method and there’s not much you can do about it. Just copy, adjust and forget. Real life example? We had to omit one step in the checkout process, which is handled in Spree by state_machine
. To achieve this we had to copy the whole configuration and ...comment ONE line!
Adjusting views like a ninja
On the frontend things do look much better. Overriding whole views is not necessary, because Spree was equipped with a great tool called “Deface”. It gives a lot of power to the frontend developer, who can:
Change parts of HTML or CSS code in any view
Remove HTML or CSS code
Wrap divs, spans, classes or other elements into his own divs, spans and whatnots
Check this:
Deface::Override.new( name: ‘assasinate_the_spree_logo’,
virtual_path: ‘spree/shared/_header’,
replace_contents: ‘#logo’,
text: “<%= image_tag ‘logo.png’ %>”)
All you need to do is put this piece of code into any .rb
file in app/overrides
directory. Spree will load it automagically. You can put the whole overriding code in one file as well. We chose to name the files accordingly to our views e.g. products_show.rb
, admin_products_index.rb
.
There is another way of handling these overrides, this one will force you to keep the proper directory structure. Let’s give it a try and use Deface::Override
example for this. Here’s what you need to do:
First, in your
app/overrides
directory create the following directories:spree/shared/_header/
,Then create a file named
obliterate_the_spree_logo.html.erb.deface
in_header
folder. A piece of advice: name it whatever you want, but remember that it should not collide withDeface::Override
engine. And don’t forget about.deface
extension!Put this markup inside:
<!-- replace_contents "#logo" namespaced --> <%= image_tag "logo.png" %>
And that’s it! You’ll get the same result as in the previous example.
Please note the “namespaced” option in the code. This should prevent name conflicts. And one last thing: naming files accordingly to views names will result in automagical view override.
You are not alone in the wilderness
Spree offers the power of quick setup, but don’t get yourself fooled. Before you go down the rabbit hole:
Read the code
Read the documentation
Analyze if extending the platform with your planned modules/features won’t be too much of a problem
Make the “Enter the Wonderland” decision a concious one.
Of course there’s plenty of gems and add-ons available. As well as content on stackoverflow. Help is around, but if you plan to change Spree’s flow and functionality strongly, I’d suggest creating the app from scratch instead.
Till next time!
Let's talk about Jamstack and headless e-commerce!
Contact us and we'll warmly introduce you to the vast world of Jamstack & headless development!