Pesan kesalahan validasi sepenuhnya kustom dengan Rails

260

Menggunakan Rails, saya mencoba mendapatkan pesan kesalahan seperti "Bidang lagu tidak boleh kosong" saat disimpan. Melakukan hal berikut:

validates_presence_of :song_rep_xyz, :message => "can't be empty"

... hanya menampilkan "Rep Lagu XYW tidak boleh kosong", yang tidak bagus karena judul bidangnya tidak ramah pengguna. Bagaimana saya bisa mengubah judul bidang itu sendiri? Saya dapat mengubah nama bidang di database, tetapi saya memiliki beberapa bidang "lagu" dan saya harus memiliki nama bidang tertentu.

Saya tidak ingin meretas proses validasi rails dan saya merasa harus ada cara untuk memperbaikinya.

marcgg
sumber

Jawaban:

432

Sekarang, cara yang diterima untuk mengatur nama manusiawi dan pesan kesalahan khusus adalah dengan menggunakan lokal .

# config/locales/en.yml
en:
  activerecord:
    attributes:
      user:
        email: "E-mail address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "is required"

Sekarang nama manusiawi dan pesan validasi keberadaan untuk atribut "email" telah diubah.

Pesan validasi dapat diatur untuk model + atribut tertentu, model, atribut, atau secara global.

greywh
sumber
19
Jika Anda menggunakan mongoid, ganti activerecord: dengan mongoid:
Intentss
88
@ abu-abu: Di mana seharusnya pertanyaan tentang jawaban diposting, jika tidak di komentar? Inilah panduan I18n: guides.rubyonrails.org/i18n.html
Tyler Rick
4
Omong-omong: jika Anda memberikan simbol untuk parameter pesan validator Anda di Rails 3.1.3, ia akan memberi tahu Anda ruang lingkup yang dicari karena tidak akan ditemukan, sehingga Anda tahu persis apa yang harus dimasukkan ke dalam locales yml.
aceofspades
4
Yah, ini baik-baik saja, tetapi bagaimana jika secara naif menuliskan nama kolom (tidak peduli seberapa mudah dibaca manusia) akan mengarah pada tata bahasa yang benar-benar ditingkatkan (terutama dalam bahasa non-Inggris)? Apakah saya benar-benar perlu menggunakannya errors.add :base, msg? Saya ingin tahu kolom tentang kesalahan ini, jadi saya bisa menampilkannya di kolom formulir yang benar.
panzi
6
@ graywh Mungkin saya melewatkan sesuatu, tetapi bukankah itu selalu mengawali nama kolom sebelum pesan? Bahkan dalam bahasa Inggris saya ingin memiliki eg The password is wrong.atau The email address is not valid.bukannya Password is wrong.dan Email is not valid..
panzi
65

Dalam model Anda:

validates_presence_of :address1, message: 'Put some address please' 

Menurut Anda

<% m.errors.each do |attr, msg|  %>
 <%= msg %>
<% end %>

Jika Anda melakukannya

<%= attr %> <%= msg %>

Anda mendapatkan pesan kesalahan ini dengan nama atribut

address1 Put some address please

jika Anda ingin mendapatkan pesan kesalahan untuk satu atribut tunggal

<%= @model.errors[:address1] %>
Federico
sumber
Itu bukan solusi yang bisa diterima. Bagaimana jika saya menginginkan perilaku default untuk semua atribut lainnya (attr + msg)?
Rômulo Ceccon
Ini dia .. Anda bisa bermain dengan 2 hal itu dan membuatnya bekerja
Federico
Anda harus menggunakan simbol sehingga akan terlihat di file yml Anda, sepertivalidates_presence_of :address1, :message => :put_some_address_please
Federico
Ini tidak dapat diterima, karena nama bidang disertakan
fatuhoku
62

Coba ini.

class User < ActiveRecord::Base
  validate do |user|
    user.errors.add_to_base("Country can't be blank") if user.country_iso.blank?
  end
end

Saya menemukan ini di sini .

Ini cara lain untuk melakukannya. Apa yang Anda lakukan adalah mendefinisikan metode human_attribute_name pada kelas model. Metode ini meneruskan nama kolom sebagai string dan mengembalikan string untuk digunakan dalam pesan validasi.

class User < ActiveRecord::Base

  HUMANIZED_ATTRIBUTES = {
    :email => "E-mail address"
  }

  def self.human_attribute_name(attr)
    HUMANIZED_ATTRIBUTES[attr.to_sym] || super
  end

