چطور فیلدی که یونیک ایندکس شده است را در changeset ولید کرد؟

با درود خدمت دوستان و اساتید محترم . امروز در changeset به یک مشکلی برخورد کردم که متوجه شدم افراد دیگر نیز در اینترنت همچنین مشکلی را دارند . ولی متاسفانه را راه حل هاشون نتونستم حل کنم .

مشکل از آنجا شروع می شود که ما یک فیلد از دیتابیس را unique می کنیم و همینطور آن را ایندکس می نماییم.

به صورت مثال به شرح زیر :


def change do
  	create table(:usersinfo, primary_key: false) do
  		.....
    end

    create index(:usersinfo, [:email], concurrently: true, name: :index_of_users_unique_email, unique: true)
end

کد های بالا در میگریشن من نوشته شده است . خوب تا اینجا مشکلی وجود ندارد و من یک ایندکس یونیک درست کردم و حالا زمان این هست که من در فایل schema خودم در changeset آن بیایم اگر ایمیلی تکراری بود جلوشو بگیرم و اروری در changeset قرار بگیرد .

کد به صورت زیر می باشد ( در خط آخر من این کارو کردم )

    def changeset(struct, params \\ %{}) do
        struct
        |> cast(params, [:full_name, :email, :password, :last_ip, :group_acl, :language, :country])
        |> validate_required([:full_name, :email, :password, :last_ip, :group_acl,:language, :country])
        |> validate_format(:email, ~r/@/)
        |> unique_constraint(:email, name: :PRIMARY)
    end

ولی در اینجا ارور زیر را می دهد

** (Ecto.ConstraintError) constraint error when attempting to insert struct:

    * unique: index_of_users_unique_email

If you would like to convert this constraint into an error, please
call unique_constraint/3 in your changeset and define the proper
constraint name. The changeset defined the following constraints:

    * unique: PRIMARY

من سعی کردم حتی در ارور این فانکشن رو پیگیری کنم و حالت مناسبی رو در بیارم تا کدم کار کنه ولی متاسفانه نتونستم و ارور هنوز پابرجاست

مطلبی در این رابطه

با تشکر

1 پسندیده
   def changeset(struct, params \\ %{}) do
       struct
       |> cast(params, [:full_name, :email, :password, :last_ip, :group_acl, :language, :country])
       |> validate_required([:full_name, :email, :password, :last_ip, :group_acl,:language, :country])
       |> validate_format(:email, ~r/@/)
       |> unique_constraint(:email, 
          name: :id //Esme unique index,
          message: "email already exists.")
   end
2 پسندیده

ممنون سام عزیز من هرچی می گشتم ببنیم این سومین پارامتر چی هست نمی تونستم . به این علت که این می گفت باید ۳ ورودی داشته باشه من گفتم پس اگر ۳ ورودی هست یکی با پایپ می فرسته به فانکشن پس باید دوتا خودمون بنویسیم.

ممنون

2 پسندیده

سام عزیز من متوجه یک موضوعی شدم . اینکه مثلا ما بیاییم یک متغییری که حتما نیازه در دیتابیس نفرستیم خود صحبتی که در پست قبلی کردیم به کاربر ارور مثلا ۴۰۰ نشون می ده ولی اگر مثلا همین ایمیل وجود ندارد پیش بیاید متاسفانه
درسته که چیزی انجام نمی شه و دیتابیس ذخیره نمی شه ولی هدر ۲۰۰ بر می گردونه بجای ۴۰۰

که من رو مجبور می کنه بیام در کنترلرم چنین رفتاری کنم آیا درست است ؟

   def test(conn, %{"full_name" => full_name, "email" => email, "password" => password, "last_ip" => last_ip, "language" => language, "country" => country}) do
      case UsersInfoQuery.create_user(full_name, email, password, last_ip, language, country).valid? do
         true -> 
            json conn, %{error: "Your request has been successfully completed."} 
         false -> 
            conn
            |> send_resp(400, "")
      end      
   end

یک بررسی کردم دیدم یک ارور به غیر از اروری که درست کردیم به صورت زیر بیان می شود :

 errors: [country: {"is invalid", [type: :string, validation: :cast]}],

ولی ارور که درست کردیم به صورت زیر می باشد

 errors: [email: {"email already exists.", []}],

احتمال می دم بخاطر اینکه اون آرایه آخر رو نداره این مورد پیش اومده که ارور خودش نمی تونه برگردونه کدی که در بالاتر case زدم هم مشکلاتی داره متاسفانه. که باید به سمتی دیگه برم مثل دستی ارور هدر زدن بجای کمک از چنج ست همه کارو بکنه


به روز رسانی

من کدمو تغییر دادم به این

