edge
Daugiau informacijos rubyonrails.org: Daugiau apie Ruby on Rails

Aktyviųjų įrašų atgaliniai kvietimai

Šis vadovas moko, kaip įsikišti į jūsų aktyviųjų įrašų objektų gyvavimo ciklo veikimą.

Po šio vadovo perskaitymo žinosite:

1 Objekto gyvavimo ciklas

Vykstant įprastam „Rails“ programos veikimui, objektai gali būti sukurti, atnaujinti ir sunaikinti. Aktyvusis įrašas suteikia galimybę įsikišti į šį objekto gyvavimo ciklą, kad galėtumėte valdyti savo programą ir jos duomenis.

Atgaliniai kvietimai leidžia jums paleisti logiką prieš arba po objekto būsenos pakeitimo.

class Kūdikis < ApplicationRecord
  after_create -> { puts "Sveikiname!" }
end
irb> @baby = Baby.create
Sveikiname!

Kaip matysite, yra daug gyvavimo ciklo įvykių, ir galite pasirinkti prisijungti prie bet kurio iš jų, arba prieš juos, arba po jų.

2 Atgalinių kvietimų apžvalga

Atgaliniai kvietimai yra metodai, kurie yra iškviečiami tam tikru objekto gyvavimo ciklo momentu. Su atgaliniais kvietimais galima parašyti kodą, kuris bus vykdomas kiekvieną kartą, kai aktyvusis įrašo objektas yra sukurtas, išsaugotas, atnaujintas, ištrintas, patikrintas arba įkeltas iš duomenų bazės.

2.1 Atgalinio kvietimo registracija

Norėdami naudoti galimus atgalinius kvietimus, turite juos užsiregistruoti. Galite įgyvendinti atgalinius kvietimus kaip įprastus metodus ir naudoti makro stiliaus klasės metodus, kad užsiregistruotumėte juos kaip atgalinius kvietimus:

class Vartotojas < ApplicationRecord
  validates :prisijungimas, :el_paštas, presence: true

  before_validation :užtikrinkite_prisijungimą_turi_reikšmę

  private
    def užtikrinkite_prisijungimą_turi_reikšmę
      if prisijungimas.blank?
        self.prisijungimas = el_paštas unless el_paštas.blank?
      end
    end
end

Makro stiliaus klasės metodai taip pat gali priimti bloką. Svarstykite naudoti šį stilių, jei jūsų bloko viduje esantis kodas yra tokio trumpumo, kad jis telpa vienoje eilutėje:

class Vartotojas < ApplicationRecord
  validates :prisijungimas, :el_paštas, presence: true

  before_create do
    self.vardas = prisijungimas.capitalize if vardas.blank?
  end
end

Alternatyviai galite perduoti proc objektą atgaliniam kvietimui, kuris bus iškviečiamas.

class Vartotojas < ApplicationRecord
  before_create ->(vartotojas) { vartotojas.vardas = vartotojas.prisijungimas.capitalize if vartotojas.vardas.blank? }
end

Galiausiai galite apibrėžti savo atgalinį kvietimo objektą, apie kurį išsamiau kalbėsime vėliau žemiau.

class Vartotojas < ApplicationRecord
  before_create MaybeAddName
end

class MaybeAddName
  def self.before_create(įrašas)
    if įrašas.vardas.blank?
      įrašas.vardas = įrašas.prisijungimas.capitalize
    end
  end
end

Atgaliniai kvietimai taip pat gali būti užregistruoti, kad būtų paleisti tik tam tikri gyvavimo ciklo įvykiai, tai leidžia visiškai kontroliuoti, kada ir kokiu kontekstu bus paleisti jūsų atgaliniai kvietimai.

class Vartotojas < ApplicationRecord
  before_validation :normalizuok_vardą, on: :create

  # :on taip pat priima masyvą
  after_validation :nustatyk_vieta, on: [ :create, :update ]

  private
    def normalizuok_vardą
      self.vardas = vardas.downcase.titleize
    end

    def nustatyk_vieta
      self.vieta = LocationService.query(self)
    end
