Вступление
У меня проблема с отношениями в Ecto. У меня есть две схемы User и Role, и я хочу иметь возможность вставить нового User с внешним ключом в БД в Таблица ролей.
Проблема в том, что каждый раз, когда я пытаюсь вставить нового пользователя, он не имеет отношения к таблице Role (столбец role_id имеет значение null в User em> таблица). Я пытался создать ассоциацию, чтобы установить отношения, но это не сработало.
Текущий код
Моя схема User:
schema "user" do
field :first_name, :string
field :last_name, :string
field :email, :string
field :encrypted_password, :string
field :password, :string, virtual: true
belongs_to :role, TestApp.Role
timestamps()
end
Схема роли:
schema "role" do
field :name, :string
timestamps()
has_many :users, TestApp.User
end
Я определил набор изменений User следующим образом:
@required_fields ~w(first_name last_name email password)
@optional_fields ~w(encrypted_password)
@required_update_fields ~w()
@optional_update_fields ~w(first_name last_name email password encrypted_password role)
def create_changeset(struct, params \\ :empty) do
changeset(struct, params, @required_fields, @optional_fields)
end
def update_changeset(struct, params \\ :empty) do
changeset(struct, params, @required_update_fields, @optional_update_fields)
end
defp changeset(struct, params \\ :empty, required_field, optional_fiels) do
struct
|> cast(params, required_field, optional_fiels)
|> validate_format(:email, ~r/@/, message: "invalid format")
|> validate_length(:password, min: 5)
|> validate_confirmation(:password, message: "password does not match")
|> unique_constraint(:email, message: "email already taken")
|> generate_encrypted_password
end
И набор изменений Роль:
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:name])
|> validate_required([:name])
|> unique_constraint(:name)
end
В моем UserController у меня есть метод create:
def create(conn, %{"user" => user_params}) do
changeset = User.create_changeset(%User{}, user_params)
case User.insert(changeset) do
{:ok, user} ->
conn
|> put_status(:ok)
|> render(TestApp.UserRender, "show.json", user: user)
{:error, changeset} ->
handle_user_creation_validation_error(conn, changeset: changeset)
end
end
Проверенные решения (безуспешно)
Я протестировал несколько вариантов, следующий список - это просто пример того, что я пробовал до сих пор (слишком много попыток). Его вообще не нужно компилировать, я просто отправлю его в качестве информации.
Я попытался создать метод insert внутри User, чтобы создать ассоциацию и сохранить набор изменений.
def insert(changeset) do
if changeset.valid? do
IEx.pry
TestApp.Role.find_by_name("user")
|> Ecto.build_assoc(:users)
|> Ecto.change(changeset.params)
|> Repo.insert
else
{:error, changeset}
end
end
Я также попробовал получить роль и создать помощника
TestApp.Role.find_by_name("user")
|> TestApp.Repo.preload(:users)
|> Ecto.build_assoc(:users)
|> Ecto.Changeset.put_assoc(:users, user)
|> Repo.insert!
Я видел этот пример в документации Ecto
def insert(changeset) do
Repo.transaction fn ->
TestApp.Role.find_by_name("user")
|> TestApp.Repo.preload(:users)
|> Ecto.Changeset.put_assoc(:users, [changeset])
|> Repo.insert
end
Текущий статус
Мне удалось создать отношения между пользователями и ролями, но каждый раз, когда я вставлял нового пользователя новую роль также была вставлена роль (я был шокирован, она просто каждый раз создавала отношение one_to_one!).
Я знаю, что есть правильный путь к этому, но мне интересно, будет ли это подразумевать предварительную загрузку :users
из роли в первую очередь. Я не думаю, что это приемлемое решение, надеюсь, кто-нибудь сможет объяснить все это об отношениях, предварительных загрузках и сохранении наборов изменений.
Решение
Что ж, похоже, необходимо отправить FK как параметр в наборе изменений User, поэтому мне пришлось изменить поле param role
на role_id
.
Пример запроса тела param может быть таким:
{
"user" : {
"first_name": "first name",
"last_name": "last name",
"email": "test@email",
"password": "longtestpassword",
"password_confirmation": "longtestpassword",
"role_id": 1
}
}
Я также добавил cast_assoc
в конвейер проверки набора изменений:
|> cast_assoc(:role)
Реализация User.insert/1
была удалена, и вместо этого контроллер вызывает Repo.insert/1
.
Спасибо за вашу помощь!