Modelele ActiveRecord din Rails fac deja o mulțime de ridicări grele, în ceea ce privește accesul la baze de date și relațiile model, dar cu puțină muncă, pot face mai multe lucruri în mod automat. Hai să aflăm cum!
Această idee funcționează pentru orice tip de proiect ActiveRecord; cu toate acestea, din moment ce Rails este cel mai comun, vom folosi acel exemplu pentru aplicația noastră. Aplicația pe care o vom folosi are multe Utilizatori, fiecare dintre care poate efectua o serie de acțiuni proiecte .
Dacă nu ați creat niciodată o aplicație Rails, citiți mai întâi acest tutorial sau program. În caz contrar, aprindeți consola veche și tastați rails new example_app
pentru a crea aplicația și apoi a schimba directoarele la noua aplicație cu cd example_app
.
În primul rând, generăm utilizatorul care va deține:
șine genera schelă Nume utilizator: e-mail text: șir parola_hash: text
Probabil, într-un proiect al unei lumi reale, vom avea câțiva câmpuri, dar acest lucru va face pentru moment. Să generăm în continuare modelul nostru de proiect:
șine generă schelă Numele proiectului: text started_at: datetime started_by_id: integer completed_at: datetime completed_by_id: integer
Apoi editați generația generată project.rb
fișier pentru a descrie relația dintre utilizatori și proiecte:
clasă < ActiveRecord::Base belongs_to :starter, :class_name =>"Utilizator",: foreign_key => "started_by_id" belongs_to: completer,: class_name => "Utilizator",: foreign_key => "finalizat_by_id" sfârșit
și relația inversă în user.rb
:
utilizator de clasă < ActiveRecord::Base has_many :started_projects, :foreign_key =>"started_by_id" has_many: completed_projects,: foreign_key => "completed_by_id" final
Apoi, alerga rapid rake db: migrați
, și suntem gata să începem să devenim inteligenți cu aceste modele. Dacă numai relațiile cu modelele au fost la fel de ușor în lumea reală! Acum, dacă ați mai folosit cadrul Rails înainte, probabil că nu ați învățat nimic ... totuși!
Primul lucru pe care îl vom face este să folosim câteva câmpuri de generare automată. Ați observat că atunci când am creat modelul, am creat un hash de parolă și nu un câmp de parolă. Vom crea un atribut faux pentru o parolă care o va converti într-o hash dacă este prezentă.
Deci, în modelul dvs., vom adăuga o definiție pentru acest câmp de parolă nouă.
def parola = new_password) write_attribute (: password_hash, SHA1 :: hexdigest (new_password)) sfarsit def parola "" sfarsit
Păstrăm doar un hash împotriva utilizatorului, așa că nu dăm parolele fără să ne luptăm.
A doua metodă înseamnă că vom returna ceva pentru a folosi formele.
De asemenea, trebuie să ne asigurăm că avem biblioteca de criptare Sha1 încărcată; adăuga cere "sha1"
pentru dumneavoastră application.rb
fișier după linia 40: config.filter_parameters + = [: parola]
.
După ce am schimbat aplicația la nivelul de configurare, reîncărcați-o rapid atingeți tmp / restart.txt
în consola dvs..
Acum, hai să schimbăm formularul implicit pentru a folosi acest loc în loc de password_hash
. Deschis _form.html.erb
în dosarul aplicații / modele / utilizatori:
<%= f.label :password_hash %>
<%= f.text_area :password_hash %>
devine
<%= f.label :password %>
<%= f.text_field :password %>
Vom face un câmp de parolă real când suntem mulțumiți de el.
Acum, încărcați-vă http: // localhost / utilizatori
și au un joc cu adăugarea de utilizatori. Ar trebui să arate cam ca imaginea de mai jos; mare, nu-i așa?!
Stai, ce-i asta? Aceasta suprascrie hash-ul parolei de fiecare dată când editați un utilizator? Să rezolvăm asta.
Deschide user.rb
din nou, și schimbați-o astfel:
write_attribute (: password_hash, SHA1 :: hexdigest (new_password)) dacă new_password.present?
În acest fel, numai când furnizați o parolă, câmpul se actualizează.
Ultima secțiune a fost despre modificarea datelor pe care le obține modelul, dar despre adăugarea mai multor informații pe baza informațiilor deja cunoscute, fără a fi nevoie să le specificați? Să aruncăm o privire la asta cu modelul proiectului. Începeți să aruncați o privire la http: // localhost / projects.
Realizați rapid următoarele modificări.
* app / controllers / projects_controler.rb * line 24
# GET / proiecte / noi #GET /projects/new.json def noi @project = Project.new @users = ["-", nil] + User.all.collect | u | [u.name, u.id] răspunde_pentru a face | format format.html # new.html.rob format.json render: json => @ project sfârșitul final # GET / projects / 1 / edita def edita @project = Project.find (params [: id] "-", nul] + User.all.collect | u | [u.name, u.id] sfârșit
* app / views / projects / _form.html.erb * line 24
<%= f.select :started_by_id, @users %>
* app / views / projects / _form.html.erb * line 24
<%= f.select :completed_by , @users%>
În cadrele MVC, rolurile sunt clar definite. Modelele reprezintă datele. Vizualizările afișează datele. Controlorii obțin date și le transmit în vizualizare.
Acum avem o formă de funcționare completă, dar mă deranjează că trebuie să-l fix incepe la
timp manual. Aș dori să-l setați când îl desemnez started_by
utilizator. Am putea pune-o în controler, totuși, dacă ați auzit vreodată expresia "modele grase, controale slabe", veți ști că acest lucru înseamnă cod rău. Dacă facem acest lucru în model, acesta va funcționa oriunde am stabilit un starter sau completă. Hai să facem asta.
Prima editare app / modele / project.rb
, și adăugați următoarea metodă:
def start_by = (utilizator) dacă (user.present?) user = user.id dacă user.class == Utilizator write_attribute (: started_by_id, user) write_attribute (: started_at, Time.now)
Acest cod asigură că ceva a fost trecut. Apoi, dacă este un utilizator, își recuperează ID-ul și în cele din urmă scrie atât utilizatorul * cât și * timpul în care sa întâmplat - fumează sfânt! Să adăugăm același lucru pentru completat de
camp.
def completed_by = (utilizator) dacă (user.present?) user = user.id dacă user.class == User write_attribute (: completed_by_id, user) write_attribute (: started_at, Time.now)
Acum editați vizualizarea formularului, astfel încât să nu avem acel timp selectat. În app / opinii / proiecte / _form.html.erb
, eliminați liniile 26-29 și 18-21.
Deschide http: // localhost / proiecte
și au un du-te!
Whoooops! Cineva (voi lua căldura din moment ce este codul meu) tăiat și lipiți și ați uitat să schimbați : started_at
la : completed_at
în cea de-a doua metodă atribute în mare măsură (indiciu). Fără biggie, schimba asta și totul merge ... bine?
Deci, în afară de o mică confuzie cut-and-paste, cred că am făcut o treabă destul de bună, dar asta alunecă și codul din jurul ei mă deranjează puțin. De ce? Ei bine, hai să ne gândim:
somethingd_at
și somethingd_by
la proiectul nostru, cum ar fi, să spunem, authorised_at
și autorizat de
>Iată și iată, de-a rândul vine un sef cu părul înalt și o cere, drumroll, autorised_at / by field și un domeniu sugges_at / by! Chiar atunci; hai să punem acele degete tăiate și să le lipim atunci ... sau dacă există o cale mai bună?
Asta e corect! Sfântul Potir; lucrurile înfricoșătoare pe care mamele tale ți-au avertizat. Se pare complicat, dar de fapt poate fi destul de simplu - mai ales ce vom încerca. Vom lua o serie de nume de etape pe care le avem și apoi vom construi automat aceste metode în zbor. Excitat? Grozav.
Desigur, va trebui să adăugăm câmpurile; deci hai să adăugăm o migrare șinele generează migrare suplimentare_workflow_stages
și adăugați acele câmpuri în interiorul noului generat db / migrate / TODAYSTIMESTAMP_additional_workflow_stages.rb
.
clasa AdditionalWorkflowStages < ActiveRecord::Migration def up add_column :projects, :authorised_by_id, :integer add_column :projects, :authorised_at, :timestamp add_column :projects, :suggested_by_id, :integer add_column :projects, :suggested_at, :timestamp end def down remove_column :projects, :authorised_by_id remove_column :projects, :authorised_at remove_column :projects, :suggested_by_id remove_column :projects, :suggested_at end end
Migrează baza de date cu rake db: migrați
, și înlocuiți clasa proiectelor cu:
clasă < ActiveRecord::Base # belongs_to :starter, :class_name =>"User_count == user # write_attribute (: started_by_id, user) # write_attribute (: started_at, Time.now) # # # sfârșitul # sfârșit # # def started_by # read_attribute (: completed_by_id) # end end
Am lăsat-o started_by
acolo pentru a vedea cum a fost codul anterior.
[: starte,: complete,: authorize,: suggeste] .debuna | arg ... ... mai mult ... sfarsit
Frumos și blând - trece prin numele (ish) a metodelor pe care le dorim să le creăm:
[: starte,: complete,: authorize,: suggeste] \ n "" ... ... pentru a asimila mai mult ... sfarsit
Pentru fiecare dintre aceste nume, vom elabora cele două atribute ale modelului pe care le setăm de ex started_by_id
și started_at
și numele asociației, de exemplu,. incepator
[: starte,: complete,: authorize,: suggeste] "nume_clasă =>" Utilizator ",: foreign_key => attr_by end_name =" # arg d_by_id ".to_sym attr_at =" #
Acest lucru pare destul de familiar. Acesta este de fapt un bit de metroprogramare Rails care definește o grămadă de metode.
[: starte,: complete,: authorize,: suggeste] "nume_clasă =>" Utilizator ",: foreign_key => attr_by get_method_name nume_clasă: nume_clasă =>" = "# arg d_by" .to_sym define_method (get_method_name) read_attribute (attr_by) sfârșit
Ok, am ajuns la o programare meta reală care acum calculează numele metodei "get method" - de ex. started_by
, și apoi creează o metodă, la fel cum o facem atunci când scriem def metoda
, dar într-o altă formă.
[: starte,: complete,: authorize,: suggeste] "nume_clasă =>" Utilizator ",: foreign_key => attr_by get_method_name nume_clasă: nume_clasă =>" = "# arg d_by" .to_sym define_method (get_method_name) read_attribute (attr_by) set_method_name = "# arg d_by =" to_sym define_method (set_method_name) dacă user.present? user = user.id dacă user.class == Utilizator write_attribute (attr_by, user) write_attribute (attr_at, Time.now) end end end
Un pic mai complicat acum. Facem același lucru ca înainte, dar acesta este a stabilit numele metodei. Definim această metodă folosind define (nume_metodă) nu | param | Sfârșit
, Decat def method_name = (param)
.
Nu a fost așa de rău, nu-i așa??
Să vedem dacă putem edita proiecte în continuare ca mai înainte. Se pare că putem! Așa că vom adăuga câmpurile suplimentare în formular și, hei, presto!
app / opinii / proiect / _form.html.erb
linia 20
<%= f.label :suggested_by %>
<%= f.select :suggested_by, @users %><%= f.label :authorised_by %>
<%= f.select :authorised_by, @users %>
Și la vizionarea spectacolului ... așa că putem vedea că funcționează.
* app / views-project / show.html.erb * linia 8
Sugestii la: <%= @project.suggested_at %>
Sugerat de: <%= @project.suggested_by_id %>
Autorizat la: <%= @project.authorised_at %>
Autorizat de: <%= @project.authorised_by_id %>
Au o altă joacă cu http: // localhost / proiecte
, și puteți vedea că avem un câștigător! Nu este nevoie să vă temeți dacă cineva solicită un alt pas de flux de lucru; pur și simplu adăugați migrarea pentru baza de date și puneți-o într-o serie de metode ... și se creează. Timp pentru o odihnă? Poate, dar am doar două lucruri pe care să le notez.
Această serie de metode mi se pare foarte utilă. Am putea face mai mult cu ea?
Mai întâi, să facem lista constantă a denumirii metodei astfel încât să putem accesa din afară.
WORKFLOW_METHODS = [: startte,: complete,: authorize,: suggeste] WORKFLOW_METHODS.each nu | arg | ...
Acum, le putem folosi pentru a crea automat forme și vederi. Deschideți _form.html.erb
pentru proiecte, și să o încercăm prin înlocuirea liniilor 19 -37 cu fragmentul de mai jos:
<% Project::WORKFLOW_METHODS.each do |workflow| %><%= f.label "#workflowd_by" %><% end %>
<%= f.select "#workflowd_by", @users %>
Dar app / vizionări-proiect / show.html.erb
este locul unde magia reală este:
<%= notice %>
Nume:: <%= @project.name %>
<% Project::WORKFLOW_METHODS.each do |workflow| at_method = "#workflowd_at" by_method = "#workflowd_by_id" who_method = "#workflowr" %><%= at_method.humanize %>:: <%= @project.send(at_method) %>
<%= who_method.humanize %>:: <%= @project.send(who_method) %>
<%= by_method.humanize %>:: <%= @project.send(by_method) %>
<% end %> <%= link_to 'Edit', edit_project_path(@project) %> | <%= link_to 'Back', projects_path %>
Acest lucru ar trebui să fie destul de clar, deși, dacă nu sunteți familiarizat cu trimite()
, este un alt mod de a apela o metodă. Asa de object.send ( "name_of_method")
este la fel ca object.name_of_method
.
Aproape am terminat, dar am observat două bug-uri: una este formatarea, iar cealaltă este un pic mai gravă.
Primul este că, în timp ce văd un proiect, întreaga metodă arată o ieșire urâtă a obiectului Ruby. Mai degrabă decât să adăugați o metodă până la capăt, ca aceasta
@ Project.send (who_method) .name
Să modificăm Utilizator
să ai a to_s
metodă. Păstrați lucrurile în model dacă puteți și adăugați acest lucru în partea de sus a paginii user.rb
, și faceți același lucru pentru project.rb
de asemenea. Este întotdeauna logic să aibă o reprezentare implicită pentru un model ca șir:
def to_s name end
Se simte un fel de metode de scriere mundane ușor, acum? Nu? Oricum, pe mai multe lucruri serioase.
Când actualizăm un proiect deoarece trimitem toate etapele fluxului de lucru care au fost atribuite anterior, toate timbrele noastre de timp sunt amestecate. Din fericire, pentru că tot codul nostru este într-un singur loc, o singură schimbare le va remedia pe toate.
define_method (set_method_name) do | user | dacă user.present? user = user.id if user.class == Utilizator # ADDITION HERE # Acest lucru asigură că este schimbat de la valoarea stocată înainte de ao seta dacă read_attribute (attr_by) .to_i! = user.to_i write_attribute (attr_by, user) write_attribute (attr_at, Time .now) sfârșitul capătului final
Ce am învățat?
Vă mulțumesc foarte mult pentru citire și spuneți-mi dacă aveți întrebări.