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

Router scope macro. #39

Merged
merged 2 commits into from
Feb 12, 2014
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ defmodule Router do
resources "users", Controllers.Users do
resources "comments", Controllers.Comments
end

scope path: "admin", alias: Controllers.Admin, helper: "admin" do
resources "users", Users
end
end

defmodule Controllers.Pages do
Expand Down
37 changes: 30 additions & 7 deletions lib/phoenix/router/mapper.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
defmodule Phoenix.Router.Mapper do
alias Phoenix.Router.Path
alias Phoenix.Controller
alias Phoenix.Router.Context
alias Phoenix.Router.ResourcesContext
alias Phoenix.Router.ScopeContext

@actions [:index, :edit, :show, :new, :create, :update, :destroy]

Expand Down Expand Up @@ -30,7 +31,7 @@ defmodule Phoenix.Router.Mapper do
conn = conn.params(Dict.merge(conn.params(), [{"page", page}]))
apply(PagesController, :show, [conn])
end

The resources macro accepts flags to limit which resources are generated. Passing
a list of actions through either :only or :except will prevent building all the
routes
Expand Down Expand Up @@ -139,8 +140,14 @@ defmodule Phoenix.Router.Mapper do
action: action,
options: options] do

current_path = Context.current_path(path, __MODULE__)
@routes {verb, current_path, controller, action, options}
current_path = ResourcesContext.current_path(path, __MODULE__)
{scoped_path, scoped_controller, scoped_helper} = ScopeContext.current_scope(current_path,
controller,
options[:as],
__MODULE__)
opts = Dict.merge(options, as: scoped_helper)

@routes {verb, scoped_path, scoped_controller, action, opts}
end
end

Expand All @@ -154,7 +161,7 @@ defmodule Phoenix.Router.Mapper do
resource: resource,
controller: controller] do
Enum.each actions, fn action ->
current_alias = Context.current_alias(action, resource, __MODULE__)
current_alias = ResourcesContext.current_alias(action, resource, __MODULE__)
opts = Dict.merge(options, as: current_alias)
case action do
:index -> get "#{resource}", controller, :index, opts
Expand All @@ -169,9 +176,25 @@ defmodule Phoenix.Router.Mapper do
end
end

Context.push(resource, __MODULE__)
ResourcesContext.push(resource, __MODULE__)
unquote(nested_route)
ResourcesContext.pop(__MODULE__)
end
end

defmacro scope(params, options) do
nested_route = Keyword.get(options, :do)
path = Keyword.get(params, :path)
controller_alias = Keyword.get(params, :alias)
helper = Keyword.get(params, :helper)

quote unquote: true, bind_quoted: [path: path,
controller_alias: controller_alias,
helper: helper] do

ScopeContext.push({path, controller_alias, helper}, __MODULE__)
unquote(nested_route)
Context.pop(__MODULE__)
ScopeContext.pop(__MODULE__)
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
defmodule Phoenix.Router.Context do
defmodule Phoenix.Router.ResourcesContext do
alias Phoenix.Router.Path
alias Phoenix.Router.Stack
import Inflex

@stack_name :nested_resources

@moduledoc """
Helper functions for pushing and popping nested Route resources
and maintaining resource state during nested expansion
Expand Down Expand Up @@ -87,57 +90,34 @@ defmodule Phoenix.Router.Context do
end
defp alias_for_action(_action, _resources, _rel_path), do: nil


@doc """
Pushes the current resource onto resources stack for given module
and sets new state in module :nested_context attribute
Pushes the current resource onto resources stack
"""
def push(resource, module) do
set([resource | get(module)], module)
Stack.push(resource, @stack_name, module)
end


@doc """
Pops the current resource off resources stack for given module
and sets new state in module :nested_context attribute
Pops the current resource off resources stack
"""
def pop(module) do
case get(module) do
[] -> set([], module)
[_ | rest] -> set(rest, module)
end
Stack.pop(@stack_name, module)
end


@doc """
Returns the current stack of resources stored in nested_context
attribute of module
"""
def get(module) do
(Module.get_attribute module, :nested_context) || []
defp get(module) do
Stack.get(@stack_name, module)
end


@doc """
Updates the current resources state of nested_context
attribute of module
"""
def set(context, module) do
Module.put_attribute module, :nested_context, context
end


@doc """
Appends the singularized inflection of named resource parameter based
on current route resource

# Examples
iex> Context.resource_with_named_param("users")
iex> Phoenix.Router.Context.resource_with_named_param("users")
"users/:user_id"

"""
def resource_with_named_param(resource) do
defp resource_with_named_param(resource) do
Path.join([resource, ":#{singularize(resource)}_id"])
end
end

89 changes: 89 additions & 0 deletions lib/phoenix/router/scope_context.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
defmodule Phoenix.Router.ScopeContext do
alias Phoenix.Router.Path
alias Phoenix.Router.Stack

@stack_name :scopes

@moduledoc """
Helper functions for scopes macro.
"""

@doc """
Returns path, controller module alias and helper scoped to currently active scope.
"""
def current_scope(path, controller, helper, module) do
{current_path(path, module),
current_controller(controller, module),
current_helper(helper, module)}
end

defp current_path(relative_path, module) do
case get_paths(module) do
[] -> relative_path
paths -> paths
|> Enum.reverse
|> Kernel.++([relative_path])
|> Path.join
end
end

defp current_controller(controller, module) do
case get_controllers(module) do
[] -> controller
controller_scopes -> controller_scopes
|> Enum.reverse
|> Kernel.++([controller])
|> Module.concat
end
end

defp current_helper(nil, _module), do: nil
defp current_helper(helper, module) do
case get_helpers(module) do
[] -> helper
helpers -> helpers
|> Enum.reverse
|> Kernel.++([helper])
|> Enum.join("_")
end
end

defp get_paths(module) do
lc scope inlist get(module) do
{path, _, _} = scope
path
end
end

defp get_controllers(module) do
lc scope inlist get(module) do
{_, controller, _} = scope
controller
end
end

defp get_helpers(module) do
lc scope inlist get(module) do
{_, _, helper} = scope
helper
end
end

@doc """
Pushes scope to scopes stack
"""
def push(scope, module) do
Stack.push(scope, @stack_name, module)
end

@doc """
Pops scope from scopes stack
"""
def pop(module) do
Stack.pop(@stack_name, module)
end

defp get(module) do
Stack.get(@stack_name, module)
end
end
37 changes: 37 additions & 0 deletions lib/phoenix/router/stack.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule Phoenix.Router.Stack do

@moduledoc """
Stack impementation used to support nasted expension for nasted resources and scopes. It is stored in module attribute given by the stack name.
"""

@doc """
Pushes the element onto the stack with given name.
"""
def push(element, stack_name, module) do
set([element | get(stack_name, module)], stack_name, module)
end

@doc """
Pops the current element off the stack.
"""
def pop(stack_name, module) do
case get(stack_name, module) do
[] -> set([], stack_name, module)
[_ | rest] -> set(rest, stack_name, module)
end
end

@doc """
Retuns the content of the stack.
"""
def get(stack_name, module) do
(Module.get_attribute module, stack_name) || []
end

@doc """
Sets the content of the stack.
"""
def set(stack_content, stack_name, module) do
Module.put_attribute module, stack_name, stack_content
end
end
Loading