end

Laikoma geru būdu deklaruoti atgalinius kvietimus kaip privačius metodus. Jei jie lieka vieši, juos galima iškviesti iš modelio išorės ir pažeisti objekto uždarumo principą.

ĮSPĖJIMAS. Venkite kvietimų į update, save ar kitus metodus, kurie sukuria šalutinį poveikį objektui, savo atgaliniuose kvietimuose. Pavyzdžiui, nekvieskite update(atributas: "reikšmė") atgaliniame kvietime. Tai gali pakeisti modelio būseną ir gali sukelti netikėtus šalutinius poveikius vykdant įsipareigojimą. Vietoj to, galite saugiai priskirti reikšmes tiesiogiai (pavyzdžiui, self.atributas = "reikšmė") before_create / before_update ar ankstesniuose atgaliniuose kvietimuose.

3 Galimi atgaliniai kvietimai

Čia pateikiamas sąrašas su visais galimais aktyviųjų įrašų atgaliniais kvietimais, išvardytais tokia pačia tvarka, kaip jie bus iškviesti atitinkamų operacijų metu:

3.1 Objekto sukūrimas

3.2 Objekto atnaujinimas

ĮSPĖJIMAS. after_save vykdomas tiek kuriant, tiek atnaujinant, bet visada po specifinių atgalinių kvietimų after_create ir after_update, nepriklausomai nuo to, kokia tvarka buvo vykdyti makro kvietimai.

3.3 Objekto sunaikinimas

PASTABA: before_destroy atgaliniai kvietimai turėtų būti įdėti prieš dependent: :destroy asociacijas (arba naudoti prepend: true parinktį), kad būtų užtikrinta, jog jie bus vykdomi prieš įrašai bus ištrinti pagal dependent: :destroy.

ĮSPĖJIMAS. after_commit suteikia labai skirtingas garantijas nei after_save, after_update ir after_destroy. Pavyzdžiui, jei after_save įvyksta išimtis, transakcija bus atšaukta ir duomenys nebus išsaugoti. O viskas, kas vyksta after_commit, gali garantuoti, kad transakcija jau baigta ir duomenys buvo išsaugoti duomenų bazėje. Daugiau informacijos apie transakcinius atgalinius kvietimus žemiau.

3.4 after_initialize ir after_find

Kiekvieną kartą, kai yra sukuriamas Active Record objektas, after_initialize atgalinis kvietimas bus iškviestas, arba tiesiogiai naudojant new, arba kai įrašas yra įkeliamas iš duomenų bazės. Tai gali būti naudinga, norint išvengti būtinybės tiesiogiai perrašyti Active Record initialize metodą.

Įkeliant įrašą iš duomenų bazės, after_find atgalinis kvietimas bus iškviestas. after_find yra iškviestas prieš after_initialize, jei abu yra apibrėžti.

PASTABA: after_initialize ir after_find atgaliniai kvietimai neturi before_* atitikmenų.

Jie gali būti užregistruoti taip pat kaip ir kiti Active Record atgaliniai kvietimai.

class User < ApplicationRecord
  after_initialize do |user|
    puts "Jūs sukūrėte objektą!"
  end

  after_find do |user|
    puts "Jūs radote objektą!"
  end
end
irb> User.new
Jūs sukūrėte objektą!
=> #<User id: nil>

irb> User.first
Jūs radote objektą!
Jūs sukūrėte objektą!
=> #<User id: 1>

3.5 after_touch

after_touch atgalinis kvietimas bus iškviestas, kai tik yra palietamas Active Record objektas.

class User < ApplicationRecord
  after_touch do |user|
    puts "Jūs palietėte objektą"
  end
end
irb> u = User.create(name: 'Kuldeep')
=> #<User id: 1, name: "Kuldeep", created_at: "2013-11-25 12:17:49", updated_at: "2013-11-25 12:17:49">