end

Kode di atas berasal dari sini

Maulin
sumber
Masalahnya adalah bahwa bidang saya disebut: song_rep_xyz (well, sesuatu yang rumit), yang tidak ramah pengguna
marcgg
16
untuk Rails 3, "def self.human_attribute_name (attr)" perlu diubah menjadi "def self.human_attribute_name (attr, options = {})", jika tidak mengembalikan kesalahan
spacemonkey
3
Terima kasih untuk ini. Saya membutuhkan sesuatu yang berfungsi untuk Rails 2. (Ya, kasihan saya ... :)
Dan Barron
18

Ya, ada cara untuk melakukan ini tanpa plugin! Tapi itu tidak sebersih dan seanggun menggunakan plugin yang disebutkan. Ini dia.

Dengan asumsi Rails 3 (saya tidak tahu apakah itu berbeda di versi sebelumnya),

simpan ini di model Anda:

validates_presence_of :song_rep_xyz, :message => "can't be empty"

dan dalam tampilan, bukannya pergi

@instance.errors.full_messages

seperti ketika kita menggunakan generator scaffold, masukkan:

@instance.errors.first[1]

Dan Anda hanya akan mendapatkan pesan yang Anda tentukan dalam model, tanpa nama atribut.

Penjelasan:

#returns an hash of messages, one element foreach field error, in this particular case would be just one element in the hash:
@instance.errors  # => {:song_rep_xyz=>"can't be empty"}

#this returns the first element of the hash as an array like [:key,"value"]
@instance.errors.first # => [:song_rep_xyz, "can't be empty"]

#by doing the following, you are telling ruby to take just the second element of that array, which is the message.
@instance.errors.first[1]

Sejauh ini kami hanya menampilkan satu pesan saja, selalu untuk kesalahan pertama. Jika Anda ingin menampilkan semua kesalahan, Anda dapat mengulang di hash dan menunjukkan nilainya.

Harapan itu membantu.

Marco Antonio
sumber
16

Kode Rails3 dengan pesan yang dilokalkan sepenuhnya:

Dalam model, user.rb mendefinisikan validasi

validates :email, :presence => true

Di config / locales / en.yml

en:  
  activerecord:
    models: 
      user: "Customer"
    attributes:
      user:
        email: "Email address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "cannot be empty"
Lukas
sumber
15

Dalam metode validasi khusus, gunakan:

errors.add(:base, "Custom error message")

karena add_to_base telah ditinggalkan.

errors.add_to_base("Custom error message")

amit_saxena
sumber
13

Terkait dengan jawaban yang diterima dan jawaban lain di daftar :

Saya mengonfirmasi bahwa garpu nanamkim untuk custom-err-msg bekerja dengan Rails 5, dan dengan pengaturan lokal.

Anda hanya perlu memulai pesan lokal dengan tanda sisipan dan seharusnya tidak menampilkan nama atribut dalam pesan.

Model didefinisikan sebagai:

class Item < ApplicationRecord
  validates :name, presence: true
end

dengan yang berikut ini en.yml:

en:
  activerecord:
    errors:
      models:
        item:
          attributes:
            name:
              blank: "^You can't create an item without a name."

item.errors.full_messages akan ditampilkan:

You can't create an item without a name

bukannya yang biasa Name You can't create an item without a name

Rystraum
sumber
11

Saya sarankan menginstal permata custom_error_message (atau sebagai plugin ) yang awalnya ditulis oleh David Easley

Ini memungkinkan Anda melakukan hal-hal seperti:

validates_presence_of :non_friendly_field_name, :message => "^Friendly field name is blank"
Ryan Bigg
sumber
Saya telah menggunakan plugin ini di masa lalu dengan sangat sukses meskipun tampaknya tidak dipelihara secara teratur lagi.
Jared Brown
1
Anda juga dapat menginstalnya sebagai permata untuk rel 3. cukup tambahkan gem "custom_error_message" ke Gemfile Anda - lihat github untuk lebih jelasnya
Dorian
Persis yang saya butuhkan
olleicua
3
@DickieBoy Saya mengkonfirmasi bahwa fork nanamkim ( github.com/nanamkim/custom-err-msg ) bekerja dengan Rails 5. Ini sebenarnya berfungsi baik dengan jawaban yang diterima. Saya akan menuliskan ini sebagai jawaban terpisah.
Rystraum
@Rystraum Untuk kehidupan saya, saya tidak dapat mengingat usecase sekitar ini, tapi terima kasih atas jawabannya! Saya pasti akan mengingatnya untuk masa depan.
DickieBoy
10

