2016-08-01 12 views
8

Mam wzór formularza rejestracyjnego, która pobiera dane wprowadzone przez użytkownika podczas rejestracji:Wielu oszczędność modelu, jak owinąć w transakcji oraz zgłaszania błędów

class RegForm 
    include ActiveModel::Model 
    include ActiveModel::Validations 

    attr_accessor :company_name, :email, :password 
    validates_presence_of # ... 

end 

Podczas tego procesu rejestracji mam wiele modeli, które muszą być tworzone, i nie jestem pewien, jak poprawnie wyświetlać komunikaty o błędach i jak wypuścić komunikaty o błędach modelu z powrotem do interfejsu użytkownika.

if @reg_form.valid? 
    account = Account.create!(@reg_form) 
else 
... 

Account.create! wygląda następująco:

def self.create!(reg_form) 
    account = Account.create_from_reg!(reg_form) 
    location = location.create_from_reg!(account, reg_form) 
    .. 
    .. 
    account.location = location 
    .. 
    account.save! 

    account 
end 
  1. Więc jestem zdezorientowany jak wyświetlać komunikaty o błędach dla wszystkich tych modeli, które są oszczędności
  2. jak wyświetlić lub nie sprawdzanie czy reg_form nie ma odpowiednich danych dla wszystkich inne modele.
  3. jak zapewnić, że jest zawijany w transakcji, więc nie zapisuję niczego, jeśli któryś model nie zapisuje podczas rejestracji.

Odpowiedz

3

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:

  1. 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).

  2. 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? 
    
  3. 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 
    
  4. 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') 
    
0

Myślę, że transakcje i obsługa błędów pomogą rozwiązać problem.

def save_or_rescue 
    ActiveRecord::Base.transaction do 
    account = Account.create_from_reg!(reg_form) 
    location = location.create_from_reg!(account, reg_form) 
    ... 
    end 

rescue ActiveRecord::RecordInvalid => exception 
    puts exception 
end 
+0

Dzięki, ale nie chcę wyświetlać wyjątku, chcę wyświetlić błędy sprawdzania poprawności z innych modeli. – Blankman

Powiązane problemy