irb> u.touch
Jūs palietėte objektą
=> true

Tai gali būti naudojama kartu su belongs_to:

class Book < ApplicationRecord
  belongs_to :library, touch: true
  after_touch do
    puts 'Knyga buvo palietė'
  end
end

class Library < ApplicationRecord
  has_many :books
  after_touch :log_when_books_or_library_touched

  private
    def log_when_books_or_library_touched
      puts 'Knyga/Biblioteka buvo palietė'
    end
end
irb> @book = Book.last
=> #<Book id: 1, library_id: 1, created_at: "2013-11-25 17:04:22", updated_at: "2013-11-25 17:05:05">

irb> @book.touch # iškviečia @book.library.touch
Knyga buvo palietė
Knyga/Biblioteka buvo palietė
=> true

4 Vykdomi atgaliniai kvietimai

Šie metodai iškviečia atgalinius kvietimus:

  • create
  • create!
  • destroy
  • destroy!
  • destroy_all
  • destroy_by
  • save
  • save!
  • save(validate: false)
  • toggle!
  • touch
  • update_attribute
  • update
  • update!
  • valid?

Be to, after_find atgalinis kvietimas yra iškviečiamas šių paieškos metodų:

  • all
  • first
  • find
  • find_by
  • find_by_*
  • find_by_*!
  • find_by_sql
  • last

after_initialize atgalinis kvietimas yra iškviečiamas kiekvieną kartą, kai yra sukuriamas naujas klasės objektas.

PASTABA: find_by_* ir find_by_*! metodai yra dinaminiai paieškos metodai, kurie automatiškai generuojami kiekvienam atributui. Sužinokite daugiau apie juos Dinaminiai paieškos metodai skyriuje.

5 Atgalinių kvietimų praleidimas

Kaip ir su validacijomis, taip pat galima praleisti atgalinius kvietimus naudojant šiuos metodus:

  • decrement!
  • decrement_counter
  • delete
  • delete_all
  • delete_by
  • increment!
  • increment_counter
  • insert
  • insert!
  • insert_all
  • insert_all!
  • touch_all
  • update_column
  • update_columns
  • update_all
  • update_counters
  • upsert
  • upsert_all

Tačiau šiuos metodus reikia naudoti atsargiai, nes svarbios verslo taisyklės ir programos logika gali būti laikomos atgaliniuose kvietimuose. Juos apeinant, nesuprasdami galimų padarinių, gali būti gauti netinkami duomenys.

6 Vykdomojo kodo sustabdymas

Kai pradedate registruoti naujus atgalinius kvietimus savo modeliams, jie bus įtraukti į vykdymo eilę. Ši eilė apims visus modelio tikrinimus, užregistruotus atgalinius kvietimus ir duomenų bazės operaciją, kuri bus vykdoma.

Visa atgalinių kvietimų grandinė yra apgaubta transakcija. Jei bet kuris atgalinis kvietimas iškelia išimtį, vykdymo grandinė bus sustabdyta ir bus išduotas ROLLBACK. Norint sąmoningai sustabdyti grandinę, naudokite:

throw :abort

ĮSPĖJIMAS. Bet kokia išimtis, kuri nėra ActiveRecord::Rollback arba ActiveRecord::RecordInvalid, bus iškelta iš naujo pagal „Rails“, kai atgalinė grandinė bus sustabdyta. Be to, tai gali sugadinti kodą, kuris nesitiki, kad metodai kaip save ir update (kurie įprastai bando grąžinti true arba false) iškels išimtį.

PASTABA: Jei after_destroy, before_destroy arba around_destroy atgaliniame kvietime iškyla ActiveRecord::RecordNotDestroyed, ji nebus iškelta iš naujo ir destroy metodas grąžins false.

7 Reliaciniai atgaliniai kvietimai