def create_user(full_name, email, password, last_ip, language, country) do
		password = Comeonin.Bcrypt.hashpwsalt(password)
		group_acl = "shahryar"
		params = %{full_name: full_name, email: email, password: password, last_ip: last_ip, language: language, country: country, group_acl: group_acl}
		
		changeset = UsersInfo.changeset(%UsersInfo{}, params)
		case Repo.insert(changeset) do
		  	{:ok, post} ->  %{valid?: true, post: post}
		  	{:error, changeset} -> IO.inspect changeset    
		end

حالا در بخش کنترلر اینجوری انجام دادم

 def test(conn, %{"full_name" => full_name, "email" => email, "password" => password, "last_ip" => last_ip, "language" => language, "country" => country}) do
      # UsersInfoQuery.create_user(full_name, email, password, last_ip, language, country)

      case UsersInfoQuery.create_user(full_name, email, password, last_ip, language, country).valid? do
         false ->   
            conn
            |> send_resp(400, "")
         true ->
            conn		  
               |> put_status(200)
               |> json(%{ error: "Your request has been successfully completed."})  
      end   
   end

چون متاسفانه خود چنج ست در ایمیل تکراری اروری رو نمی ده که هدر بگیره و نیاز به شرط نباشه

1 پسندیده

به نظر میاد یک کاری که نباید انجام بدی و داری انجام میدی فکر کنم باید از with در کنترلر استفاده کنی که با پترن :ok: فقط کار کنه
مثال ببین

  def create(conn, %{"user" => user_params}) do
    with {:ok, %User{} = user} <- Account.create_user(user_params) do
      conn
      |> put_status(:created)
      |> put_resp_header("location", user_path(conn, :show, user))
      |> render("show.json", user: user)
    end
  end
2 پسندیده

سام عزیز . به این صورت در صورتی که شرط اوکی باشه خوبه . البته من برای اینکه شرط اوکی هم باشه کمی اطلاعات برگشتیمو ویرایش کردم ولی اگر شرط اوکی نباشه ارور ۵۰۰ سروری می ده

تابع ساخت یوزر :

def create_user(full_name, email, password, last_ip, language, country) do
		password = Comeonin.Bcrypt.hashpwsalt(password)
		group_acl = "shahryar"
		params = %{full_name: full_name, email: email, password: password, last_ip: last_ip, language: language, country: country, group_acl: group_acl}
		
		changeset = UsersInfo.changeset(%UsersInfo{}, params)
		case Repo.insert(changeset) do
		  	{:ok, post} ->  {:ok, post} #%{valid?: true, post: post}
		  	{:error, changeset} ->  changeset    
		end

کنترلر ویرایش شده بر اساس فرمایش شما که لطفا کردید مشورت دادید

   def test(conn, %{"full_name" => full_name, "email" => email, "password" => password, "last_ip" => last_ip, "language" => language, "country" => country}) do
      with {:ok, %UsersInfo{} = user} <- UsersInfoQuery.create_user(full_name, email, password, last_ip, language, country) do
      
      conn
         |> put_status(:created)
         |> put_status(200)
         |> json(%{ error: "Your request has been successfully completed."}) 

      end
    end

ولی به هر صورت چه case چه with که من در ویدیو های فونیکس ۱.۳ دیدم برای معرفی خیلی ازش استفاده می شد برای بررسی چندین شرط هم باز مشرط تلقی می شود . اگر شرط بشود می تونیم با برگشت یک مپ تو کنرتلر برسی کنیمش مثل کد قبلی بنده خدمتتون دادم .

1 پسندیده

وقتی از تابع json استفاده میکنی در آخر باید conn برگردونی
render این کارو اتوماتیک میکنه

1 پسندیده

مثل اول رو true false هم میتونی پترن مچ کنی من حرفم این بود که change set خودش tuple :ok یا :error میده
ضمنا ۲۰۰ کد با error نده

1 پسندیده

سام عزیز اون چنج ست متاسفانه من چک کردم در زمانی که ایمیل دابلکیت بوده نمی یاد توپل error بده بخاطر همین ارور 500 می گیرم .

در رابطه با این render جز خودکار کرداین کار چیز دیگه ای که کمک کننده نیست نه ؟ چون فکر کنم با رندر باید بیام ویو هم درست کنم الان view درست نکردم ساده تره این روش

منظور از متغییر error استفاده نکنم درسته ؟ ولی اگر بخوام بیام مثلا چیزی رو به کاربر پس بدم بیام از متغییری بجز error استفاده کنم مثلا post ?

اینو میگم ۲۰۰
با ارور

اینو شما دیدی ?

تشکر سام عزیز از وقتی که می زاری

من تغییر دادم به این صورت

