Skip to content

Extension Approach 2

Pikender Sharma edited this page Mar 31, 2016 · 1 revision

This approach of adding Nectar Extensions and building the Use Store is built around having having nectar, extensions and user store application in a single umbrella project

  • Nectar

    • Provides the E-commerce model and resources (This is the core code which forms the nectar commerce framework). Modification to this code should be avoided.
  • ExtensionManager (will be a part of default install, meant to be modifiable by user)

    • Provides a basic DSL and install point for 3rd party extensions to use
    • Add the third party extensions as dependencies and follow their install instructions.
  • Extensions

    • 3rd Party code to add / enhance Nectar feature, for example wallet or wishlist.
    • They can be anything ranging from a payment gateway to a full fledged phoenix app.
  • User Store

    • The launch point for the application, all apps that need to be run when starting the store need to present here.
    • User code goes here as well as a root forward to nectar. Please see the example code for this.

Any modification to core code should be preferably done only via the Extensions application and modifying nectar commerce source should be avoided to maintain compatibility with future releases.

Dependencies

  • User Store depends upon Nectar + Extensions which need to run. Not all extensions need to run, some may just modify nectar at compile time and may have no role at runtime.

  • ExtensionManager depends upon Extensions that need to modify nectar core.

  • Extensions may themselves rely on nectar if needed, this is only required if some component of nectar is required in the extension at compile time. for example they may want to do so to use nectar models in pattern matching or reusing Nectar.Web in their own phoenix apps. For all other cases aliases and direct references should be sufficient.

  • Note: reusing Nectar.Web, also has the sometimes useful side effect that Extensions themselves become open for extension by other Extensions.

ExtensionsManager

Regular Mix project with

  • Special macros to provide a DSL to extend schema, inject functions in Nectar Models and add routes in Nectar Router
  • Help consolidate extensions to be available to extend Nectar.
  • Knowledge of all registered extensions.
  • Should always be present in umbrella, but is not required/loaded at runtime.

Model Extension

  • lib/extensions_manager/model_extension.ex
    • Provides a DSL to extend Model schema present in Nectar App and inject support functions in Model

    • Model Schema extension

      • add_to_schema(schema_change, name, type, options)

      • schema_change can be a field in Model or an association

        add_to_schema(:has_many, :liked_by, through: [:likes, :user])
        add_to_schema(:has_many, :likes, FavoriteProducts.UserLike, [])
    • Inject Model functions

      include_method do
        def liked_products(model) do
          from like in assoc(model, :likes),
          preload: [:liked_products]
        end
      end

Router Extension

  • lib/extensions_manager/router_extension.ex
    • Provides a DSL to add routes in Nectar App as needed by extension

      ```elixir
      define_route do
        scope "/", FavoriteProducts do
          pipe_through [:browser, :browser_auth] # Use the default browser stack
          resources "/likes", FavoriteController, only: [:index, :create, :delete]
        end
      end
      ```
      

Install / Hook Extensions to Nectar

  • lib/extensions_manager/install_extensions.ex

    • Provides an interface for extensions to talk to Nectar and convey their requests of schema_changes and function_inclusions

    • Provides an interface for extensions to add routes in Nectar Router

      defmodule ExtensionsManager.ExtendProduct do
        use ExtensionsManager.ModelExtension
        use FavoriteProducts.NectarExtension, install: "products"
      end
      
      defmodule ExtensionsManager.ExtendUser do
        use ExtensionsManager.ModelExtension
        # note multiple extensions can be installed via this process.
        use FavoriteProducts.NectarExtension, install: "users"
      end
      
      defmodule ExtensionsManager.Router do
        use ExtensionsManager.RouterExtension
        use FavoriteProducts.NectarExtension, install: "router"
      end
  • mix.exs

    • add extensions as dependency in deps

      defp deps do
        [{:favorite_products, in_umbrella: true}]
      end

Note: The naming convention in install.ex is utilized by Nectar to search for modules to add. Please see the convention document for more details

Nectar

Changes in Nectar to enable and hook the extensions through Extensions Application

  • web/web.ex

    • model definition is enhanced to include use Nectar.Extender to enable the discovery of extensions during compilation. this module uses Code.ensure_loaded to load the extension into memory if available instead of acquiring it via a dependency.

      defmodule Nectar.Web do
        def model do
          quote do
            ...
            use Nectar.Extender
            ...
          end
        end
      end
  • Schema block/definition in all models will have extensions function to help extend schema from extensions. This only allows for adding to schema right now.

    defmodule Nectar.Product do
      ...
      schema "products" do
        ...
        extensions
        ...
      end
      ...
    end
  • web/router.ex

    • uses Nectar.RouteExtender to search for and add the extension routes into the nectar router.

      defmodule Nectar.Router do
        ...
        use Nectar.RouteExtender
      end

Sample Extension - Favorite Products

