-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Demonstration of Extension Approach 2 #47
Changes from all commits
d3fe0fc
c991971
96b4529
e78b477
4bafcf2
ffdf592
7b37b0f
c29d4eb
fa3ed4e
0307ace
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/_build | ||
/cover | ||
/deps | ||
erl_crash.dump | ||
*.ez | ||
|
||
|
||
# Vim Swap Files | ||
*.swp | ||
/**/*.swp | ||
/**/**/*.swp | ||
/**/**/**/*.swp | ||
/**/**/**/**/*.swp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Extensions Manager |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# This file is responsible for configuring your application | ||
# and its dependencies with the aid of the Mix.Config module. | ||
use Mix.Config | ||
|
||
# This configuration is loaded before any dependency and is restricted | ||
# to this project. If another project depends on this project, this | ||
# file won't be loaded nor affect the parent project. For this reason, | ||
# if you want to provide default values for your application for | ||
# 3rd-party users, it should be done in your "mix.exs" file. | ||
|
||
# You can configure for your application as: | ||
# | ||
# config :extensions_manager, key: :value | ||
# | ||
# And access this configuration in your application as: | ||
# | ||
# Application.get_env(:extensions_manager, :key) | ||
# | ||
# Or configure a 3rd-party app: | ||
# | ||
# config :logger, level: :info | ||
# | ||
|
||
# It is also possible to import configuration files, relative to this | ||
# directory. For example, you can emulate configuration per environment | ||
# by uncommenting the line below and defining dev.exs, test.exs and such. | ||
# Configuration from the imported file will override the ones defined | ||
# here (which is why it is important to import them last). | ||
# | ||
# import_config "#{Mix.env}.exs" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
defmodule ExtensionsManager do | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
defmodule ExtensionsManager.ExtendProduct do | ||
use ExtensionsManager.ModelExtension | ||
use FavoriteProducts.NectarExtension, install: "products" | ||
end | ||
|
||
defmodule ExtensionsManager.ExtendUser do | ||
use ExtensionsManager.ModelExtension | ||
use FavoriteProducts.NectarExtension, install: "users" | ||
end | ||
|
||
defmodule ExtensionsManager.Router do | ||
use ExtensionsManager.RouterExtension | ||
use FavoriteProducts.NectarExtension, install: "router" | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
defmodule ExtensionsManager.ModelExtension do | ||
defmacro __using__(_opts) do | ||
quote do | ||
Module.register_attribute(__MODULE__, :schema_changes, accumulate: true) | ||
Module.register_attribute(__MODULE__, :method_block, accumulate: true) | ||
|
||
import ExtensionsManager.ModelExtension, only: [add_to_schema: 4, add_to_schema: 3, include_method: 1] | ||
@before_compile ExtensionsManager.ModelExtension | ||
|
||
defmacro __using__(_opts) do | ||
quote do | ||
import unquote(__MODULE__), only: [extensions: 0, schema_changes: 0, include_methods: 0, method_blocks: 0] | ||
@before_compile unquote(__MODULE__) | ||
end | ||
end | ||
|
||
defmacro __before_compile__(_env) do | ||
quote do | ||
include_methods | ||
end | ||
end | ||
end | ||
end | ||
|
||
defmacro add_to_schema(method, name, through) do | ||
quote bind_quoted: [name: name, through: through, method: method], location: :keep do | ||
Module.put_attribute(__MODULE__, :schema_changes, {method, name, through}) | ||
end | ||
end | ||
defmacro add_to_schema(method, name, type, options) do | ||
quote bind_quoted: [name: name, type: type, options: options, method: method], location: :keep do | ||
Module.put_attribute(__MODULE__, :schema_changes, {method, name, type, options}) | ||
end | ||
end | ||
|
||
defmacro include_method(methd) do | ||
block = Macro.escape(methd) | ||
quote bind_quoted: [block: block] do | ||
Module.put_attribute(__MODULE__, :method_block, block) | ||
end | ||
end | ||
|
||
defmacro __before_compile__(_env) do | ||
quote do | ||
defmacro extensions do | ||
quote do | ||
Enum.map(schema_changes, fn | ||
({:field, name, type, options}) -> field name, type, options | ||
({:has_one, name, type, options}) -> has_one name, type, options | ||
({:has_many, name, type, options}) -> has_many name, type, options | ||
({:has_many, name, through}) -> has_many name, through | ||
end) | ||
end | ||
end | ||
|
||
def schema_changes do | ||
@schema_changes | ||
end | ||
|
||
defmacro include_methods do | ||
quote do | ||
unquote(Enum.map(method_blocks, fn (method_block) -> method_block end)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for pointing this out, yes it can be safely removed with no impact. 👍 |
||
end | ||
end | ||
|
||
def method_blocks do | ||
@method_block | ||
end | ||
end | ||
end | ||
end | ||
|
||
defmodule ExtensionsManager.DefaultExtend do | ||
use ExtensionsManager.ModelExtension | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
defmodule ExtensionsManager.RouterExtension do | ||
defmacro __using__(_opts) do | ||
quote do | ||
Module.register_attribute(__MODULE__, :defined_routes, accumulate: true) | ||
|
||
import ExtensionsManager.RouterExtension, only: [define_route: 1] | ||
@before_compile ExtensionsManager.RouterExtension | ||
end | ||
end | ||
|
||
defmacro define_route([do: rt]) do | ||
block = Macro.escape(rt) | ||
quote bind_quoted: [block: block] do | ||
Module.put_attribute(__MODULE__, :defined_routes, block) | ||
end | ||
end | ||
|
||
defmacro __before_compile__(_env) do | ||
quote do | ||
defmacro mount do | ||
@defined_routes | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
defmodule ExtensionsManager.SampleMacro do | ||
defmacro __using__(_opts) do | ||
quote do | ||
Module.register_attribute(__MODULE__, :module_blocks, accumulate: true) | ||
import ExtensionsManager.SampleMacro, only: [define_model: 1] | ||
@before_compile ExtensionsManager.SampleMacro | ||
|
||
defmacro __using__(_opts) do | ||
quote do | ||
import unquote(__MODULE__), only: [include_models: 0] | ||
@before_compile unquote(__MODULE__) | ||
end | ||
end | ||
|
||
defmacro __before_compile__(_env) do | ||
quote do | ||
include_models | ||
end | ||
end | ||
end | ||
end | ||
|
||
defmacro __before_compile__(_env) do | ||
quote do | ||
defmacro include_models do | ||
quote do | ||
unquote(Enum.map(module_blocks, fn (module_block) -> module_block end)) | ||
end | ||
end | ||
def module_blocks do | ||
@module_blocks | ||
end | ||
end | ||
end | ||
|
||
defmacro define_model([do: block]) do | ||
quote bind_quoted: [block: block] do | ||
Module.put_attribute(__MODULE__, :module_blocks, block) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
defmodule ExtensionsManager.Mixfile do | ||
use Mix.Project | ||
|
||
def project do | ||
[app: :extensions_manager, | ||
version: "0.0.1", | ||
build_path: "../../_build", | ||
config_path: "../../config/config.exs", | ||
deps_path: "../../deps", | ||
lockfile: "../../mix.lock", | ||
elixir: "~> 1.2", | ||
build_embedded: Mix.env == :prod, | ||
start_permanent: Mix.env == :prod, | ||
deps: deps] | ||
end | ||
|
||
# Configuration for the OTP application | ||
# | ||
# Type "mix help compile.app" for more information | ||
def application do | ||
[applications: [:logger]] | ||
end | ||
|
||
# Dependencies can be Hex packages: | ||
# | ||
# {:mydep, "~> 0.3.0"} | ||
# | ||
# Or git/path repositories: | ||
# | ||
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} | ||
# | ||
# To depend on another app inside the umbrella: | ||
# | ||
# {:myapp, in_umbrella: true} | ||
# | ||
# Type "mix help deps" for more examples and options | ||
defp deps do | ||
[{:favorite_products, in_umbrella: true}] | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
defmodule ExtensionsManagerTest do | ||
use ExUnit.Case | ||
|
||
test "the truth" do | ||
assert 1 + 1 == 2 | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ExUnit.start() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# App artifacts | ||
/_build | ||
/db | ||
/deps | ||
/*.ez | ||
|
||
# Generate on crash by the VM | ||
erl_crash.dump | ||
|
||
# The config/prod.secret.exs file by default contains sensitive | ||
# data and you should not commit it into version control. | ||
# | ||
# Alternatively, you may comment the line below and commit the | ||
# secrets file as long as you replace its contents by environment | ||
# variables. | ||
/config/prod.secret.exs | ||
/config/dev.secret.exs | ||
/config/test.secret.exs | ||
|
||
# Vim Swap Files | ||
*.swp | ||
/**/*.swp | ||
/**/**/*.swp | ||
/**/**/**/*.swp | ||
/**/**/**/**/*.swp | ||
|
||
# Misc | ||
.DS_Store | ||
**/.DS_Store |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# FavoriteProducts | ||
|
||
To start your Phoenix app: | ||
|
||
* Install dependencies with `mix deps.get` | ||
* Create and migrate your database with `mix ecto.create && mix ecto.migrate` | ||
* Start Phoenix endpoint with `mix phoenix.server` | ||
|
||
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. | ||
|
||
Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment). | ||
|
||
## Learn more | ||
|
||
* Official website: http://www.phoenixframework.org/ | ||
* Guides: http://phoenixframework.org/docs/overview | ||
* Docs: http://hexdocs.pm/phoenix | ||
* Mailing list: http://groups.google.com/group/phoenix-talk | ||
* Source: https://github.com/phoenixframework/phoenix |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
exports.config = { | ||
// See http://brunch.io/#documentation for docs. | ||
files: { | ||
javascripts: { | ||
joinTo: "js/app.js" | ||
|
||
// To use a separate vendor.js bundle, specify two files path | ||
// https://github.com/brunch/brunch/blob/stable/docs/config.md#files | ||
// joinTo: { | ||
// "js/app.js": /^(web\/static\/js)/, | ||
// "js/vendor.js": /^(web\/static\/vendor)|(deps)/ | ||
// } | ||
// | ||
// To change the order of concatenation of files, explicitly mention here | ||
// https://github.com/brunch/brunch/tree/master/docs#concatenation | ||
// order: { | ||
// before: [ | ||
// "web/static/vendor/js/jquery-2.1.1.js", | ||
// "web/static/vendor/js/bootstrap.min.js" | ||
// ] | ||
// } | ||
}, | ||
stylesheets: { | ||
joinTo: "css/app.css" | ||
}, | ||
templates: { | ||
joinTo: "js/app.js" | ||
} | ||
}, | ||
|
||
conventions: { | ||
// This option sets where we should place non-css and non-js assets in. | ||
// By default, we set this to "/web/static/assets". Files in this directory | ||
// will be copied to `paths.public`, which is "priv/static" by default. | ||
assets: /^(web\/static\/assets)/ | ||
}, | ||
|
||
// Phoenix paths configuration | ||
paths: { | ||
// Dependencies and current project directories to watch | ||
watched: [ | ||
"web/static", | ||
"test/static" | ||
], | ||
|
||
// Where to compile files to | ||
public: "priv/static" | ||
}, | ||
|
||
// Configure your plugins | ||
plugins: { | ||
babel: { | ||
// Do not use ES6 compiler in vendor code | ||
ignore: [/web\/static\/vendor/] | ||
} | ||
}, | ||
|
||
modules: { | ||
autoRequire: { | ||
"js/app.js": ["web/static/js/app"] | ||
} | ||
}, | ||
|
||
npm: { | ||
enabled: true, | ||
// Whitelist the npm deps to be pulled in as front-end assets. | ||
// All other deps in package.json will be excluded from the bundle. | ||
whitelist: ["phoenix", "phoenix_html"] | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does defining a
__using__/1
prevent the user from defining their own__using__/1
? Since mostuse X
statements are at the top, that'd mean this one would match everything and any other__using__/1
definitions would generate a compiler warning and never match.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, Unfortunately this will prevent the another using/1 declaration in
ExtensionsManager.ExtendUser
(where its currently being used)ExtensionsManager.ExtendUser
is a special module wrapping up schema changes and support functions forNectar.User
(Ecto Model) from other modules (extensions) so we never envisaged a situation where another using macro would be required forExtensionsManager.ExtendUser
But for exceptional cases, we can probably add a
defoverridable [__using__: 1]
inExtensionsManager.ExtendUser
trusting the developer's intent for change 💭 💂♂️There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking maybe you're attempt to implicitly change the schema definition should take a more explicit approach. Create a macro instead that adds the fields you want and just insert that macro where it needs to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mgwidmann Project is in very early stages. It will evolve where
Nectar
and all other extensions will be phoenix projects which can be downloaded as hex packages.Nectar and extensions can be grown and develop in isolations. Nectar being maintained by one team and extensions probably by many different teams where no one team will own all the extensions and Nectar Project.
So, Nectar should provide a way to extensions to hook back and grow independently.
Changes in extensions should not result / demand a change in Nectar Project but it could work otherwise, wherein extensions can adapt to change as per Nectar, if need some specific functionality which is provided by Nectar later.
Not very sure, whether above answers your question or not :( 😞
We are in process of releasing the Blog Series which will provide in-depth details of nature of project and how it would be used.
We hope, it should make things more clear 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, its your choice. Just wanted to provide some input. I'll look for your blog post.