How do I edit one or two field of record which is checked by ChangeSet?

Hello dear friends,
I am supposed to edit one field of record in ecto, but I need to check my record by ChangeSet, it should be noted , I need to check the field which I need to edit , not other field, not all of my fields . just one or two field that I need.

for example :

my fields :

field :full_name, :string
        field :email, :string
        field :password, :string, virtual: true
        field :name, :string, virtual: true
        field :lastname, :string, virtual: true
        field :password_hash, :string
        field :last_ip, :string
        field :group_acl, :string
        field :language, :string
        field :country, :string

I just decided to edit “:group_acl” , I did it

def get_user_by_id(id) do
	Repo.get!(UsersInfo, id)
end

def edit_user(id, map_params) do
	user = get_user_by_id(id)
	users = Ecto.Changeset.change(user , map_params)
	case Repo.update(users) do
		  {:ok, struct}       -> IO.inspect struct
		  {:error, changeset} -> IO.inspect changeset
	end
end

and in my phoenix controller :

      def forget_password(conn, %{"id" => id , "group_acl" => group_acl}) do
            forget_passwords = case UsersInfoQuery.edit_user(id, %{group_acl: group_acl}) do
                  {:ok, _} ->
                        conn    
                        |> put_status(200)
                        |> json(%{message: "Your edit was successful."})
                  {:error, _} ->
                        conn
                        |> put_status(403)
                        |> json(%{error_code: "403"})
            end
            forget_passwords
      end

it is edited successful, but I have errors in my terminal ( The condition is always correct, but my request is not valid) :

[error] #PID<0.687.0> running TrangellUsersServiceWeb.Endpoint terminated
Server: localhost:4021 (http)
Request: POST /api/users/forget-password
** (exit) an exception was raised:
    ** (CaseClauseError) no case clause matching: %TrangellUsersService.Login.Db.UsersInfo{__meta__: #Ecto.Schema.Metadata<:loaded, "usersinfo">, country: "Australia", email: "3@gmail.com", full_name: "shahryar tavakkoli", group_acl: "999", id: "2c409b05-99d0-4f73-8dfe-fc3df9aa7c73", inserted_at: ~N[2018-03-27 08:13:33.380492], language: "fa", last_ip: "127.0.0.1", lastname: nil, login_actions: #Ecto.Association.NotLoaded<association :login_actions is not loaded>, name: nil, password: nil, password_hash: "$2b$12$KqSpLbLkFvl6jw9D1QDR/uTw/NZPb4pdOf3XgfJfnbv9.Qi/bS6ki", profile_actions: #Ecto.Association.NotLoaded<association :profile_actions is not loaded>, token_actions: #Ecto.Association.NotLoaded<association :token_actions is not loaded>, updated_at: ~N[2018-04-02 20:59:51.986916]}
        (trangell_users_service_web) lib/trangell_users_service_web/controllers/login_controller.ex:178: TrangellUsersServiceWeb.LoginController.forget_password/2
        (trangell_users_service_web) lib/trangell_users_service_web/controllers/login_controller.ex:5: TrangellUsersServiceWeb.LoginController.action/2
        (trangell_users_service_web) lib/trangell_users_service_web/controllers/login_controller.ex:5: TrangellUsersServiceWeb.LoginController.phoenix_controller_pipeline/2
        (trangell_users_service_web) lib/trangell_users_service_web/endpoint.ex:1: TrangellUsersServiceWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
        (trangell_users_service_web) lib/trangell_users_service_web/endpoint.ex:1: TrangellUsersServiceWeb.Endpoint.plug_builder_call/2
        (trangell_users_service_web) lib/plug/debugger.ex:99: TrangellUsersServiceWeb.Endpoint."call (overridable 3)"/2
        (trangell_users_service_web) lib/trangell_users_service_web/endpoint.ex:1: TrangellUsersServiceWeb.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) /Applications/MAMP/htdocs/elixir-ex-source/Trangell_Main/trangell_users_service_umbrella/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

just the field which i need validation :

    def changeset(struct, params \\ %{}) do
        struct
        |> cast(params, [:full_name, :lastname, :name, :email, :password, :last_ip, :group_acl, :language, :country, :password_hash])
        |> validate_required([:name, :lastname, :email, :password, :last_ip, :group_acl,:language, :country])
          .......
        |> validate_length(:group_acl, max: 50)
        |> validate_inclusion(:group_acl, ["actived", "unactived", "blocked", "registered"])
          .......
          .......
    end

IO.inspect will return whatever input is passed to it, in this case will the either the struct or the changeset that you’re inspecting, but then on the case function that calls the changeset you only have tuple matches. If you just do inspect and after that return {:ok, struct}, or {:error, changeset} it should work fine

1 Like