Salah satu solusinya mungkin dengan mengubah format kesalahan standar i18n:

en:
  errors:
    format: "%{message}"

Defaultnya adalah format: %{attribute} %{message}

cappie013
sumber
7

Ini cara lain:

Jika Anda menggunakan templat ini:

<% if @thing.errors.any? %>
  <ul>
    <% @thing.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

Anda dapat menulis sendiri pesan khusus seperti ini:

class Thing < ActiveRecord::Base

  validate :custom_validation_method_with_message

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:_, "My custom message")
    end
  end

Dengan cara ini, karena garis bawah, pesan lengkap menjadi "Pesan khusus saya", tetapi ruang ekstra pada awalnya tidak terlalu mencolok. Jika Anda benar-benar tidak menginginkan ruang ekstra di awal, cukup tambahkan .lstripmetode ini.

<% if @thing.errors.any? %>
  <ul>
    <% @thing.errors.full_messages.each do |message| %>
      <li><%= message.lstrip %></li>
    <% end %>
  </ul>
<% end %>

Metode String.lstrip akan menghilangkan ruang ekstra yang dibuat oleh ': _' dan akan membiarkan pesan kesalahan lainnya tidak berubah.

Atau bahkan lebih baik, gunakan kata pertama dari pesan khusus Anda sebagai kuncinya:

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:my, "custom message")
    end
  end

Sekarang pesan lengkapnya adalah "Pesan khusus saya" tanpa ruang tambahan.

Jika Anda ingin pesan lengkap dimulai dengan kata dengan huruf besar seperti "URL tidak boleh kosong" tidak dapat dilakukan. Alih-alih mencoba menambahkan beberapa kata lain sebagai kuncinya:

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:the, "URL can't be blank")
    end
  end

Sekarang pesan lengkapnya adalah "URL tidak boleh kosong"

Cruz Nunez
sumber
ooo, Anda bahkan dapat melakukan errors.add(:_, 'foobar')dan mendapatkan 'foobar' sebagai pesan
xxjjnn
6

Lakukan saja seperti biasa:

validates_presence_of :email, :message => "Email is required."

Tetapi tampilkan seperti ini sebagai gantinya

<% if @user.errors.any? %>
  <% @user.errors.messages.each do |message| %>
    <div class="message"><%= message.last.last.html_safe %></div>
  <% end %>
<% end %>

Kembali

"Email is required."

Metode pelokalan jelas merupakan cara yang "tepat" untuk melakukan ini, tetapi jika Anda melakukan sedikit, proyek non-global dan hanya ingin cepat - ini jelas lebih mudah daripada file hopping.

Saya menyukainya karena kemampuan untuk menempatkan nama bidang di suatu tempat selain dari awal string:

validates_uniqueness_of :email, :message => "There is already an account with that email."
brittohalloran
sumber
2

Jika Anda ingin mendaftar semuanya dalam daftar yang bagus tetapi tanpa menggunakan nama yang tidak ramah manusiawi yang kasar, Anda dapat melakukan ini ...

object.errors.each do |attr,message|
  puts "<li>"+message+"</li>"
end
adam
sumber
1

Menurut Anda

object.errors.each do |attr,msg|
  if msg.is_a? String
    if attr == :base
      content_tag :li, msg
    elsif msg[0] == "^"
      content_tag :li, msg[1..-1]
    else
      content_tag :li, "#{object.class.human_attribute_name(attr)} #{msg}"
    end
  end
end

Ketika Anda ingin mengganti pesan kesalahan tanpa nama atribut, cukup tambahkan pesan dengan ^ seperti:

validates :last_name,
  uniqueness: {
    scope: [:first_name, :course_id, :user_id],
    case_sensitive: false,
    message: "^This student has already been registered."
  }
luckyruby
sumber
tidak bekerja dengan rel 5.1 / ruby ​​2.4? mendapatkan nama model dalam lingkup itu
Ben
@Ben Bekerja untuk saya di Rails 5.1.2, Ruby 2.4.1p111. Bisakah Anda membagikan kode Anda?
luckyruby
saya kira saya harus melihat lebih jauh, Anda dapat memeriksa kode dan jawabannya di sana stackoverflow.com/q/45128434/102133
Ben
0

