Presentation is loading. Please wait.

Presentation is loading. Please wait.

Associations: Mechanics (ESaaS §5.3)

Similar presentations


Presentation on theme: "Associations: Mechanics (ESaaS §5.3)"— 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:
Add has_many to owning model and belongs_to to owned model Create migration to add foreign key to owned side that references owning side Apply migration rake db:test:prepare to regenerate test database schema

4 END

5 We can say movie.reviews, but review.movie won’t work ☐
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? We can say movie.reviews, but review.movie won’t work We will get a database error when trying to save a Review 1. True. The has_many will set up the getter in Movie, but there will be no getter in Review. 2. True. The foreign key won’t be set. 3. True. We won’t have the foreign key, and the movie name is not unique. 4. True We will have no way of determining which movie a given review is associated with All of the above

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
id reviews moviegoer_id movie_id number movies id ... moviegoer: has_many :reviews movie: has_many :reviews review: belongs_to :moviegoer belongs_to :movie How to get all movies reviewed by some moviegoer?

10 has_many :through moviegoers id reviews moviegoer_id movie_id ... movies id ... 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 Why is there no review_id field in the movie table?

11 Through Now you can do: My potato scores for R-rated movies
@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' } Example: show => vouchers

12 has_many :through moviegoers id reviews moviegoer_id movie_id ...
movies id ... @user.movies SELECT * FROM movies JOIN moviegoers ON reviews.moviegoer_id = moviegoers.id JOIN movies ON reviews.movie_id = movies.id Why is there no review_id field in the movie table?

13 END

14 Which of these, if any, is NOT a correct way of saving a new association, given m is an existing movie: Review.create!(:movie_id=>m.id, :potatoes=>5) r = m.reviews.build(:potatoes => 5) r.save! m.reviews << Review.new(:potatoes=>5) m.save! 1. Works. But definitely not desirable. 2. Works. The save is needed. 3. Works. Adds new review to list of m’s reviews and saves it. The save is redundant. 4. All work. All will work

15 END

16 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') movies id name ...etc. genres id description genres_movies genre_id movie_id

17 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 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

19 END

20 Faculty has-many appointments, Student has-many appointments ☐
We want to model students having appointments with faculty members. Our model would include which relationships: Faculty has-many appointments, Student has-many appointments Faculty HABTM Students, Students HABTM Faculty Faculty belongs-to appointment, Student belongs-to appointment 1. True. Similar to a movie has-many reviews and a moviegoer has-many reviews. 2. False. We want appointments as a separate object. 3. False. The belongs-to goes the other way. 4. False. Faculty has-many students through appointments. Faculty has-many appointments, through Students

21 END

22 RESTful Routes for Associations (ESaaS §5.5)
© 2013 Armando Fox & David Patterson, all rights reserved

23 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

24 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

25 Nested RESTful Routes available as params[:movie_id]
Why do you need movie_id when creating a review? => nested RESTful routes manage it for you by making it part of URL. but don't HAVE to do it this way. available as params[:movie_id] available as params[:id]

26 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]) flash[:notice] = 'Review successfully created.' else render :action => 'new' end to what action does movie_reviews_path point? why is it OK to render :action => 'new' on failure?

27 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 end Another possibility: do it in a before-filter to what action does movie_reviews_path point? why is it OK to render :action => 'new' on failure? See for a discussion of a ||= b. It is not exactly the same as a = a || b. If a is not nil/false, no action is taken, otherwise a = b. So it acts as a test and set operator – is nil (no review has been created), then one is created 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

28 Views %h1 Edit = form_tag :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', Remember, these are for convenience. Invariant is: review when created or edited must be associated with a movie.

29 END

30 If we also have moviegoer has_many reviews,
can we use moviegoer_review_path() as a helper? Yes, it should work as-is because of convention over configuration Yes, but we must declare reviews as a nested resource of moviegoers in routes.rb No, because there can be only one RESTful route to any particular resource 1. False. 2. True. See Figure /moviegoers/:moviegoer_id/reviews 3. False. You can have several routes to the same resource. 4. False. This is not a through association, as reviews belong-to moviegoers. No, because having more than one through-association involving Reviews would lead to ambiguity

31 END

32 DRYing Out Queries with Reusable Scopes (ESaaS §5.6)
© 2013 Armando Fox & David Patterson, all rights reserved

33 “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?

34 Scopes Can Be “Stacked”
Movie.for_kids.with_good_reviews(3) Movie.with_many_fans.recently_reviewed Scopes are evaluated lazily!

35 END

36 Where do database queries happen?
1 # in controller: 2 def good_movies_for_kids 3 @m = Movie.for_kids.with_good_reviews(3) 4 end 5 # in view: 6 do |movie| 7 %p= pretty_print(movie) Where do database queries happen? Line 3 only Lines 6-7 only The ActiveRecord database queries are lazy. The only time that the access is required is when the value must be known, which is in lines 6 (determine whether there are any such movies and get the next one) and line 7 (print it out). False True True. If no movies are returned, then line 7 is never executed. Line 3 AND lines 6-7 Depends on return value of for_kids

37 END

38 Associations Wrap-Up (ESaaS §5.7-5.9)
© 2013 Armando Fox & David Patterson, all rights reserved

39 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

40 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 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)

42 Testing Referential Integrity
it "should nuke reviews when movie deleted" = end lambda { Review.find(review_id) }.should raise_error(ActiveRecord::RecordNotFound)

43 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: The Rails 4 Way, Chapter 9

44 END

45 to have to write the association methods yourself
If using the DataMapper pattern and you want to do one-to-many associations, you can expect: to have to write the association methods yourself Better scalability Worse scalability 1. True. The mapper methods provide the associations. 2. True. You can use noSQL databases. 3. True. If the associations are well-mapped to relational databases, then it might be very fast, especially with custom RDBMS hardware. 4. True. All of the above are possible

46 END


Download ppt "Associations: Mechanics (ESaaS §5.3)"

Similar presentations


Ads by Google