Download presentation
Presentation is loading. Please wait.
Published byTyler Baker Modified over 9 years ago
1
Associations: Mechanics (ESaaS §5.3) © 2013 Armando Fox & David Patterson, all rights reserved
2
How Does It Work? Models must have attribute for foreign key of owning object –e.g., movie_id in reviews table ActiveRecord manages this field in both database & in-memory AR object Don’t manage it yourself! –Harder to read –May break if database schema doesn’t follow Rails conventions
3
Rails Cookery #4 To add a one-to-many association: 1.Add has_many to owning model and belongs_to to owned model 2.Create migration to add foreign key to owned side that references owning side 3.Apply migration 4.rake db:test:prepare to regenerate test database schema
4
4 END
5
We will have no way of determining which movie a given review is associated with All of the above We can say movie.reviews, but review.movie won’t work ☐ ☐ ☐ ☐ 5 Suppose we have setup the foreign key movie_id in reviews table. If we then add has_many :reviews to Movie, but forget to put belongs_to :movie in Review, what happens?
6
6 END
7
Through-Associations (ESaaS §5.4) © 2013 Armando Fox & David Patterson, all rights reserved
8
Many-to-Many Associations Scenario: Moviegoers rate Movies –a moviegoer can have many reviews –but a movie can also have many reviews Why can’t we use has_many & belongs_to? Solution: create a new AR model to model the multiple association
9
Many-to-Many moviegoer: has_many :reviews movie: has_many :reviews review: belongs_to :moviegoer belongs_to :movie How to get all movies reviewed by some moviegoer? reviews moviegoer_id movie_id number movies id... moviegoer id
10
has_many :through moviegoer: has_many :reviews has_many :movies, :through => :reviews movie: has_many :reviews has_many :moviegoers, :through => :reviews reviews: belongs_to :moviegoer belongs_to :movie reviews moviegoer_id movie_id... movies id... moviegoers id
11
Through Now you can do: @user.movies # movies rated by user @movie.users # users who rated this movie My potato scores for R-rated movies @user.reviews.select { |r| r.movie.rating == 'R' }
12
has_many :through @user.movies SELECT * FROM movies JOIN moviegoers ON reviews.moviegoer_id = moviegoers.id JOIN movies ON reviews.movie_id = movies.id reviews moviegoer_id movie_id... movies id... moviegoers id
13
13 END
14
m.reviews 5) m.save! All will work Review.create!(:movie_id=>m.id, :potatoes=>5) ☐ ☐ ☐ ☐ 14 Which of these, if any, is NOT a correct way of saving a new association, given m is an existing movie:
15
15 END
16
Has and Belongs to Many (ESaaS §5.4) © 2013 Armando Fox & David Patterson, all rights reserved
17
Shortcut: Has and Belongs to Many (habtm) join tables express a relationship between existing model tables using FKs Join table has no primary key because there’s no object being represented! movie has_and_belongs_to_many :genres genre has_and_belongs_to_many :movies @movie.genres << Genre.find_by_name('scifi') genres id description movies id name...etc. genres_movies genre_id movie_id http://pastebin.com/tTVGtNLx
18
Rules of Thumb If you can conceive of things as different real-world objects, they should probably be distinct models linked through an association If you don’t need to represent any other aspect of a M-M relationship, use habtm Otherwise, use has_many :through 18
19
19 HABTM Naming Conventions M-M relationship naming convention: if a Bar has_and_belongs_to_many :foos then a Foo has_and_belongs_to_many :bars and the database table is the plural AR names in alphabetical order bars_foos
20
20 END
21
Faculty belongs-to appointment, Student belongs-to appointment Faculty has-many appointments, through Students Faculty has-many appointments, Student has-many appointments ☐ ☐ ☐ ☐ 21 We want to model students having appointments with faculty members. Our model would include which relationships:
22
22 END
23
RESTful Routes for Associations (ESaaS §5.5) © 2013 Armando Fox & David Patterson, all rights reserved
24
Creating/Updating Through- Associations When creating a new review, how to keep track of the movie and moviegoer with whom it will be associated? –Need this info at creation time –But route helpers like new_movie_path (provided by resources :movies in routes file) only “carry around” the ID of the model itself
25
Nested RESTful Routes in config/routes.rb: resources :movies becomes resources :movies do resources :reviews end Nested Route: access reviews by going ”through” a movie
26
Nested RESTful Routes available as params[:movie_id] available as params[:id]
27
ReviewsController#create # POST /movies/1/reviews # POST /movies/1/reviews.xml def create # movie_id because of nested route @movie = Movie.find(params[:movie_id]) # build sets the movie_id foreign key automatically @review = @movie.reviews.build(params[:review]) if @review.save flash[:notice] = 'Review successfully created.' redirect_to(movie_reviews_path(@movie)) else render :action => 'new' end
28
ReviewsController#new # GET /movies/1/reviews/new def new # movie_id because of nested route @movie = Movie.find(params[:movie_id]) # new sets movie_id foreign key automatically @review ||= @movie.reviews.new @review = @review || @movie.reviews.new end Another possibility: do it in a before-filter before_filter :lookup_movie def lookup_movie @movie = Movie.find_by_id(params[:movie_id]) || redirect_to movies_path, :flash => {:alert => "movie_id not in params"} end
29
Views %h1 Edit = form_tag movie_review_path(@movie,@review), :method => :put do |f|...Will f create form fields for a Movie or a Review? = f.submit "Update Info" = link_to 'All reviews for this movie', movie_reviews_path(@movie) Remember, these are for convenience. Invariant is: review when created or edited must be associated with a movie.
30
30 END
31
No, because there can be only one RESTful route to any particular resource No, because having more than one through-association involving Reviews would lead to ambiguity Yes, it should work as-is because of convention over configuration ☐ ☐ ☐ ☐ 31 If we also have moviegoer has_many reviews, can we use moviegoer_review_path() as a helper?
32
32 END
33
DRYing Out Queries with Reusable Scopes (ESaaS §5.6) © 2013 Armando Fox & David Patterson, all rights reserved
34
“Customizing” Associations with Declarative Scopes Movies appropriate for kids? Movies with at least N reviews? Movies with at least average review of N? Movies recently reviewed? Combinations of these?
35
Scopes Can Be “Stacked” Movie.for_kids.with_good_reviews(3) Movie.with_many_fans.recently_reviewed Scopes are evaluated lazily! http://pastebin.com/BW40LAHX
36
36 END
37
Line 3 AND lines 6-7 Depends on return value of for_kids Line 3 only ☐ ☐ ☐ ☐ 37 1 # in controller: 2 def good_movies_for_kids 3 @m = Movie.for_kids.with_good_reviews(3) 4 end 5 # in view: 6 - @m.each do |movie| 7 %p= pretty_print(movie) Where do database queries happen?
38
38 END
39
Associations Wrap-Up (ESaaS §5.7-5.9) © 2013 Armando Fox & David Patterson, all rights reserved
40
Associations Wrap-Up Associations are part of application architecture – provides high-level, reusable association constructs that manipulate RDBMS foreign keys –Mix-ins allow Associations mechanisms to work with any ActiveRecord subclass Proxy methods provide Enumerable-like behaviors –A many-fold association quacks like an Enumerable –Proxy methods are an example of a design pattern Nested routes help you maintain associations RESTfully - but they’re optional, and not magic
41
Elaboration: DataMapper Data Mapper associates separate mapper with each model –Idea: keep mapping independent of particular data store used => works with more types of databases –Used by Google AppEngine –Con: can’t exploit RDBMS features to simplify complex queries & relationships 41
42
Referential Integrity What if we delete a movie with reviews? –movie_id field of those reviews then refers to nonexistent primary key –another reason primary keys are never recycled Various possibilities depending on app... –delete those reviews? has_many :reviews, :dependent => :destroy –make reviews “orphaned”? (no owner) has_many :reviews, :dependent => :nullify Can also use lifecycle callbacks to do other things (e.g., merging)
43
Testing Referential Integrity it "should nuke reviews when movie deleted" do @movie = @movie.create!(...) @review = @movie.reviews.create!(...) review_id = @review.id @movie.destroy end lambda { Review.find(review_id) }.should raise_error(ActiveRecord::RecordNotFound)
44
Advanced Topics Single-Table Inheritance (STI) & Polymorphic Associations Self-referential has_many :through Many declarative options on manipulating associations (like validations) To learn (much) more: –http://guides.rubyonrails.org/association_basics. htmlhttp://guides.rubyonrails.org/association_basics. html –The Rails Way, Chapter 9
45
45 END
46
Worse scalability All of the above are possible to have to write the association methods yourself ☐ ☐ ☐ ☐ 46 If using the DataMapper pattern and you want to do one-to-many associations, you can expect:
47
47 END
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.