true ->
            conn		  
               |> put_status(200)
               |> json(%{post: regreq.post.full_name})  
      end 

الان درسته یا کلا با ۲۰۰ نباید هیج جی سانی بیارم ؟ به عنوان پیغام به کاربر

1 پسندیده

الان درسته

1 پسندیده

سام عزیز . الان این اومده کل شرط قرار داده تو یک متغییر بعد در آخر اون متغییر رو صدا زده . الان کد من داره کار می کنه ولی من نیومده در آخر صدا بزنم کد این طرف مثلا

def(conn, params) do
  final_conn = 
    case HTTPoison.get("https://*****.zendesk.com/api/v2/users/search.json?query=" <> clid, headers, [hackney: hackney]) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        conn
        |> put_status(200)
        |> json(body)
      {:ok, %HTTPoison.Response{status_code: 404}} ->
        conn
        |> put_status(404)
        |> json(%{error_code: "404", reason_given: "Resource not found."})
      {:error, %HTTPoison.Error{reason: reason}} ->
        conn
        |> put_status(500)
        |> json(%{error_code: "500", reason_given: "None."})
    end

  do_something_else(params)

  final_conn
en

حالا کد من اینطوری هست یعنی منم باید بیام اول case یک متغییر بزارم و آخر متغییر رو صدا بزنم یعنی کد زیر رو

def test(conn, %{"full_name" => full_name, "email" => email, "password" => password, "last_ip" => last_ip, "language" => language, "country" => country}) do
      
      regreq = UsersInfoQuery.create_user(full_name, email, password, last_ip, language, country)

      case regreq.valid? do
         false ->   
            conn
            |> put_status(404)
            |> json(%{error_code: "404"})
         true ->
            conn		  
               |> put_status(200)
               |> json(%{post: regreq.post.full_name})  
      end   
   end

تغییر بدم به کد زیر ؟

 def test(conn, %{"full_name" => full_name, "email" => email, "password" => password, "last_ip" => last_ip, "language" => language, "country" => country}) do
      
      regreq = UsersInfoQuery.create_user(full_name, email, password, last_ip, language, country)

      final_conn = 
      case regreq.valid? do
         false ->   
            conn
            |> put_status(400)
            |> json(%{error_code: "400"})
         true ->
            conn		  
               |> put_status(200)
               |> json(%{post: regreq.post.full_name})  
      end 
      final_conn  
   end

دلیلیشو تو توضیحات انگلیسی طرف هم متوجه نشدم چرا این کارو می کنه

این برمیگرده به architecture کلی فونیکس
در framework فونیکس Request به conn تبدیل میشه و یک pipeline درست میشه که در هر قسمت یک تابع روش اجرا میشه و به قسمت بعدی داده میشه
Plug ها همه conn میگیرن و conn پس میدن اگه conn پس ندی pipeline خراب میشه و خطا که گرفتی میبینی

1 پسندیده

بعد سام عزیز بنده اگر شما رو نداشتم باید چیکار می کردم ؟
ولاه تمام پروژه من از ۱۰۰ درصد ۹۹ درصد شما زدی تاحالا ممنونم

1 پسندیده

conn
|> endpoint
|> router
|> controller
|> model
|> view

خواهش میکنم

1 پسندیده

اینو رفاکتور کردم خودم تست نزدم ولی باید کار کنه

def test(conn, %{"full_name" => full_name, "email" => email, "password" => password, "last_ip" => last_ip, "language" => language, "country" => country}) do  
  regreq = UsersInfoQuery.create_user(full_name, email, password, last_ip, language, country)
  handle_query_result(conn, regreq.valid?, regreq)
end

def handle_query_result(conn, true, regreq) do
  conn    
   |> put_status(200)
   |> json(%{post: regreq.post.full_name})  
end

def handle_query_result(conn, false, _) do
  conn
    |> put_status(400)
    |> json(%{error_code: "400"})
end
1 پسندیده

ممنون سام عزیز پشت سیستم رفتم سریع تست می کنم. ممنونم ازت . اینطوری خیلی تمیز تر شده . آیا جز تمیز تر شدن کد در کارایی کد در سیستم هم تاثیر داره مثل پرو فانکشن و …


سام عزیز ممنون کار کرد تست کردم بدون مشکل و ممنون بخاطر تذکر و مشورتت واقعا نیاز بود یاداوری بشه بهم که دارم روی الکسیر کد می زنم و باید زیباتر کد بزنم . فقط سوال بالا هنوز برام هست جز خوانایی کد آیا در سرعت یا کارایی کد نیز موثر است یا خیر؟

1 پسندیده

نه در سرعت هیچ فرقی نداره چون کامپایل میشه

1 پسندیده