Association Join Models December 13, 2007 08:34 over 4 years ago
Midt i et foredrag kom jeg i tvivl om hvilken form for join model der er den bedste i Rails. Svaret er jo nok afhængig af løsningen og den enkelts krav til information på selve relationen. Hidtil har det været god metodik at man skal holde sine join tabeller rene. Det betyder altså at der ikke bør være anden information end primær nøgler der relatere de to mange til mange tableller.
I Rails findes der to løsninger på mange til mange relationen. Den ene er en simple (habtm) implementation som kun kræver en ekstra database join tabel. Den anden løsning(through association) kræver også en ekstra tabel men har et model objekt og ekstra attributter på relationen.
Simple Associations(HABTM)
Tabel Join
create_table :posts_tags, :id => false do |t|
t.column :post_id, :integer
t.column :tag_id, :integer
end
Model
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
Her har vi altså to model objekter som har en many-to-many relation mellem posts og tags. Det er en konvention i Rails at join tabellen bliver kaldt posts_tags. Man kan ikke lægge ekstra information på relationen og tabellen kan ikke indenholde primær nøgle. Denne model er at fortrække hvis løsningen ikke kræver mere information omkring selve relationen.
@post.tags << Tag.find ”Ruby” << Tag.find ”Java”
Denne løsning er enkelt at bruge og sætte op. Bemærk at der ikke er en model for relationen.
Rich Associations(through association)
I denne variation kræver AC en primær nøgle på relationen samt et model objekt. I dette tilfælde kan det betale sig at finde på et godt navn til relationen. I den simple model skulle vi blot navngive tabellen udfra de to tabeller som skal relateres men nu skal vi bruge et godt sigene navn til den model som indkapsler relationen.
Tabel Join
create_table :tagszations do |t|
t.column :post_id, :integer
t.column :tag_id, :integer
t.column :created_at, :datetime
end
Model
class Tagszation < ActiveRecord::Base belongs_to :post belongs_to :tag end class Tag < ActiveRecord::Base has_many :tagszation has_many :posts, :through => :tagszation end class Post < ActiveRecord::Base has_many :tagszation has_many :tags, :through => :tagszation, :select => "DISTINCT tags.*" end
Denne metode giver mere fleksibilitet på selve relationen og er nemt at sætte op for den simple løsning men kan snyde lidt når man går fra HABTM til through association.
post = Post.find(1) post.tags.size # => 4 ruby = Tag.find(2) tagz = Tagszation.new(:post => post, :tag => ruby) post.save post.tags.size # => 4 post.tags(true).size # => 5
Ups. Push metoden virker ikke for has_many :through relation. Hvorfor? Rails API siger at metoden tilføjer en samling attributter til objektet i relationen. Denne relation virker som et array af relateret objekter men indeholder ekstra hjælpe metoder som gør livet værd at leve.
Faktisk er dette ikke et array men en instans af AssociationCollection som igen er en supklasse af AssociationProxy. Dette er klasser som følger Proxy pattern. Klassen fungerer som front objekt for at give ekstra adfærd til det bagvedliggende objekt. AssociationProxy uddelegere til et enkelt model objekt og for has_one eller belongs_to. AssociationCollection delegere et Array af model objekter.
De delegerede metoder er med i class så hvis du spore objektet hvilken type vil det sige array selvom det ikke er tilfældet. Funny
Her er en sammenlinning af de to typer i tabelform. Tak til has_many :through
| Association | has_and_belongs_to_many | has_many :through |
| AKA | habtm | through association |
| Structure | Join Table | Join Model |
| Primary Key | no | yes |
| Rich Association | no | yes |
| Proxy Collection | yes | no |
| Distinct Selection | yes | no yes |
| Self-Referential | yes | yes |
| Eager Loading | yes | yes |
| Polymorphism | no | yes |
| N-way Joins | no | yes |
Structure
has_and_belongs_to_many benytter en simple join table hvor hver række blot er to fremme nøgler. Der findes ikke et model objekt i rails og man tilgår ikke denne tabel direkte
Primary Key
Join tables har ikke en primærnøgle. Nogle forsøger sig med at generer en nøgle ud fra et par fremmed nøgler men det er noget crap.
Join models derimod har en primærnøgle ligesom alle modeller har et telefonnummer. Det betyder at du kan manipolere tabellen direkte.
Rich Association
Hvis du behøver information på relations tabellen må du bruge en join model.
Proxy Collection
En fordel ved habtm er at den skabte relation er en såkaldt proxy collection. Det betyder mulighed for oprette elementer i join tabellen med relationens << (push) metode på samme måde som med has_many relationer. Fordi join model har ekstra attributter på relationen bliver det mere kompliceret at oprette dem automatisk.
Distinct Selection
Nogle gange kan en join table, eller model have flere referencer mellem de samme rækker. Hvis man ikke vil ha alle men blot en kan man bruge :unique. Dette ville svare til at bruge DISTINCT i sql. Forsørgelsen returner uden duplikater. Forskellen er om duplikater skal fjernes i databasen eller i Ruby.
Self-Referential
Begge modeller kan referere til sig selv. Fx kan man i habtm bruge :foreign_key og :association_foreign_key.
Eager Loading
Begge modeller supporter eager loading af relaterede objekter med :include.
Polymorphism
Join models og through relationer kan arbejde med polymorfe model typer.
N-way Joins
En habtm relation kan kun joine to modeller men through modellen kan joine ligeså mange du vil. Men pas på, det bliver let kompliceret.
By Frank Vilhelmsen - 2 tags: database rails - Add comment