Atgaliniai kvietimai veikia per modelio ryšius ir gali būti apibrėžti pagal juos. Pavyzdžiui, pagalvokime apie situaciją, kurioje vartotojas turi daug straipsnių. Vartotojo straipsniai turėtų būti sunaikinami, jei vartotojas yra sunaikinamas. Pridėkime after_destroy atgalinį kvietimą prie User modelio per jo ryšį su Article modeliu:

class User < ApplicationRecord
  has_many :articles, dependent: :destroy
end

class Article < ApplicationRecord
  after_destroy :log_destroy_action

  def log_destroy_action
    puts 'Straipsnis sunaikintas'
  end
end
irb> user = User.first
=> #<User id: 1>
irb> user.articles.create!
=> #<Article id: 1, user_id: 1>
irb> user.destroy
Straipsnis sunaikintas
=> #<User id: 1>

8 Sąlyginiai atgaliniai kvietimai

Kaip ir su validacijomis, galime padaryti atgalinio kvietimo metodo iškvietimą sąlyginį, priklausomai nuo tam tikro predikato tenkinimo. Tai galime padaryti naudodami :if ir :unless parametrus, kurie gali priimti simbolį, Proc arba masyvą.

Galite naudoti :if parametrą, kai norite nurodyti, kada atgalinis kvietimas turėtų būti iškviestas. Jei norite nurodyti sąlygas, kada atgalinis kvietimas neturėtų būti iškviestas, galite naudoti :unless parametrą.

8.1 Naudodami :if ir :unless su simboliu

Galite susieti :if ir :unless parametrus su simboliu, kuris atitinka predikato metodo pavadinimą, kuris bus iškviestas tiesiogiai prieš atgalinį kvietimą.

Naudodami :if parametrą, atgalinis kvietimas nebus vykdomas, jei predikato metodas grąžins false; naudojant :unless parametrą, atgalinis kvietimas nebus vykdomas, jei predikato metodas grąžins true. Tai yra dažniausiai naudojama parinktis.

class Order < ApplicationRecord
  before_save :normalize_card_number, if: :paid_with_card?
end

Naudojant šią registracijos formą taip pat galima užregistruoti kelis skirtingus predikatus, kurie turėtų būti iškviesti, norint patikrinti, ar atgalinis kvietimas turėtų būti vykdomas. Apie tai kalbėsime žemiau.

8.2 Naudodami :if ir :unless su Proc

Galima susieti :if ir :unless su Proc objektu. Ši parinktis geriausiai tinka rašant trumpus validacijos metodus, paprastai vienos eilutės:

class Order < ApplicationRecord
  before_save :normalize_card_number,
    if: Proc.new { |order| order.paid_with_card? }
end

Kadangi Proc yra įvertinamas objekto kontekste, taip pat galima rašyti taip:

class Order < ApplicationRecord
  before_save :normalize_card_number, if: Proc.new { paid_with_card? }
end

8.3 Kelios atgalinio kvietimo sąlygos

if ir unless parametrai taip pat priima Proc arba metodo pavadinimų masyvą:

class Comment < ApplicationRecord
  before_save :filter_content,
    if: [:subject_to_parental_control?, :untrusted_author?]
end

Lengvai galite įtraukti Proc į sąlygų sąrašą:

class Comment < ApplicationRecord
  before_save :filter_content,
    if: [:subject_to_parental_control?, Proc.new { untrusted_author? }]
end

8.4 Naudodami tiek :if, tiek :unless

Atgaliniai kvietimai gali derinti tiek :if, tiek :unless vienoje deklaracijoje:

class Comment < ApplicationRecord
  before_save :filter_content,
    if: Proc.new { forum.parental_control? },
    unless: Proc.new { author.trusted? }
end

Atgalinis kvietimas vykdomas tik tada, kai visos :if sąlygos ir jokios :unless sąlygos neįvertintos kaip true.

9 Atgalinio kvietimo klasės