Thank you the error message solved, but my conditional codes works in both ways if it is true or false, please see |> validate_inclusion(:group_acl, ["actived", "unactived", "blocked", "registered"])

but my request :

Hi is not in the list ["actived", "unactived", "blocked", "registered"], but my code is updated.

You’re inserting the changes directly with Ecto.Changeset.change , if you want to use the changeset you’ve defined you need to call YourModule.changeset…

1 Like
forget_passwords = case UsersInfoQuery.edit_user(id, %{group_acl: group_acl}) do
                  {:ok, _} ->
                        conn    
                        |> put_status(200)
                        |> json(%{message: "Your edit was successful."})
                  {:error, _} ->
                        conn
                        |> put_status(403)
                        |> json(%{error_code: "403"})
            end

UsersInfoQuery.edit_user/2 is not returning what you’re expecting, so it’s raising an exception. Add a final match, something like other -> IO.inspect(other) to see what it’s giving you and change the case accordingly.

Here’s an example of how to debug:

forget_passwords = case UsersInfoQuery.edit_user(id, %{group_acl: group_acl}) do
                  {:ok, _} ->
                        conn    
                        |> put_status(200)
                        |> json(%{message: "Your edit was successful."})
                  {:error, _} ->
                        conn
                        |> put_status(403)
                        |> json(%{error_code: "403"})
                  other ->
                        IO.inspect(other)
                        conn |> put_status(403) |> json(%{error_code: "403"})
            end
2 Likes

I have checked by YourModule.changeset like this changeset = UsersInfo.changeset(%UsersInfo{}, params) , but it check all conditions not the field concerned only.

But where are you placing the changes on the model?

1 Like

like this :

def edit_user(id, map_params) do
		get_user_by_id(id)
		|> UsersInfo.changeset(map_params)
		|> Repo.update
end

I input the field concerned like this edit_user(2, %{group_acl: "actived"})

Your controller’s action should return connection not the UserInfo struct.

1 Like

Please , Can you take an example? I don’t understand what you say.

In other words the last expression in your controller’s action is forget_passwords which causes the error. In Phoenix it is expected that controller’s functions return conn structs, so

conn    
|> put_status(200)
|> json(%{message: "Your edit was successful."})`Preformatted text`

is enough. You shouldn’t return anything after that.

def forget_password(conn, %{"id" => id , "group_acl" => group_acl}) do
        forget_passwords = case UsersInfoQuery.edit_user(id, %{group_acl: group_acl}) do
              {:ok, _} ->
                    conn    
                    |> put_status(200)
                    |> json(%{message: "Your edit was successful."})
              {:error, _} ->
                    conn
                    |> put_status(403)
                    |> json(%{error_code: "403"})
        end
        forget_passwords # <------ that causes the error
  end
1 Like

Although redundant that’s not gonna cause an error, as forget_passwords will be the last evaluated expression from the case, which will be valid.

@shahryarjb,

def edit_user(id, map_params) do
	user = get_user_by_id(id)
	users = Ecto.Changeset.change(user , map_params) ################## This is not == to Yourmodel.changeset 
	case Repo.update(users) do
		  {:ok, struct}       -> IO.inspect struct
		  {:error, changeset} -> IO.inspect changeset
	end
end
1 Like

Password is an example , it may the field name or email

That makes it exactly invalid :slight_smile:

1 Like

It’s only as invalid as you want it to be!

1 Like

Ooh, I think you didn’t understand what I said, I am sorry I didn’t explain you frankly, Because I’m weak in English.

please see my changeset validation

 |> validate_inclusion(:group_acl, ["actived", "unactived", "blocked", "registered"])

then I sent an invalid request .

{
  "group_acl": "Hi",
  "id": "2c409b05-99d0-4f73-8dfe-fc3df9aa7c73"
}

my db was updated to “Hi”, but it should not be updated.

how do I fix this ?

So, if you want to change just one field you can have separate changeset function, e.g.:

def update_acl_changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:group_acl])
    |> validate_required([ :group_acl])
    |> validate_inclusion(:group_acl, ["actived", "unactived", "blocked", "registered"])
end

if you use your original changeset function you will have to fulfil another validations as well (for example relating to name, email and so on).

Then your edit function:

def edit_user(id, params) do
    user = get_user_by_id(id)
    changes = YourSchemaModule.update_acl_changeset(user , params)
    Repo.update(changes) 
end

and the controller function:

def forget_password(conn, %{"id" => id , "group_acl" => group_acl} = params) do
        case UsersInfoQuery.edit_user(id, params) do
              {:ok, _} ->
                    conn    
                    |> put_status(200)
                    |> json(%{message: "Your edit was successful."})
              {:error, _} ->
                    conn
                    |> put_status(403)
                    |> json(%{error_code: "403"})
        end
  end
1 Like