Saya mencoba mengikuti, bekerja untuk saya :)

1 pekerjaan.rb

class Job < ApplicationRecord
    validates :description, presence: true
    validates :title, 
              :presence => true, 
              :length => { :minimum => 5, :message => "Must be at least 5 characters"}
end

2 jobs_controller.rb

def create
      @job = Job.create(job_params)
      if @job.valid?
        redirect_to jobs_path
      else
        render new_job_path
      end     
    end

3 _form.html.erb

<%= form_for @job do |f| %>
  <% if @job.errors.any? %>
    <h2>Errors</h2>
    <ul>
      <% @job.errors.full_messages.each do |message|%>
        <li><%= message %></li>
      <% end %>  
    </ul>
  <% end %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :description %>
    <%= f.text_area :description, size: '60x6' %>

  </div>
  <div>
    <%= f.submit %>
  </div>
<% end %> 
Aigul
sumber
0

Ini kode saya yang dapat berguna bagi Anda jika Anda masih membutuhkannya: Model saya:

validates :director, acceptance: {message: "^Please confirm that you are a director of the company."}, on: :create, if: :is_director?

Lalu saya telah membuat pembantu untuk menampilkan pesan:

module ErrorHelper
  def error_messages!
    return "" unless error_messages?
    messages = resource.errors.full_messages.map { |msg|
       if msg.present? && !msg.index("^").nil?
         content_tag(:p, msg.slice((msg.index("^")+1)..-1))
       else
         content_tag(:p, msg)
       end
    }.join

    html = <<-HTML
      <div class="general-error alert show">
        #{messages}
      </div>
    HTML

    html.html_safe
  end

  def error_messages?
    !resource.errors.empty?
  end
end
Oleksii Danylevskyi
sumber
0

Suatu pendekatan unik yang saya belum pernah melihat orang menyebutkan!

Satu-satunya cara saya bisa mendapatkan semua penyesuaian yang saya inginkan adalah menggunakan after_validationpanggilan balik untuk memungkinkan saya memanipulasi pesan kesalahan.

  1. Izinkan pesan validasi dibuat seperti biasa, Anda tidak perlu mencoba dan mengubahnya di pembantu validasi.

  2. buat after_validationpanggilan balik yang akan menggantikan pesan validasi di back-end sebelum sampai ke tampilan.

  3. Dalam after_validationmetode ini Anda dapat melakukan apa pun yang Anda inginkan dengan pesan validasi, seperti string normal! Anda bahkan dapat menggunakan nilai dinamis dan memasukkannya ke dalam pesan validasi.


#this could be any validation
validates_presence_of :song_rep_xyz, :message => "whatever you want - who cares - we will replace you later"

after_validation :replace_validation_message

def replace_validation_message
    custom_value = #any value you would like
    errors.messages[:name_of_the_attribute] = ["^This is the replacement message where 
    you can now add your own dynamic values!!! #{custom_value}"]
end

Metode after_validation akan memiliki cakupan yang jauh lebih besar daripada pembantu validasi rel bawaan, jadi Anda akan dapat mengakses objek yang Anda validasi seperti yang Anda coba lakukan dengan object.file_name. Yang tidak berfungsi dalam pembantu validasi tempat Anda mencoba memanggilnya.

Catatan: kami menggunakan ^untuk menyingkirkan nama atribut di awal validasi sebagaimana @Rystraum menunjukkan referensi permata ini

Sami Birnbaum
sumber
0

graywh's jawaban adalah yang terbaik jika itu benar-benar lokal berbeda dalam menampilkan nama bidang. Dalam hal nama bidang dinamis (berdasarkan bidang lain untuk ditampilkan), saya akan melakukan sesuatu seperti ini

<% object.errors.each do |attr, msg| %>
<li>
  <% case attr.to_sym %>
  <% when :song_rep_xyz %>
    <%= #display error how you want here %>
  <% else %>
    <%= object.errors.full_message(attr, msg) %>
  <% end %>
</li>
<% end %>

metode full_message pada yang lain adalah apa yang digunakan rel di dalam metode full_messages, sehingga itu akan memberikan kesalahan Rails normal untuk kasus lain (Rails 3.2 dan lebih tinggi)

An Nguyen Dang
sumber