Zacznijmy od początku.
Chcemy nasz formularz rejestracyjny obiektu mieć taki sam API, jak każdego innego modelu ActiveRecord:
// view.html
<%= form_for(@book) do |f| %>
<% end %>
# controller.rb
def create
@book = Book.new(book_params)
if @book.save
redirect_to @book, notice: 'Book was successfully created.'
else
render :new
end
end
to zrobić, tworzymy następujący obiekt:
class RegForm
include ActiveModel::Model
attr_accessor :company_name, :email, :password
def save
# Save Location and Account here
end
end
Teraz o tym ActiveModel::Model
, nasz RegForm
zyskuje mnóstwo funkcjonalności, w tym pokazuje błędy i atrybuty sprawdzania poprawności (tak, nie ma konieczności dołączania ActiveModel::Validations
). W tym następnym kroku dodamy kilka walidacji:
validates :email, :password, presence: true
I zmieniamy save
tak że biegnie te walidacji:
def save
validate
# Save Location and Account here
end
validate
zostaje przywrócone true
jeśli przechodzą wszystkie walidacje i false
inaczej.
validate
dodaje również errors do @reg_form
(wszystkie modele ActiveRecord mieć errors
hash, który jest wypełniany, gdy walidacja nie powiedzie się). Oznacza to, że z punktu widzenia możemy zrobić każdy z nich:
@reg_form.errors.messages
#=> { email: ["can't be blank"], password: ["can't be blank"] }
@reg_form.errors.full_messages
#=> ["Email can't be blank", "Password can't be blank"]
@reg_form.errors[:email]
#=> ["can't be blank"]
@reg_form.errors.full_messages_for(:email)
#=> ["Email can't be blank"]
Tymczasem nasz RegistrationsController
powinien wyglądać mniej więcej tak:
def create
@reg_form = RegForm.new(reg_params)
if @reg_form.save
redirect_to @reg_form, notice: 'Registration was successful'
else
render :new
end
end
widzimy wyraźnie, że kiedy @reg_form.save
powraca false
, widok new
jest ponownie renderowany.
Wreszcie możemy zmienić save
tak, że nasze modele save
połączenia są owinięte wewnątrz transaction:
def save
if valid?
ActiveRecord::Base.transaction do
location = Location.create!(location_params)
account = location.create_account!(account_params)
end
true
end
rescue ActiveRecord::StatementInvalid => e
# Handle database exceptions not covered by validations.
# e.message and e.cause.message can help you figure out what happened
end
Punkty godne uwagi:
create!
jest używany zamiast create
. transaction jest wycofywany tylko wtedy, gdy zostanie zgłoszony wyjątek (które zwykle mają zastosowanie w przypadku huk).
validate
is just an alias for valid?
. Aby uniknąć wszystkie te wcięcia moglibyśmy zamiast używać strażnika na górze metody save
:
return if invalid?
Możemy zamienić wyjątek bazy danych (like an email uniqueness constraint) do błędu wykonując coś jak:
rescue ActiveRecord::RecordNotUnique
errors.add(:email, :taken)
end
Możemy dodać błąd nie jest bezpośrednio związany z atrybutem za pomocą symbolu :base
:
errors.add(:base, 'Company and Email do not match')
Dzięki, ale nie chcę wyświetlać wyjątku, chcę wyświetlić błędy sprawdzania poprawności z innych modeli. – Blankman