The post belongs to NectarCommerce and Extension Framework Awareness Series
- NectarCommerce Vision
- Extension Framework Game Plan
- Introduction to Metaprogramming
- Ecto Model Schema Extension
- Ecto Model Support Functions Extension
- Phoenix Router Extension
- Phoenix View Extension
- Running Multiple Elixir Apps Together
- Extension Approach Explained
- Learning from failures: First Experiment at NectarCommerce Extension Approach
- Developing NectarCommerce Extensions
- Building an exrm release including NectarCommerce
Learning from failures: First Experiment at NectarCommerce Extension Approach
Where we left off
In the past few posts we have learned how to write code that extends existing models, routers, added methods to override view rendering and run multiple phoenix application together in the same umbrella project. Let’s Continue to build upon that and write our first extension for NectarCommerce favorite products and ultimately our store based on NectarCommerce.
A layered guide to NectarCommerce extensions
Index
Setup
Create a new phoenix application to hold the favorite products application, in your shell run inside the umbrella/apps folder:
We could have gone with a regular mix application, but Phoenix/Ecto will be handy in this case, since we want to have views to display stuff and a model to store data.
While we are at it let’s configure our dev.exs and test.exs to use the same db as Nectar, we could write some code and share the db settings between Nectar and our extensions, see: running multiple elixir applications together for more details. But, for simplicity’s sake we are just copying the settings from Nectar to get started.
DB_SETTINGS:
We need to let the extension manager know that this application is an extension for Nectar. Update the dependencies in extension_manager/mix.exs with the favorite_products dependency.
That should be enough to get us going.
MODEL LAYER
We want a Nectar user to have some products to like and a way to remember them, we can use a join table with relations to user and products to achieve this. let’s generate the model:
Now to point to correct Nectar models. Open up the source and change the associations from favorite products model to Nectar models. In the end we have a schema like:
Of, course this is only the extension view of this relationship, We want the Nectar user to be aware of this relationship and most important of all, we should be able to do something like Nectar.User.liked_products(user)
to fetch the products liked by user.
Time to call our handy macros written earlier to perform the compile time code injection. Let’s create the Nectar_extension.ex file in favorite_products/lib/ directory and place this code there:
Don’t forget to update the install file in extensions_manager.
Now we have a user that can like products and product from which we can query which users liked it.
Time to play with what we have built so far, start a shell in Nectar folder iex -S mix
Oops!, forgot the migration, remember we shared the db config earlier let’s put that to use and run:
Which will migrate the user_likes table onto the original Nectar database. Back to our shell
Voila! we can now save and retrieve records to a relation we defined outside Nectar from Nectar models without actually modifying Nectar code.
VIEW LAYER
Now that we can save the user likes, we should probably add an interface for the user to like them as well. Which leads us to the first shortcoming, in our current approach, we can replace existing views but right now we don’t have anything for adding to an existing view(Please leave us a note here if you know of a clean & performant method to do this). I also suspect most of us will end up overriding the existing views to something more custom then updating it piecemeal via extensions. For now let’s have a page where we list all the products and user can mark them as liked or unlike the previously liked ones.
controller
Notice how we use the Nectar.Repo itself instead of using the FavoriteProducts.Repo. In-fact besides migration, we won’t be utilizing or starting the FavoriteProducts.Repo, which will help us keep the number of connections open to database limited only to the Nectar.Repo.
the view file: index.html.eex
In both of the files we refer to routes via NectarRoutes alias instead of favorite products. To add the route from Nectar, update nectar_extension.ex with the following code:
And add to install.ex the calls:
Now we can see the added routes from Nectar:
So far so good, we have modified and added routes and controller to Nectar’s router. Time to see our handiwork in action, start the server from Nectar application with:
And, visit 127.0.0.1:4000/favorite and click on mark to like a product.
But things don’t seem right do they? Our Nectar layout has been replaced with the default one used by phoenix. Let’s rectify that.
Update layout_view.ex as:
and recompile and restart the server
On our next visit:
Much better.
Note: When we need to change the extension code while running the server we will have to recompile and reload. We don’t have anything in Nectar right now to monitor all extensions file and do an augistscripttomatic compilation and code reload.
Testing
We are almost done now. To ensure that we know when things break we should consider adding a few test cases. For that we need to make sure that Nectar migrations are run before running the migrations for favorite products and we need the Nectar repo running as well.
For the former we could update the test_helper.ex with:
But things are not so smooth for accessing the Nectar Repo. Which brings us to what we think is the ultimate downfall of this approach:
An Untestable soution
Ideally, running mix test
should work and we should see our test running green, unfortunately this requires Nectar to be compiled before running the tests, which is impossible since Nectar depends upon the extension_manager to be compiled which depends upon all the extensions to be compiled, resulting in a cyclic dependency. Also we used Nectar’s repo for all database work. That works because we were running our server through Nectar and the repo was started in Nectar’s Supervision tree. Which again adds an implicit requirement i.e. Nectar application is available and ready to be started during test time. We could replace Nectar.Repo
with FavoriteProducts.Repo
using compile time conditionals i.e. when MIX_ENV==test
, but it is a can of worms we would rather avoid right now.
This seems like the end of the road for this approach. Where we are failing right now is making Nectar available to extensions as a dependency at compile time and in turn test time. So that they can run independently. Let’s try that in our second approach and reverse the dependency order.
Our aim with these posts is to start a dialog with the Elixir community on validity and technical soundness of our approach. We would really appreciate your feedback and reviews, and any ideas/suggestions/pull requests for improvements to our current implementation or entirely different and better way to do things to achieve the goals we have set out for NectarCommerce.
Enjoy the Elixir potion !!