About
fv_2007
Agile innovative developer with deep insight into lots of platforms, technologies and protocols. Absolute “early adopter” in Web 2.0 technologies and more. Large professional network and eagerly talking about architecture, strategy, design patterns, restful ressources, object-oriented thinking and modeling languages such as PML. Special interest in programminglanguages constructs, knowledge on languages like Smalltalk, Erlang, Java, Clojure, Scala, Ruby... read more
Comments
Language

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