Open danschultzer opened 4 years ago
I'll write some short instructions here to answer the above post.
Create the organization and add a migration to add the organization reference:
mix phx.gen.context Organizations Organization organizations name:string
mix ecto.gen.migration add_organization_to_users
Update add_organization_to_users.exs
migration file:
defmodule MyApp.Repo.Migrations.AddOrganizationToUsers do
use Ecto.Migration
def change do
alter table(:users) do
add :organization_id, references("organizations", on_delete: :delete_all), null: false
end
end
end
And update the user to restraint them to organizations:
defmodule MyApp.Users.User do
# ...
alias MyApp.Organizations.Organization
schema "users" do
belongs_to :organization, Organization
pow_user_fields()
timestamp()
end
def changeset(user_or_changeset, attrs) do
user_or_changeset
|> pow_changeset(attrs)
|> pow_extension_changeset(attrs)
|> Ecto.Changeset.assoc_constraint(:organization)
end
def invite_changeset(user_or_changeset, invited_by, attrs) do
user_or_changeset
|> pow_invite_changeset(invited_by, attrs)
|> changeset_organization(invited_by)
end
defp changeset_organization(changeset, invited_by) do
Ecto.Changeset.change(changeset, organization_id: invited_by.organization_id)
end
# ...
end
Update the organizations schema to require an initial user:
defmodule MyApp.Organizations.Organization do
use Ecto.Schema
import Ecto.Changeset
alias MyApp.Users.User
schema "organizations" do
field :name, :string
has_many :users, User, on_delete: :delete_all
timestamps()
end
@doc false
def changeset(organization, attrs) do
organization
|> cast(attrs, [:name])
|> validate_required([:name])
end
defp changeset_users(changeset) do
case Ecto.get_meta(changeset.data, :state) do
:built -> cast_assoc(changeset, :users, required: true)
_any -> any
end
end
Set up a controller action to create the organization:
defmodule MyAppWeb.RegistrationController do
use MyAppWeb, :controller
alias MyApp.{Organizations.Organization, Repo}
def new(conn, _params) do
changeset = Organization.changeset(%Organization{}, %{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"organization" => user_params}) do
%Organization{}
|> Organization.changeset(user_params)
|> Repo.insert()
|> case do
{:ok, organization} ->
conn
|> auth_user(organization.users)
|> put_flash(:info, "Welcome!")
|> redirect(to: Routes.page_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
defp auth_user(conn, [user]) do
config = Pow.Plug.fetch_config(conn)
Pow.Plug.get_plug(config).do_create(conn, user, config)
end
end
The form could look like this:
<%= form_for @changeset, Routes.organization_path(@conn, :create), fn f -> %>
<%= text_input f, :name %>
<%= inputs_for f, :users, [append: [%MyApp.Users.User{}]], fn f_user -> %>
<%= label f_user, :email, "email" %>
<%= email_input f_user, :email %>
<%= error_tag f_user, :email %>
<%= label f_user, :password, "password" %>
<%= password_input f_user, :password %>
<%= error_tag f_user, :password %>
<%= label f_user, :confirm_password, "confirm_password" %>
<%= password_input f_user, :confirm_password %>
<%= error_tag f_user, :confirm_password %>
<% end %>
<% end %>
Thank you for the instructions. Could you assign this one to me? I'll start working on it.
Thanks!
I started implementing the proposed workflow along these steps:
I have an issue with registration view: We can not combine default: on line 4 with changeset. When I remove this, the user fields do not show up. Do you know how to solve this part?
The code is here any other comments are most welcome!
Thank you for your help!
Hmm, what kind of error do you get?
Also, you can do the rest in in much less code by overriding the registration :new
and :create
route with a organization controller.
scope "/", MyAppWeb do
pipe_through [:browser]
resources "/registration", OrganizationController, singleton: true, only: [:new, :create]
end
scope "/" do
pipe_through :browser
pow_routes()
end
defmodule MyAppWeb.OrganizationController do
use MyAppWeb, :controller
alias MyApp.{Organizations.Organization, Repo}
def new(conn, _params) do
changeset = Organization.changeset(%Organization{}, %{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"organization" => organization_params}) do
%Organization{}
|> Organization.changeset(user_params)
|> Repo.insert()
|> case do
{:ok, organization} ->
conn
|> auth_user(organization.users)
|> put_flash(:info, "Welcome!")
|> redirect(to: Routes.page_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
defp auth_user(conn, [user]) do
config = Pow.Plug.fetch_config(conn)
Pow.Plug.get_plug(config).do_create(conn, user, config)
end
end
# organization new.html.eex
<%= form_for @changeset, Routes.organization_path(@conn, :create), fn f -> %>
<%= text_input f, :name %>
<%= inputs_for f, :users, [append: [%MyApp.Users.User{}]], fn f_user -> %>
<%= label f_user, :email, "email" %>
<%= email_input f_user, :email %>
<%= error_tag f_user, :email %>
<%= label f_user, :password, "password" %>
<%= password_input f_user, :password %>
<%= error_tag f_user, :password %>
<%= label f_user, :confirm_password, "confirm_password" %>
<%= password_input f_user, :confirm_password %>
<%= error_tag f_user, :confirm_password %>
<% end %>
<% end %>
This way you don't need to override anything else in Pow, just the routes 😄 Also the organization constraint in the user changeset will prevent users from signing up through the regular Pow registration controller if you somehow inadvertently expose it, so it's safe.
Thank you! I updated the branch as you suggested. Much cleaner indeed!
The error I have is:
ArgumentError at GET /registration/new :default is not supported on inputs_for with changesets. The default value must be set in the changeset data
It's originating from lib/phoenix_ecto/html.ex
line 23..
The source of the problem in my code is the line 4 of the view with the :default argument.
Oh, try use :append
instead: <%= inputs_for f, :users, [append: [%MyApp.Users.User{}]], fn f_user -> %>
It works but only with a list of: [%User{}] instead of %User{} directly.
<%= inputs_for f, :users, [append: [%MyApp.Users.User{}]], fn f_user -> %>
I get a "protocol Enumerable not implemented for %User{} otherwise.
Is it OK to solve this error like this?
Yeah, it was my mistake. Updated the examples!
This would be a good newbie guide, based on this post in the elixir forum:
This would be a very easy guide to write. It can go into PowInvitation, roles and/or user management within the organization, and maybe on how to limit organizations to a certain subdomain (so users in a certain organization can only sign in on that subdomain). Any ideas are welcome!