ActiveRecord-modeller i Rails gjør allerede mye tung løft, når det gjelder databasetilgang og modellrelasjoner, men med litt arbeid kan de gjøre flere ting automatisk. La oss finne ut hvordan!
Denne ideen fungerer for alle typer ActiveRecord-prosjekter; Siden Rails er den vanligste, bruker vi det for eksempeleksemplet vårt. Appen vi skal bruke har mange brukere, hver av dem kan utføre en rekke handlinger på prosjekter .
Hvis du aldri har opprettet en Rails-app før, så les denne veiledningen eller pensum først. Ellers brann opp den gamle konsollen og skriv inn skinner nytt eksempel_app
å opprette appen og deretter endre kataloger til den nye appen din med cd example_app
.
Først genererer vi brukeren som vil eie:
skinner generere stillas Brukernavn: tekst e-post: streng password_hash: text
Sannsynligvis, i et ekte verdensprosjekt, ville vi ha noen flere felt, men dette vil gjøre for nå. La oss neste generere vår prosjektmodell:
skinner generere stillas Prosjektnavn: tekst startet_at: datetime startet_by_id: heltall completed_at: datetime completed_by_id: heltall
Vi redigerer deretter den genererte project.rb
fil for å beskrive forholdet mellom brukere og prosjekter:
klasseprosjekt < ActiveRecord::Base belongs_to :starter, :class_name =>"User",: foreign_key => "started_by_id" belongs_to: completer,: class_name => "Bruker",: foreign_key => "completed_by_id" slutten
og det omvendte forholdet i user.rb
:
klassen bruker < ActiveRecord::Base has_many :started_projects, :foreign_key =>"started_by_id" har_many: completed_projects,: foreign_key => "completed_by_id" slutten
Deretter kjør en rask rake db: migrere
, og vi er klare til å begynne å bli intelligente med disse modellene. Hvis bare å få forhold til modeller, var det like lett i den virkelige verden! Nå, hvis du noen gang har brukt Rails-rammeverket før, har du sikkert ikke lært noe ... ennå!
Det første vi skal gjøre er å bruke noen auto genererende felt. Du har lagt merke til at når vi opprettet modellen, opprettet vi en passord hash og ikke et passordfelt. Vi skal lage en fauxattributt for et passord som vil konvertere det til en hash hvis det er til stede.
Så, i modellen din, legger vi til en definisjon for dette nye passordfeltet.
def passord = new_password) write_attribute (: password_hash, SHA1 :: hexdigest (new_password)) avslutte def passord
Vi lagrer bare en hash mot brukeren, slik at vi ikke gir ut passordene uten litt kamp.
Den andre metoden betyr at vi returnerer noe for skjemaer som skal brukes.
Vi må også sikre at vi har Sha1-krypteringsbiblioteket lastet; Legg til krever 'sha1'
til din application.rb
fil etter linje 40: config.filter_parameters + = [: passord]
.
Når vi har endret appen på konfigureringsnivået, legger du det på nytt med en rask trykk tmp / restart.txt
i konsollen din.
La oss nå endre standardskjemaet for å bruke dette i stedet for password_hash
. Åpen _form.html.erb
i app / modeller / brukere mappen:
<%= f.label :password_hash %>
<%= f.text_area :password_hash %>
blir
<%= f.label :password %>
<%= f.text_field :password %>
Vi gjør det til et faktisk passordfelt når vi er fornøyd med det.
Nå, last http: // localhost / brukere
og ha et spill med å legge til brukere. Det skal se litt ut som bildet nedenfor; flott, er det ikke!
Vent, hva er det? Det overskriver passord hash hver gang du redigerer en bruker? La oss fikse det.
Åpne opp user.rb
igjen, og endre det slik:
write_attribute (: password_hash, SHA1 :: hexdigest (new_password)) hvis new_password.present?
På denne måten blir feltet bare oppdatert når du oppgir et passord.
Den siste delen handlet om å endre dataene som modellen din får, men hva med å legge til mer informasjon basert på ting som allerede er kjent uten å måtte spesifisere dem? La oss se på det med prosjektmodellen. Begynn med å se på http: // localhost / prosjekter.
Gjør følgende endringer raskt.
* app / controllers / projects_controler.rb * linje 24
# GET / prosjekter / nytt # GET /projects/new.json def new @project = Project.new @users = ["-", null) + User.all.collect | u | [u.name, u.id] reply_to | format | format.html # new.html.erb format.json gjør: json => @ prosjekt slutten # GET / prosjekter / 1 / rediger def edit @project = Project.find (params [: id]) @users = [ "-", null] + User.all.collect | u | [u.name, u.id] slutt
* app / visninger / prosjekter / _form.html.erb * linje 24
<%= f.select :started_by_id, @users %>
* app / visninger / prosjekter / _form.html.erb * linje 24
<%= f.select :completed_by , @users%>
I MVC-rammene er rollene klart definert. Modeller representerer dataene. Visninger viser dataene. Kontrollører får data og sender dem til visningen.
Vi har nå en fullstendig fungerende form, men det bugser meg at jeg må sette start ved
tid manuelt. Jeg vil gjerne ha det satt når jeg tildeler en started_by
bruker. Vi kunne sette den i kontrolleren, men hvis du noen gang har hørt frasen "fete modeller, tynne kontrollere", vet du at dette gir dårlig kode. Hvis vi gjør dette i modellen, vil det fungere hvor som helst vi setter en startpakke eller komplett. La oss gjøre det.
Først redigere app / modeller / project.rb
, og legg til følgende metode:
def start_by = (bruker) hvis (user.present?) user = user.id hvis user.class == Bruker write_attribute (: startet_by_id, bruker) write_attribute (: startet_at, Time.now) slutten
Denne koden sikrer at noe faktisk har blitt bestått. Så, hvis det er en bruker, henter den sin ID og til slutt skriver både brukeren * og * tiden det skjedde - hellige røyker! La oss legge til det samme for Fullført av
felt.
def completed_by = (bruker) hvis (user.present?) user = user.id hvis user.class == Bruker write_attribute (: completed_by_id, bruker) write_attribute (: startet_at, Time.now) slutten
Rediger nå skjemavisningen slik at vi ikke har den aktuelle tiden. I app / visninger / prosjekter / _form.html.erb
, fjern linjene 26-29 og 18-21.
Åpne opp http: // localhost / prosjekter
og ta en tur!
Whoooops! Noen (jeg tar varmen siden det er koden min) kutt og lim, og glemte å endre : started_at
til : completed_at
i den andre stort sett identiske (hint) attributtmetoden. Ikke biggie, endre det og alt er gå ... riktig?
Så bortsett fra en liten kutte-og-lim-forvirring, tror jeg vi gjorde ganske god jobb, men det glir opp og koden rundt det plager meg litt. Hvorfor? Vel, la oss få en tanke:
somethingd_at
og somethingd_by
til vårt prosjekt, som, si, authorised_at
og autorisert av
>Se og se, langs kommer en kjekk haired sjef og ber om, drumroll, authorised_at / by field og en suggested_at / by felt! Greit da; la oss få de klippe og lim inn fingrene klar da ... eller er det en bedre måte?
Det er riktig! Den hellige gral; de skummelene dine mødre advarte deg om. Det virker komplisert, men det kan faktisk være ganske enkelt - spesielt hva vi skal forsøke. Vi skal ta en rekke navn på stadier vi har, og deretter bygge disse metodene automatisk på fly. Spent? Flott.
Selvfølgelig må vi legge til feltene; så la oss legge til en migrering skinner genererer migrasjon additional_workflow_stages
og legg til de feltene i den nylig genererte db / migrerer / TODAYSTIMESTAMP_additional_workflow_stages.rb
.
klasse 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
Overfør databasen din med rake db: migrere
, og erstatt prosjektklassen med:
klasseprosjekt < ActiveRecord::Base # belongs_to :starter, :class_name =>"User" # def started_by = (bruker) # if (user.present?) # User = user.id hvis user.class == Bruker # write_attribute (: startet_by_id, bruker) # write_attribute (: startet_tid, Time.now) # slutt # slutt # # def startet av # read_attribute (: completed_by_id) # slutten
Jeg har forlatt started_by
der inne, så du kan se hvordan koden var før.
[: start,: complete,: authorize,: suggeste] .each do | arg | ... MER ... slutten
Fint og forsiktig - går gjennom navnene (ish) av metodene vi ønsker å skape:
[: start,: complete,: authorize,: suggeste] .each do | arg | attr_by = "# arg d_by_id" .to_sym attr_at = "# arg d_at" .to_sym object_method_name = "# arg r" .to_sym ... MER ... ende
For hvert av disse navnene trener vi ut de to modellattributtene vi setter inn f.eks started_by_id
og started_at
og navnet på foreningen f.eks. starter
[: start,: complete,: authorize,: suggeste] .each do | arg | attr_by = "# arg d_by_id" .to_sym attr_at = "# arg d_at" .to_sym object_method_name = "# arg r" .to_sym belongs_to object_method_name,: class_name => "Bruker",: foreign_key => attr_by end
Dette virker ganske kjent. Dette er faktisk en Rails bit av metaprogrammering som allerede definerer en rekke metoder.
[: start,: complete,: authorize,: suggeste] .each do | arg | attr_by = "# arg d_by_id" .to_sym attr_at = "# arg d_at" .to_sym object_method_name = "# arg r" .to_sym belongs_to object_method_name,: class_name => "Bruker",: foreign_key => attr_by get_method_name = "# arg d_by" .to_sym define_method (get_method_name) read_attribute (attr_by) avslutte
Ok, vi kommer til noen ekte metaprogrammering nå som beregner navnet på "få metode" - f.eks. started_by
, og skaper da en metode, akkurat som vi gjør når vi skriver def metode
, men i en annen form.
[: start,: complete,: authorize,: suggeste] .each do | arg | attr_by = "# arg d_by_id" .to_sym attr_at = "# arg d_at" .to_sym object_method_name = "# arg r" .to_sym belongs_to object_method_name,: class_name => "Bruker",: foreign_key => attr_by get_method_name = "# 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) gjør | bruker | hvis user.present? user = user.id hvis user.class == Bruker write_attribute (attr_by, bruker) write_attribute (attr_at, Time.now) slutten slutten
Litt mer komplisert nå. Vi gjør det samme som før, men dette er sett Metodenavn. Vi definerer den metoden, ved hjelp av define (method_name) do | param | slutt
, heller enn def method_name = (param)
.
Det var ikke så ille, var det?
La oss se om vi fortsatt kan redigere prosjekter som tidligere. Det viser seg at vi kan! Så vi legger til de ekstra feltene i skjemaet, og hei, presto!
app / visninger / prosjekt / _form.html.erb
linje 20
<%= f.label :suggested_by %>
<%= f.select :suggested_by, @users %><%= f.label :authorised_by %>
<%= f.select :authorised_by, @users %>
Og til showvisningen ... så kan vi se det som fungerer.
* app / visninger-prosjekt / show.html.erb * linje 8
Foreslått på: <%= @project.suggested_at %>
Foreslått av: <%= @project.suggested_by_id %>
Godkjent på: <%= @project.authorised_at %>
Autorisert av: <%= @project.authorised_by_id %>
Ha en annen lek med http: // localhost / prosjekter
, og du kan se vi har en vinner! Du trenger ikke å frykte hvis noen ber om et annet arbeidsflytstrinn; bare legg til migrering for databasen, og sett den i en rekke metoder ... og det blir opprettet. Tid til hvile? Kanskje, men jeg har bare to ting å merke seg.
Det utvalg av metoder virker ganske nyttig for meg. Kan vi gjøre mer med det?
Først må vi gjøre listen over metodenavn konstant, slik at vi kan få tilgang til det fra utsiden.
WORKFLOW_METHODS = [: start,: fullfør,: autoriser,: suggeste] WORKFLOW_METHODS.each do | arg | ...
Nå kan vi bruke dem til å automatisk lage skjema og visninger. Åpne opp _form.html.erb
for prosjekter, og la oss prøve det ved å erstatte linjene 19 -37 med koden nedenfor:
<% Project::WORKFLOW_METHODS.each do |workflow| %><%= f.label "#workflowd_by" %><% end %>
<%= f.select "#workflowd_by", @users %>
Men app / views-prosjekt / show.html.erb
er der den virkelige magien er:
<%= notice %>
Navn:: <%= @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 %>
Dette bør være ganske klart, selv om du ikke er kjent med sende()
, det er en annen måte å ringe en metode på. Så object.send ( "name_of_method")
er det samme som object.name_of_method
.
Vi er nesten ferdige, men jeg har lagt merke til to feil: den ene er formatering, og den andre er litt mer alvorlig.
Den første er at mens jeg ser på et prosjekt, viser hele metoden en stygg Ruby-objektutgang. Snarere enn å legge til en metode til slutt, slik som dette
@ Project.send (who_method) .name
La oss endre Bruker
å ha en to_s
metode. Hold ting i modellen hvis du kan, og legg dette til toppen av user.rb
, og gjør det samme for project.rb
også. Det er alltid fornuftig å ha en standardrepresentasjon for en modell som en streng:
def to_s navn slutt
Føler litt dagligdagse skrivemetoder på den enkle måten nå, eh? Nei? Uansett, videre til mer alvorlige ting.
Når vi oppdaterer et prosjekt fordi vi sender alle arbeidsflytstadiene som tidligere er tildelt, blir alle tidsstemplene våre blandet sammen. Heldigvis, fordi all vår kode er på ett sted, vil en enkelt endring fikse dem alle.
define_method (set_method_name) do | user | hvis user.present? user = user.id if user.class == Bruker # ADDITION HERE # Dette sikrer at den er endret fra den lagrede verdien før du setter den inn hvis read_attribute (attr_by) .to_i! = user.to_i write_attribute (attr_by, bruker) write_attribute (attr_at, Time .now) slutten slutten
Hva har vi lært?
Takk så mye for å lese, og gi meg beskjed hvis du har noen spørsmål.