Kartais rašomi atgaliniai kvietimai bus pakankamai naudingi, kad juos galima būtų perpanaudoti kituose modeliuose. Active Record leidžia kurti klases, kurios apgaubia atgalinius kvietimus, kad juos būtų galima perpanaudoti.

Čia pateikiamas pavyzdys, kai sukuriamos klasės su after_destroy atgaliniu kvietimu, skirtu tvarkyti ištrintų failų valymą iš failų sistemos. Šis elgesys gali nebūti unikalus mūsų PictureFile modelyje ir galime norėti jį bendrinti, todėl gerai būtų tai apgaubti į atskirą klasę. Tai padarys testavimą ir pakeitimus šio elgesio labai lengvesnius.

class FileDestroyerCallback
  def after_destroy(file)
    if File.exist?(file.filepath)
      File.delete(file.filepath)
    end
  end
end

Kai deklaruojama klasėje, kaip pavyzdžiui aukščiau, atgaliniai kvietimo metodai gaus modelio objektą kaip parametrą. Tai veiks bet kuriame modelyje, kuris naudoja klasę taip:

class PictureFile < ApplicationRecord
  after_destroy FileDestroyerCallback.new
end

Atkreipkite dėmesį, kad turėjome sukurti naują FileDestroyerCallback objektą, nes deklaravome atgalinį kvietimą kaip objekto metodo. Tai ypač naudinga, jei atgaliniai kvietimai naudoja sukurto objekto būseną. Tačiau dažnai bus prasmingiau deklaruoti atgalinius kvietimus kaip klasės metodus:

class FileDestroyerCallback
  def self.after_destroy(file)
    if File.exist?(file.filepath)
      File.delete(file.filepath)
    end
  end
end

Kai atgalinio kvietimo metodas yra deklaruojamas šiuo būdu, mūsų modelyje nebereikės sukurti naujo FileDestroyerCallback objekto.

class PictureFile < ApplicationRecord
  after_destroy FileDestroyerCallback
end

Savo atgalinių kvietimų klasėse galite deklaruoti tiek kvietimų, kiek norite.

10 Transakcijos atgaliniai kvietimai

10.1 Užtikrinant nuoseklumą

Yra du papildomi atgaliniai kvietimai, kurie yra iškviečiami po duomenų bazės transakcijos užbaigimo: after_commit ir after_rollback. Šie atgaliniai kvietimai labai panašūs į after_save atgalinį kvietimą, išskyrus tai, kad jie nevykdomi, kol duomenų bazės pakeitimai yra patvirtinti arba atšaukti. Jie yra labiausiai naudingi, kai jūsų Active Record modeliams reikia sąveikauti su išorinėmis sistemomis, kurios nėra dalis duomenų bazės transakcijos. Pavyzdžiui, apsvarstykime ankstesnį pavyzdį, kai modeliui PictureFile reikia ištrinti failą po susijusio įrašo sunaikinimo. Jei po after_destroy atkakliojo iškvietimo kyla išimtis ir transakcija grąžinama atgal, failas bus ištrintas, o modelis liks nesuderintas. Pavyzdžiui, tarkime, kad picture_file_2 kode žemiau nėra galiojantis ir save! metodas iškelia klaidą.

PictureFile.transaction do
  picture_file_1.destroy
  picture_file_2.save!
end

Naudodami after_commit atkaklą galime tai apsvarstyti.

class PictureFile < ApplicationRecord
  after_commit :delete_picture_file_from_disk, on: :destroy

  def delete_picture_file_from_disk
    if File.exist?(filepath)
      File.delete(filepath)
    end
  end
end

PASTABA: :on parametras nurodo, kada bus iškviečiamas atkaklis. Jei nepateikiate :on parametro, atkaklis bus iškviestas kiekvienai veiksmui.

10.2 Kontekstas svarbus

Kadangi after_commit atkaklą dažniausiai naudojame tik su kūrimu, atnaujinimu ar ištrynimu, yra sinonimai šiems veiksmams:

