Skip to content
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 1 #48

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/extensions_manager/.gitignore
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
1 change: 1 addition & 0 deletions apps/extensions_manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Extensions Manager
30 changes: 30 additions & 0 deletions apps/extensions_manager/config/config.exs
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"
2 changes: 2 additions & 0 deletions apps/extensions_manager/lib/extensions_manager.ex
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
75 changes: 75 additions & 0 deletions apps/extensions_manager/lib/extensions_manager/model_extension.ex
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))
end
end

def method_blocks do
@method_block
end
end
end
end

defmodule ExtensionsManager.DefaultExtend do
use ExtensionsManager.ModelExtension
end
21 changes: 21 additions & 0 deletions apps/extensions_manager/lib/extensions_manager/nectar_extender.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule ExtensionsManager.NectarExtender do
defmacro __using__(_opts) do
# determine from env which model we want to extend
# load all impls for extend#Module here
# use SayHelloWorldInProduct
useable_module = infer_module_from_caller(__CALLER__)
quote do
use unquote(useable_module)
end
end

def infer_module_from_caller(caller) do
module_to_extend = caller.module |> Module.split |> List.last
module_name = String.to_atom("Elixir.ExtensionsManager.Extend" <> module_to_extend)
if Code.ensure_loaded?(module_name) do
apply(module_name, :__info__, [:module])
else
apply(ExtensionsManager.DefaultExtend, :__info__, [:module])
end
end
end
25 changes: 25 additions & 0 deletions apps/extensions_manager/lib/extensions_manager/router_extension.ex
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
41 changes: 41 additions & 0 deletions apps/extensions_manager/lib/extensions_manager/sample_macro.ex
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
40 changes: 40 additions & 0 deletions apps/extensions_manager/mix.exs
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
7 changes: 7 additions & 0 deletions apps/extensions_manager/test/extensions_manager_test.exs
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
1 change: 1 addition & 0 deletions apps/extensions_manager/test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ExUnit.start()
37 changes: 37 additions & 0 deletions apps/favorite_products/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# App artifacts
/_build
/db
/deps
/*.ez

# Generate on crash by the VM
erl_crash.dump

# Static artifacts
/node_modules

# Since we are building assets from web/static,
# we ignore priv/static. You may want to comment
# this depending on your deployment strategy.
/priv/static/

# 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
20 changes: 20 additions & 0 deletions apps/favorite_products/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# FavoriteProducts

To start your Phoenix app:

* Install dependencies with `mix deps.get`
* Create and migrate your database with `mix ecto.create && mix ecto.migrate`
* Install Node.js dependencies with `npm install`
* 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
Loading