1defmodule Phoenix.Router.Resource do
2  # This module defines the Resource struct that is used
3  # throughout Phoenix's router. This struct is private
4  # as it contains internal routing information.
5  @moduledoc false
6
7  alias Phoenix.Router.Resource
8
9  @default_param_key "id"
10  @actions [:index, :edit, :new, :show, :create, :update, :delete]
11
12  @doc """
13  The `Phoenix.Router.Resource` struct. It stores:
14
15    * :path - the path as string (not normalized)
16    * :param - the param to be used in routes (not normalized)
17    * :controller - the controller as an atom
18    * :actions - a list of actions as atoms
19    * :route - the context for resource routes
20    * :member - the context for member routes
21    * :collection - the context for collection routes
22
23  """
24  defstruct [:path, :actions, :param, :route, :controller, :route, :member, :collection, :singleton]
25  @type t :: %Resource{}
26
27  @doc """
28  Builds a resource struct.
29  """
30  def build(path, controller, options) when is_atom(controller) and is_list(options) do
31    path    = Phoenix.Router.Scope.validate_path(path)
32    alias   = Keyword.get(options, :alias)
33    param   = Keyword.get(options, :param, @default_param_key)
34    name    = Keyword.get(options, :name, Phoenix.Naming.resource_name(controller, "Controller"))
35    as      = Keyword.get(options, :as, name)
36    private = Keyword.get(options, :private, %{})
37    assigns = Keyword.get(options, :assigns, %{})
38
39    singleton = Keyword.get(options, :singleton, false)
40    actions   = extract_actions(options, singleton)
41
42    route       = [as: as, private: private, assigns: assigns]
43    collection  = [path: path, as: as, private: private, assigns: assigns]
44    member_path = if singleton, do: path, else: Path.join(path, ":#{name}_#{param}")
45    member      = [path: member_path, as: as, alias: alias, private: private, assigns: assigns]
46
47    %Resource{path: path, actions: actions, param: param, route: route,
48              member: member, collection: collection, controller: controller, singleton: singleton}
49  end
50
51  defp extract_actions(opts, singleton) do
52    only = Keyword.get(opts, :only)
53    except = Keyword.get(opts, :except)
54
55    cond do
56      only ->
57        supported_actions = validate_actions(:only, singleton, only)
58        supported_actions -- (supported_actions -- only)
59
60      except ->
61        supported_actions = validate_actions(:except, singleton, except)
62        supported_actions -- except
63
64      true -> default_actions(singleton)
65    end
66  end
67
68  defp validate_actions(type, singleton, actions) do
69    supported_actions = default_actions(singleton)
70
71    unless actions -- supported_actions == [], do: raise ArgumentError, """
72    invalid :#{type} action(s) passed to resources.
73
74    supported#{if singleton, do: " singleton", else: ""} actions: #{inspect(default_actions(singleton))}
75
76    got: #{inspect(actions)}
77    """
78
79    supported_actions
80  end
81
82  defp default_actions(true = _singleton),  do: @actions -- [:index]
83  defp default_actions(false = _singleton), do: @actions
84end
85