# Forms For Relationships Between Existing Records
Make sure you're familiar with the basics of `AshPhoenix.Form` and relationships before reading this guide.
When we talk about "relationships between existing records", we mean inputs on a form that manage the relationships between records that already exist.
For example, you might have a form for creating a "service" that can be performed at some "locations", but not others.
When creating or updating a service, the user is only able to select from the existing locations.
## Defining the resources and relationships
First, we have a simple `Location`
```elixir
defmodule MyApp.Operations.Location do
use Ash.Resource,
otp_app: :my_app,
domain: MyApp.Operations,
data_layer: AshPostgres.DataLayer
...
attributes do
integer_primary_key :id
attribute :name, :string do
allow_nil? false
end
end
end
```
Then we have a `Service`, which has a `many_to_many` association to `Location`, through `ServiceLocation`.
We add a `list` aggregate for `:location_ids` for populating the form values.
```elixir
defmodule MyApp.Operations.Service do
use Ash.Resource,
otp_app: :my_app,
domain: MyApp.Operations,
data_layer: AshPostgres.DataLayer
...
relationships do
has_many :location_relationships, MyApp.Operations.ServiceLocation do
destination_attribute :service_id
end
many_to_many :locations, MyApp.Operations.Location do
join_relationship :location_relationships
source_attribute_on_join_resource :service_id
destination_attribute_on_join_resource :location_id
end
end
aggregates do
list :location_ids, :locations, :id
end
end
```
`ServiceLocation` has default `actions` as well as the `relationships` declared to operate as the joining resource between a `Service` and one or more `Location`s.
```elixir
defmodule MyApp.Operations.ServiceLocation do
use Ash.Resource,
otp_app: :my_app,
domain: MyApp.Operations,
data_layer: AshPostgres.DataLayer
...
actions do
defaults [:create, :read, :update, :destroy]
default_accept [:service_id, :location_id]
end
relationships do
belongs_to :service, MyApp.Operations.Service do
attribute_type :integer
allow_nil? false
primary_key? true
end
belongs_to :location, MyApp.Operations.Location do
attribute_type :integer
allow_nil? false
primary_key? true
end
end
end
```
## Declaring the `create` and `update` actions
First, we need to update our `Service` and declare custom `create` and `update` actions, which take a list of `Location` ids as an argument.
We use `type: :append_and_remove` to cause a `ServiceLocation` to be added or removed for each `Location` as we add and remove them using our form.
(See `Ash.Changeset.manage_relationship/4` for more.)
```elixir
# in lib/my_app/operations/service.ex
create :create do
accept [:name]
primary? true
argument :location_ids, {:array, :integer}, allow_nil?: true
change manage_relationship(:location_ids, :locations, type: :append_and_remove)
end
update :update do
accept [:name]
primary? true
argument :location_ids, {:array, :integer}, allow_nil?: true
require_atomic? false
change manage_relationship(:location_ids, :locations, type: :append_and_remove)
end
```
Note: in this example, we are using `integer_primary_key`, so the argument's type is `{:array, :integer}`.
If we were using `uuid_primary_key`, the type would be `{:array, :uuid}`.
Now we can create and update our `Services`.
```elixir
iex> service = Ash.create!(Service, %{name: "Tuneup", location_ids: [location_1_id, location_2_id]}, load: [:locations])
%MyApp.Operations.Service{
id: 9,
name: "Tuneup",
location_relationships: [
%MyApp.Operations.ServiceLocation{ service_id: 9, location_id: 1, ... },
%MyApp.Operations.ServiceLocation{ service_id: 9, location_id: 2, ... }
],
locations: [
%MyApp.Operations.Location{ id: 1, name: "HQ", ... },
%MyApp.Operations.Location{ id: 2, name: "Downtown", ... }
],
...
}
iex> Ash.update!(service, %{location_ids: [location_2_id]}, load: [:locations])
%MyApp.Operations.Service{
id: 9,
name: "Tuneup",
location_relationships: [
%MyApp.Operations.ServiceLocation{ service_id: 9, location_id: 2, ... }
],
locations: [
%MyApp.Operations.Location{ id: 2, name: "Downtown", ... }
],
...
}
```
Now, let's expose this to a user.
## Adding the forms
In our view, we create our form as normal.
For update forms, we'll make sure to load our `locations`.
We use the `:prepare_params` option with our `for_update` form to set `"location_ids"` to an empty list if no value is provided.
This allows the user to de-select all `Location`s to update a `Service` so that it's not available at any `Location`.
```elixir
# lib/my_app_web/service_live/form_component.ex
defp assign_form(%{assigns: %{service: service}} = socket) do
form =
if service do
service
|> Ash.load!([:locations, :location_ids])
|> AshPhoenix.Form.for_update(:update, as: "service", prepare_params: &prepare_params/2)
else
AshPhoenix.Form.for_create(MyApp.Operations.Service, :create, as: "service")
end
assign(socket, form: to_form(form))
end
defp prepare_params(params, :validate) do
Map.put_new(params, "location_ids", [])
end
```
When rendering the form, we'll have to manually provide the `options` to our `input`.
Using Phoenix generated core components, `options` is passed to `Phoenix.HTML.Form.options_for_select/2`, which expects a list of two-element tuples.
Assuming the available `Location`s are already assigned to `@locations`:
```elixir
<.input
field={@form[:location_ids]}
type="select"
multiple
label="Locations"
options={Enum.map(@locations, &{&1.name, &1.id})}
/>
```
Now, when our form is submitted, we will receive a list of location ids.
```elixir
%{"service" => %{"locations" => ["1", "2"], "name" => "Overhaul"}}
```
That's all we need to do.
We can pass these parameters to `AshPhoenix.Form.submit/2` as normal and `manage_relationship` will create and destroy our `ServiceLocation` records as needed.