Presentation is loading. Please wait.

Presentation is loading. Please wait.

Associations: Mechanics (ESaaS §5.3) © 2013 Armando Fox & David Patterson, all rights reserved.

Similar presentations


Presentation on theme: "Associations: Mechanics (ESaaS §5.3) © 2013 Armando Fox & David Patterson, all rights reserved."— Presentation transcript:

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


Download ppt "Associations: Mechanics (ESaaS §5.3) © 2013 Armando Fox & David Patterson, all rights reserved."

Similar presentations


Ads by Google