You just have to know that when you save a model, the lifecycle accepts that validations are running in the certain point. These are a bunch of functions that I can provide to control what happens to that point, and validation happens at a specific point in the cycle, so I have controlled before validations happen, and after the validations have run. This is a form of Aspect-oriented programming. These functions will be injected before and after each model save, or model.valid?. Make Checks DRY Goal: enforce that movie names must be less than 40 characters – Call a “check” function from every place in app where a Movie might get created or edited? That’s not DRY! Everywhere you want to save do as follows: check consistency log save check consistency # you might miss to call these. It is not DRY • How do we DRY out cross-cutting concerns: Logically centralized, but may appear multiple places in implementation? Aspect • Advice is a specific piece of code that implements a cross-cutting concern • Pointcuts are the places you want to “inject” advice at runtime • Advice+Pointcut = Aspect" • Goal: DRY out your code • Specify declaratively in model class Validation is advice in AOP sense – many places in app where a model could be modified/updated – including indirectly via associations! • So where are the pointcuts? (as shown above in the picture) even though there isn't necessarily something in the source code that says, " Call this validation function," you essentially want the ability to annotate the code in different places saying, " These are the places that I would like to have it called." So, we would want to check that the changes are valid, so the question is, " Where are the pointcuts here?" This brings to the idea of model lifecycle callbacks. This is what you can think of as rails very limited implementation of a certain type of Aspect- Oriented Programming. What actually happens when you use the active record calls like create or update attributes is that before they actually do their thing and talk to the database, there's a number of hooks that are provided where you can essentially cause other things to happen. They're called callbacks ... lifecycle callbacks. But the idea is that this is a limited version of Aspect- Oriented Program because we don't get to decide where the pointcuts are, but the pointcuts have been determined to be, " This is where validations are run, and these are other specific places where you can add your functionality," so when you call valid explicitly, validation gets triggered; you could do that. that we think of Validation as kind of Aspect- Oriented is because nowhere in the code in a typical active record model are you going to find something that says, " Call this everything that does validation." It's implicit that it's going to happen along these two paths. anytime that you're going to modify model information in the database if you're doing it via one of the active record calls, they all have to funnel down one of these two paths (create/update), and both paths include the ability to call validation. If you think of goto as bad, think of this as come-from :) of that, if Go To is bad, think of this as Come From. Like you find yourself in a validation method, and how did you get here because there isn't a call to that method that's obvious anywhere. You just have to know that when you save a model, the lifecycle accepts that validations are running in the certain point. So far we saw checking constraints on a model; what about constraints on call and controller actions? The classic example of this is in most interesting applications, there are certain actions you can only do after you have logged in. There are other actions that anybody can do whether they're logged in or not, so what you really like to do is rather than putting a specific check in front of every action that requires you to be logged in, you would like declare that we say, " The following list of actions should actually do some check before calling the actual controller method to make sure ..." Let's say that the user's logged in. want to set a filter for any action (any controller action) that requires you to be logged- in,
before filter, Means that this is something that's going to happen before the controller action is called. Which controller actions does it apply to? Well, we're saying it applies to everything except whatever action is matched by this route. Note: Notice where I'm putting this filter, I'm putting it in Application Controller which is the parents in the class hierarchy sense of all of your applications' other controllers, so when you put a filter in Application Controller, it will apply to all the actions across all the controllers. -You can also create more controller specific filter e.g. they only apply to actions on Movies. inside the filter I am redirecting to login. This means that the regular Controller Action that was going to run when this before filter was triggered doesn't get to run because I have terminated things with the redirect. Caution: Controlled filters can actually change the flow of execution Summary Filters declared in a controller also apply to its subclasses – Corollary: filters in ApplicationController apply to all controllers • A filter can change the flow of execution! – by calling redirect_to or render – You should add something to the flash to explain to the user what happened, otherwise will manifest as a “silent failure”! I've helped debug a lot of cases like this, where if somebody would say, " I'm clearly ... My route is correct. My route is supposed to take me into this controller action. I'm putting a debugger break point in the controller action, but when I run, I'm not hitting the debugger breakpoint. What happened?" Well it's not that you're not hitting the debugger breakpoint; it's that you're never getting to the controller action because you've got a filter earlier on that's actually stopping the show somewhere else. Higher-order FunctionsFunctions that do one or more of the following:
Single Sign-on and Third-Party AuthenticationSo, as a general rule, even though you want sites to possibly be able to share information with each other about you, for your benefit, you don't want to do this by revealing your login information to different sites.The idea is pretty neat. You actually model a session as its own entity. So if you think of a session beginning at the time that I log in and ending either with me logging out or with it timing out, like my abandoning. Then we can think of a session controller, whose job it is to create and delete the session and that's where we can centralize the logic that's going to negotiate with the remote service and try to act on my behalf. class Movie < ActiveRecord::Base has_many :reviews end class Review < ActiveRecord::Base belongs_to :movie end • Now you can say: @movie.reviews # Enumerable of reviews • And also go the other way:
@review.movie # what movie is reviewed? • You can add new reviews for a movie: Association proxy methods! @movie = Movie.where("title='Fargo'") @movie.reviews.build(:potatoes => 5) OR @movie.reviews.create(:newspaper=>'Chron', ...) # how are these different from just new() & create()? OR @movie.reviews << @new_review # instantly updates @new_review's FK in database! @movie.reviews.find(:first,:conditions => '...') Summary To add a one to many association: 1. Add has_many to owning model and belongs_to to owned model2. Create migration to add foreign key to owned site that references owning side 3. Apply migration 4. rake db:test:prepare to regenerate test database schemaSuppose we have setup the foreign key movie.reviews , but review.movie won't workbecause we have already added the foreign key so the datasbe does it's job. These macros add abstraction for rails to fetch foreign keys so that we don't have to. so if we add it to one side we can use it but if we forget it to the other side that side won't work. moviegoer: has_many :reviews movie: has_many :reviews review: belongs_to :moviegoer
belongs_to :movie # belong to deals with foreign key management, and can belong to a lot of other model classes All the movies reviewed by some person moviegoer: has_many :reviews movie: has_many :reviews reviews: belongs_to :moviegoer belongs_to :movie
• 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' } If we only have movies and reviews in our schema, Which of these, if any, is NOT a correct way of saving a new association, given m is an existing movie:
does m.save also save new reviews associated with it? yes. it might be counter intuitive as the movie table does not change but merely one review has been added but, we can save on movie itself. If you call save on an object that owns other objects it calls save on all objects it owns/has created. how about m.destroy? yes all reviews associated with that movie will be destroyed also difference between option 2 and 3: in 3 all other pending changes that are for an object that belongs to the movie also get saved, but in 2 only the review gets saved. If we had moviegoers also, we would have to use one of the above on movie or moviegoer and set the other foreignkey explicitly.
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 |