class PictureFile < ApplicationRecord
  after_destroy_commit :delete_picture_file_from_disk

  def delete_picture_file_from_disk
    if File.exist?(filepath)
      File.delete(filepath)
    end
  end
end

ĮSPĖJIMAS. Kai transakcija baigiasi, after_commit arba after_rollback atkakliai yra iškviesti visiems modeliams, kurie buvo sukurti, atnaujinti ar ištrinti per tą transakciją. Tačiau, jei išimtis iškyla viename iš šių atkaklių, išimtis bus perduota aukštyn ir bet kokie likę after_commit arba after_rollback metodai nebus vykdomi. Todėl, jei jūsų atkaklio kodas gali sukelti išimtį, turėsite ją pagauti ir tvarkyti atkaklyje, kad leistumėte vykdyti kitus atkaklius.

ĮSPĖJIMAS. Kodas, vykdomas after_commit arba after_rollback atkakliuose, pats savaime nėra apgaubtas transakcija.

ĮSPĖJIMAS. Naudojant tiek after_create_commit, tiek after_update_commit su tuo pačiu metodo pavadinimu, veiks tik paskutinis apibrėžtas atkaklis, nes abu jie viduje yra sinonimai after_commit, kuris perrašo anksčiau apibrėžtus atkaklius su tuo pačiu metodo pavadinimu.

class User < ApplicationRecord
  after_create_commit :log_user_saved_to_db
  after_update_commit :log_user_saved_to_db

  private
    def log_user_saved_to_db
      puts 'Vartotojas buvo išsaugotas duomenų bazėje'
    end
end
irb> @user = User.create # nieko nespausdina

irb> @user.save # atnaujinama @user
Vartotojas buvo išsaugotas duomenų bazėje

10.3 after_save_commit

Taip pat yra after_save_commit, kuris yra sinonimas naudojant after_commit atkaklį tiek kūrimui, tiek atnaujinimui kartu:

class User < ApplicationRecord
  after_save_commit :log_user_saved_to_db

  private
    def log_user_saved_to_db
      puts 'Vartotojas buvo išsaugotas duomenų bazėje'
    end
end
irb> @user = User.create # kuriamas vartotojas
Vartotojas buvo išsaugotas duomenų bazėje

irb> @user.save # atnaujinama @user
Vartotojas buvo išsaugotas duomenų bazėje

10.4 Transakcijų atkaklių tvarka

Kai apibrėžiame kelis transakcinius after_ atkaklius (after_commit, after_rollback, ir t.t.), jų tvarka bus apversta nuo tos, kuri buvo apibrėžta.

class User < ActiveRecord::Base
  after_commit { puts("tai iš tikrųjų bus iškviesta antra") }
  after_commit { puts("tai iš tikrųjų bus iškviesta pirmoji") }
end

PASTABA: Tai taikoma ir visiems after_*_commit variantams, pvz., after_destroy_commit.

Atsiliepimai

Jūs esate skatinami padėti pagerinti šio vadovo kokybę.

Prašome prisidėti, jei pastebite rašybos klaidų ar faktinių klaidų. Norėdami pradėti, galite perskaityti mūsų dokumentacijos prisidėjimo skyrių.

Taip pat gali būti nepilnos informacijos arba informacijos, kuri nėra atnaujinta. Prašome pridėti bet kokią trūkstamą dokumentaciją pagrindiniam. Patikrinkite Edge vadovus pirmiausia, ar problemas jau išspręsta arba ne pagrindinėje šakoje. Patikrinkite Ruby on Rails vadovų gaires dėl stiliaus ir konvencijų.

Jei dėl kokios nors priežasties pastebite kažką, ką reikia ištaisyti, bet negalite patys tai pataisyti, prašome pranešti apie problemą.

Ir galiausiai, bet ne mažiau svarbu, bet koks diskusijos dėl Ruby on Rails dokumentacijos yra labai laukiamos oficialiame Ruby on Rails forume.