This Extension is created as a regular Phoenix Apps to provide additional views and models to Nectar along with an extra module in lib/favorite_products/nectar_extension.ex which defines model and route extension install steps using the DSL provided by ExtensionManager application.

All the other files as needed in model, controller, views, templates, migrations are added & worked upon as in any regular phoenix project

Favorite Products Extension needs

  • Model
    • a join table to store user favorited products
    • put in web/models
  • Migration to create new joins table with user_id and product_id
    • put in priv/repo/migrations
  • Product and User Model Schema Extension to inject associations to be aware of likes_join table
  • Ensure the database configured is same in nectar and the extensions.
  • User Facing Favorite Controller
    • shows user favorite products
    • allows mark / unmark product as favorite
    • put in web/controllers, web/views, web/templates
  • Admin Facing Favorite Controller
    • show all favorited products
    • shows users who favorited particular product
    • put in web/controllers/admin, web/views/admin, web/templates/admin
  • Routes to Expose User / Admin Facing Favorite Controller

Product and User Model Schema Extension to inject associations to be aware of likes_join table

  • Extension must define the install steps for:
    • schema_changes
    • support functions
    • routes to be registered with full scope mentioning pipelines to be used
  • Don't forget to include favorite_products as umbrella dependency to extensions
# lib/favorite_products/nectar_extension.ex
# Add it next to endpoint.ex under phoenix app lib folder

defmodule FavoriteProducts.NectarExtension do
  # note there will be a convention/dsl for writing this file
  # to make it more readable and uniform across all extensions
  defmacro __using__([install: install_type]) do
    do_install(install_type)
  end

  defp do_install("router") do
    quote do
      define_route do
        scope "/", FavoriteProducts do
          ## Do not forget to add the pipelines request should go through
          pipe_through [:browser, :browser_auth]
          resources "/favorites", FavoriteController, only: [:index, :create, :delete]
        end

        scope "/admin", FavoriteProducts.Admin, as: :admin do
          ## Do not forget to add the pipelines request should go through
          ## Note the `admin_browser_auth` pipeline instead of `browser_auth`
          pipe_through [:browser, :admin_browser_auth]
          resources "/favorites", FavoriteController, only: [:index]
        end
      end
    end
  end

  defp do_install("products") do
    quote do
      ## In Phoenix App Model Schema definition, join association is defined first and then through association
      ## Please note the reverse order here as while collecting, it gets the reverse order and
      ## injected into actual model schema as expected .. tricky huhh !!
      add_to_schema(:has_many, :liked_by, through: [:likes, :user])
      add_to_schema(:has_many, :likes, FavoriteProducts.UserLike, [])
      include_method do

        def like_changeset(model, params \\ :empty) do
          model
          |> cast(params, ~w(), ~w())
          |> cast_assoc(:likes) # will be passed the user id here.
        end

        def liked_by(model) do
          from like in assoc(model, :liked_by)
        end
      end
    end
  end

  defp do_install("users") do
    quote do
      add_to_schema(:has_many, :liked_products, through: [:likes, :product])
      add_to_schema(:has_many, :likes, FavoriteProducts.UserLike, [])
      include_method do

        def liked_products(model) do
          from like in assoc(model, :liked_products)
        end
      end
    end
  end
end

POC Implementation

Run mix clean && mix compile umbrella root and then start the phoenix server from iex -S mix phoenix.server from User Store app. Note there will a second round of compilation at this point which loads the extensions into nectar. The compile process will need to be repeated after adding any new extension to the umbrella.

  • Confirming Model Schema Extension, associations in the IEx shell with project loaded.

    alias Nectar.Repo
    alias Nectar.User
    alias Nectar.Product
    # Add an entry in user_likes join table manually for valid product and user
    # Change the User and Product id as available in DB
    u = Repo.get(User, 1) |> Repo.preload([:liked_products])
    p = Repo.get(Product, 1) |> Repo.preload([:liked_by])
  • Hit through Browser http://localhost:4000/favorites

    iex(4)> [info] GET /favorites
    [debug] Processing by FavoriteProducts.FavoriteController.index/2
      Parameters: %{}
      Pipelines: [:browser, :browser_auth]
    [info] Sent 200 in 329µs
    

##Refer working favorite_products extension code here##

##Installation Instructions##

  1. The current code base is configured to use postgres. Please see the adapter configuration in /apps/favorite_products/config/dev.exs and /apps/user_app/config/dev.exs more details, Please see here on how to configure nectar.
  2. Once configuration is complete. run mix deps.get && mix clean && mix compile from root of the umbrella project.
  3. Then migrate the code by running mix ecto.migrate -r Nectar.Repo then mix ecto.migrate -r FavoriteProducts.Repo.
  4. Seed the data with mix run apps/nectar/priv/repo/seeds.exs.
  5. Change directory to cd apps/user_app and run npm install and bower install.
  6. And start the phoenix server with mix phoenix.server .
  7. Visit localhost:4000/favorites to see the extension in action.