Assignment 4: Models, Controllers, and Views

November 14th, 2009 Leave a comment Go to comments

Errata

  1. (14-Nov-2009) The AddTags migration is missing one tag and had the wrong name for another. Without this tweak, clicking on a couple of publications will raise an exception. So, in AddTags, change the lines that add the tags to this:
    %W[ java ruby ruby1.9 rails projects crud models rest activerecord ].each do |tag_name|
    Tag.create!(:name => tag_name, :user => john)
     end
    
  2. (9-Nov-2009) At the request of Ron, the download version 1.0.004 wraps up a number of small changes from these errata, namely: (a) the use of :uniq on Tag.index_publications; (b) a similar addition on Publication.index_tags; (c) removal of .uniq in views/publications/_publication.html.erb; (d) fix for doubled occurrence of the word “for” in views/index_entries/new.html.erb; (e) a check that there’s a current_user before displaying the link to add an index entry in views/publications/show.html.erb, and, in this same template, another check for a current_user before displaying annotations, as well as the use of the “for_user” association extension on Publication.annotations; (f) from Keith, functional tests of the PublicationsController and SessionsController. That should be it.
  3. (8-Nov-2009) Thanks to a mistake on my part, I forgot to constrain the indexed publications for a tag so that the results are unique. Ron found the bug and suggested the fix, which works. In app/models/tag.rb, add “:uniq => true” to the indexed_publications association:
    has_many :indexed_publications,
      :source => :publication,
      :through => :index_entries,
      :uniq => true
    

    “uniq” is described on p. 371 (print), p. 375 (PDF).

  4. (5-Nov-2009) For item 1, the CreateIndexEntries migration: The self.down method is already done for you — disregard the comment in the code that says you need to do something. I accidentally left in the drop_table. :-)
  5. (4-Nov-2009) As I was revising some slides on Rails filtering, a stream of invective emerged from my mouth as I discovered that I had not “turned on” the filtering completely in the code download for assignment 4. This tweak does not affect your ability to get the assignment done, but I recommend it nonetheless. For all controllers except ApplicationController, RequiresAuthenticationController, and SessionController, make sure that the controllers subclass RequiresAuthenticationController. Example:
    class TagsController < RequiresAuthenticationController
    

    This tweak is reflected in e168-assignments4and5-jgn-1.0.003.zip on the downloads page. Again, this goof doesn’t really affect your ability to complete the assignment. The sympton is that if you log out and then go to a page such as /tags/new, you won’t properly be kicked over to authentication.

  6. In app/models/publication.rb, there may be a “for_tag” association extension on on the wrong association. The has_many associations are as follows. This is the only difference between e168-assignments4and5-jgn-1.0.000.zip, and e168-assignments4and5-jgn-1.0.002.zip (001 was missing the update). You can either download e168-assignments4and5-jgn-1.0.002.zip from the downloads page, or put this code below in place of the associations in the Publication model. The reason I wanted you to have this is because I’ll be discussion association extensions Thursday (at Keith’s request) and these additional methods should make it easier to avoid putting “find” code into your views.
      belongs_to :user
      has_many :annotations, :dependent => :destroy do
        # Students: This is an "association extension":
        # See AWDR, p. 372 (print), p. 376 (PDF)
        def for_user(user)
          find(:first, :conditions => ['annotations.user_id = ?', user.id])
        end
      end
      has_many :publication_tags, :dependent => :destroy
      has_many :tags, :through => :publication_tags
      has_many :index_entries, :dependent => :destroy do
        # Another "association extension"; see references above.
        def for_tag(tag)
          find(:all, :conditions => { :tag_id => tag.id })
        end
      end
      has_many :index_tags,
        :source => :tag,
        :through => :index_entries
    

    This is fixed in the freshest download: e168-assignments4and5-jgn-1.0.002.zip on the downloads page.


Both assignments 4 and 5 are due 23-Nov.; automatic extension to 25-Nov. If you complete assignment 4 early, submit it, and we’ll see if we can get comments back to you early.

In this assignment you will implement parts of a full-blown web application: migrations, models, views, and controllers. These components are the foundation of any Rails application. If you can do this well, it should be clear sailing to the end of the course, because most of the other aspects of Rails go deeper or decorate the application with plugin or gem functionality.

You can find a package for the assignment on the downloads page.

The application is essentially an online bibliography/index of publications. (We actually wanted to use the word “resource,” but that has a technical meaning in Rails, so we’re going with something a bit more ordinary.)  Here’s the home page of the reference implementation (which you can try out at http://publications.plugh.org):

assn4-home-small

For the most part, the application just gives a list of books and links to web sites. Each publication may have tags and annotations. There are also links to Amazon and Safari when appropriate.

[NOTE regard "Safari" links: For the "reference implementation" and your own app, you can log in as john@7fff.com to see the Safari links; or, in your own implementation, you can set user.safari = true [and save that user] to turn on the Safari links — I decided to leave user management out of this assignment, because it gets complicated fast.]

If a user logs in, there are buttons on the page that allow the user to see the detail for a publication, edit a publication (if the user added it in the first place), and add an annotation (if the user has never added an annotation for the publication). Additionally, in the “detail” view, there are some additional buttons for adding index entries. You will need to play around with the application to get a feel for what’s there. Here’s what the home page looks after a user logs in:

assn4-home-logged-in-small

Here’s the schema (click to enlarge):

assn4-schema-small

The central table is publications. As you can see, a Publication has many annotations, publication tags, and index entries. A publication has tags through both index entries and through publication tags. For example, the book Agile Web Development with Rails might be tagged as “rails.” That means there would be a row in the publication_tags table with publication_id set to the id of AWDR in the publications table, and tag_id set to the id for the “rails” tag. There might also be an index entry for the tag “models.” This would be represented by a row in index_entries. The publication_id would be set to the id for AWDR, and the tag_id would be set to the id for the “models” tag.

The annotations table has brief comments on publications. A publication has many of these. Note that a user is only allowed one annotation per publication.

Finally, notice that all of the tables except users have a foreign key user_id pointing back to the users table. By this means, we know who created every row in the application. In general, we only want the user who created a row to be able to edit or delete it. I believe this is implemented completely in the reference implementation, but I might have forgotten a case!

Here’s what you need to do. We have left a number of parts of the application unimplemented. You need to implement them so that they behave in the same manner as the reference implemention (link above). Here’s the list:

  1. You must implement the CreateIndexEntries migration. The full set of migrations won’t complete without it. The down method is already done (disregard the code comment that says there’s something for you to do — there isn’t).
  2. You must complete the Annotation model.
    • You must validate the uniqueness of the publication_id for a particular value of the user_id. In other words, the combination of publication_id and user_id must be unique. Hint: Study the validations for PublicationTag.
    • The presence of the ‘brief’ attribute is required.
    • Add a before validation callback that converts all whitespace in the ‘brief’ and ‘full’ attributes to one space.
  3. On the Publication model, add a custom validation that ensures that there is a value for either the ‘title’ or ‘url’ attributes, or both.
  4. Add helpers link_to_amazon and link_to_safari in ApplicationHelper that format links to Amazon and Safari. For the formats, hover over the links in the reference implementation. Hint: Implement these by calling the link_to helper with the appropriate values. See the “Note regarding Safari links” above, regarding how to see them in the application.
  5. In AnnotationController, implement the new, edit, create, update, and destroy actions. It is critical that it be impossible for a user to edit or delete another user’s annotation. For example, the edit URL (in the reference implemenation) is http://localhost:3000/annotations/5/edit. If the current user didn’t create annotation 5, the action should fail (it doesn’t have to fail gracefully; it just has to not work). Hint: See the other controllers. It would also help to compare how the other controllers are implemented, vs. what script/generate scaffold gives you.
  6. In PublicationController, change create and update so that they can handle the keywords. This is by far the hardest part of the assignment because of the management of the checkboxes. To get some ideas for handling the free-form tag box, take a look at IndexEntriesController. The “Child-Care Coop” application also provides some guidance for checkbox handling.
  7. Implement views/annotations/_annotation.html.erb
  8. Implement views/shared/_indexed_publication.html.erb
  9. Implement the missing parts of views/publications/show.html.erb

That’s it!

  1. Lateral Punk
    November 12th, 2009 at 19:27 | #1

    Yes I think there is some confusion, and my second posting above was worded wrongly..

    So again:
    Q: How do you access instance variables *of* a Controller from inside *of* a custom helper?

    Again I don't think this is possible...

  2. November 13th, 2009 at 15:09 | #2
  3. Gabriel
    November 14th, 2009 at 07:28 | #3

    Just found an error in the data migrations:

    In ..._add_tags.rb you're missing the activerecord tag.
    This will lead to one of the publication_tags.tag on Pro Active Record to be nil (which made my assn5 solution break).

  4. Gabriel
    November 14th, 2009 at 08:17 | #4

    and another one with the same problem: tag projects on Practial Rails Projects

  5. November 14th, 2009 at 08:50 | #5

    @Gabriel

    I actually noticed those myself and fixed them, but was loathe to force people to do another download -- I'll add an errata, though.

  6. Ron Newman
    November 14th, 2009 at 11:54 | #6

    Looks like the application crashes only if you are logged in as keithlmorrison@gmail.com or and@mit.edu and you try to edit one of those two publications. If you're just displaying the publications, the association

    has_many :tags, :through => :publication_tags

    silently suppresses the nils.

    Is this a good argument that models should validate presence of foreign key IDs, and that migrations should declare foreign key references with :null => false ?

  7. November 14th, 2009 at 12:33 | #7

    @Ron Newman

    That would be a good argument if you think the code allows for that situation.

    The data problem is that I should be checking for nil's when the tags are looked up in the migration that creates the publications.

  8. Ron Newman
    November 14th, 2009 at 16:48 | #8

    It looks to me like the PublicationTagsController can never be invoked anywhere in the application. Am I correct?

  9. November 15th, 2009 at 07:53 | #9

    @Ron Newman

    Right - No HTML views for the publication_tags resource.

  10. David Palmer
    November 15th, 2009 at 18:56 | #10

    i noticed, unless i'm doing this wrong, that entering multiple tags for a publication doesn't create multiple tag records (as the field suggests -- using the text field) so, my question is, am i doing something wrong? or do we need to implement this feature?

  11. peter
    November 15th, 2009 at 19:22 | #11

    @David Palmer
    Yeah, it's listed as #6 in the above instructions. I missed it myself earlier today. I dont know how, but I did.

  12. Ron Newman
    November 15th, 2009 at 19:35 | #12

    Any place in the code with the words "student must implement" is a place where you need to add code. Look for those words in two places in app/controllers/publications_controller.rb

  13. qiangwu
    November 18th, 2009 at 01:02 | #13

    Hi, everyone

    I got this huge error when I testing my pages

    "We're sorry, but something went wrong."
    "We've been notified about this issue and we'll take a look at it shortly."

    I search google for an answer but failed, the solution is not quite my situation. I believe it has something to do with my asn4, since if I create a "rails dummy" the server back in normally.

    Anyone has any ideas?

    Thanks

  14. qiangwu
    November 18th, 2009 at 03:48 | #14

    I sort of figure it out, I accidentally place a method inside the ControllerHelper file. Took me three hours to trace it. The error msg so obscure that I thought my server was broken or something.

  15. November 18th, 2009 at 09:04 | #15

    @qiangwu

    Are you running in production mode? You should be seeing the stack trace on your web page when running in development mode.

    Also: script/server lists a lot of information regarding requests, including exception traces: If you don't see the exception trace on the page, look at the console output from script/console.

  16. qiangwu
    November 18th, 2009 at 11:37 | #16

    Hi, John
    how you doing

    The server break-down happens when I doing my asn4, Q6, I tried to place a routine method in the (squeezing and tokenizing the "space separated" tag string & building a production_tags record out of them) inside the Publications_helper file, and I include the module PublicationsHelper in my implementation controller, it was then the server went on strike.

    When that on-strike error msg shows, the script\console could not be loaded either, even other rails programs in different path cannot be run because of the server failure. Finally I kind of using de-migration technique to undo what I could memorize the last few lines of code, only then I catch the bug. I am curious that is there any other way to start the Rails Server (or start another server not running on port 3000) ? I guess my question is that is there any way to run more than one servers in my PC at the same time?

    Like most of the classmate, this is my 1st course to get to know RoR, I feel like Apache server is more predictive when I run my PHP script on it. But the more I get used to RoR, the more I like it.

  17. November 18th, 2009 at 11:47 | #17

    @qiangwu

    You can run all the server instances you want:

    script/server -p 3001
    script/server -p 3002
    script/server -p 3003

    (You will run out of memory at some point.)

    It sounds to me as though you have changed some settings in config/environments/development.rb -- I would create a new Rails app, and move over the original development.rb file.

    When you get an exception, you are supposed to see the trace on the web page -- for some reason, that is not happening for you.

    Are you browsing into http://localhost:3000 ? Or http://127.0.0.1:3000 ?

    Are you starting your serer in an unusual way?

  18. Ron Newman
    November 18th, 2009 at 11:55 | #18

    So is it a bad idea to include helper modules in controllers and to call helper methods from within controllers?

  19. November 18th, 2009 at 11:58 | #19

    @Ron Newman

    Include helpers in controllers all you want. Typically, helpers are for views, so you are somewhat breaking the MVC pattern, but it is not uncommon for people to bring in standard Rails helpers.

  20. qiangwu
    November 18th, 2009 at 12:00 | #20

    HI, John

    Not that I remember.

    When that "We are sorry .." box shows, only click "refresh' could cause millions of execution lines in the server console, most of them refer to the gem/.. paths, I guess it try to trace the very source of the problem.

    Now the server running great, and I am glad to know there's more than one way to start Rails Server :)). So far I am good, thanks, John

    ps: I prefer the old Harvard Calendar, we used to have six to eight week to design our project. In this 09 fall term, we lost at least 3 weeks of time.

  21. Ron Newman
    November 18th, 2009 at 12:04 | #21

    In the ApplicationController you gave us, I see this line:

    helper :all # include all helpers, all the time

    which I thought meant that you don't need to explicitly include the helper modules -- they're already included automatically?

  22. November 18th, 2009 at 12:23 | #22

    @Ron Newman

    For YOUR helpers. You might want a Rails helper, or you might have one in lib/ or in a gem . . .

  23. November 18th, 2009 at 14:18 | #23

    Regarding cross-site scripting (XSS).

    Check this out:

    http://www.reddit.com/r/technology/comments/a5osy/oh_apple_when_will_you_learn/

    Click where it says "Oh, Apple, When will you learn..."

    You go to Apple, and, as you can see, it brings in some of the Reddit site.

  24. Ron Newman
    November 19th, 2009 at 00:08 | #24

    Now that's just embarrassing.

    I prefer the term 'HTML injection' rather than 'Cross-Site Scripting' -- it's clearer what's going on.

  25. Ron Newman
    November 19th, 2009 at 00:11 | #25

    Looks like Apple has now fixed it, but it was definitely still hackable when I visited at 3 pm.

  26. November 19th, 2009 at 00:21 | #26

    @Ron Newman

    The "cross-site" part, though, is where the security issue lies.

  27. November 21st, 2009 at 02:42 | #27

    I had finished this assignment before several of the small changes came about. Should I attempt to merge these?

  28. November 21st, 2009 at 02:47 | #28

    Also, My eyes may be crossed as it is getting really late. Am I reading it right? In the past, when a write up is required (as well as Assignment 5), you have mentioned that one is required. When it isn't required, you have said it wasn't required. It's just not very clear whether we need this for Assignment 4 or not; or perhaps I missed it. Advise.

  29. November 21st, 2009 at 07:28 | #29

    @Jeff Ancel

    No, you do not need to merge changes.

    No writeup for 4.

  30. Ben Dawson
    November 22nd, 2009 at 15:06 | #30

    Do the 9 tasks need to be completed in order for any particular reason, beyond where it's obviously a prerequisite?

    Thanks in advance

    Ben

  31. November 22nd, 2009 at 18:16 | #31

    @Ben Dawson

    You need to do 1 and 2 before everything else.

  32. Qilong Xu
    November 22nd, 2009 at 20:43 | #32

    how to access http://publications.plugh.org? Please, I need to see how link_to_Amazon works. john@7fff.com is not working any more?

  33. November 22nd, 2009 at 21:24 | #33

    @Qilong Xu

    It's been running all evening -- I just verified it. The Amazon links show whether you're logged in or not.

  34. Qilong Xu
    November 22nd, 2009 at 21:33 | #34

    Thanks, but I could not login as "john@7fff.com". It had workd before. I only need to know how the html link looks like when asin is included.

  35. November 22nd, 2009 at 21:38 | #35

    @Qilong Xu

    Is it working now? Nothing has changed.

  36. Qilong Xu
    November 22nd, 2009 at 21:47 | #36

    not yet. no password is required, right? I wondered if the problem is caused by the linux system I am using? I will try MAC system again.

  37. Qilong Xu
    November 22nd, 2009 at 21:55 | #37

    No luck either for using Mac system.

  38. November 22nd, 2009 at 22:22 | #38

    @Qilong Xu

    When you first go to the site, you will be challenged with a popup for a username and password.

    Then you should see the site.

    Note that you should be able to see the Amazon links whether or not you are logged in.

    If you like, you can then log into the app. It doesn't matter what e-mail address you use.

    Qilong, you haven't really told me what you are seeing. It is really hard to help you if you don't say what the behavior you're seeing is.

    It is conceivable that you logged in with a user that was deleted when the database was updated -- though this shouldn't screw up your ability to use the site or log in.

    In any case, try removing your cookies for publications.plugh.org

  39. Qilong Xu
    November 22nd, 2009 at 23:01 | #39

    The problem is that no matter what kind of emails I used the authentication window keeps popuping, which prevents me from visiting the site (http://publications.plugh.org), even I cleaned up all cookies. I had been able to visit the site about 3 or 4 times before. I do not know what has gone wrong.

  40. Andre
    November 23rd, 2009 at 09:59 | #40

    In Assn. 4, when updating the publisher, there's a comment with a suggestion to delete the publication_tags relationship elements from the publication before creating new ones from the select box and the names in the text box. However, when you save the publication, the form still references the publication_tags (ids) that were deleted, so the update fails. I tried setting params[:publication][:publication_tag_ids] to null as well as emptying its contents, but that results in no tags shown in the publication summary even if they are in the db. How should I go around this?

  41. November 23rd, 2009 at 10:05 | #41

    @Andre

    This is just one way to do it. One sequence is:

    1. Delete all existing publication tags.
    2. Add the publication_tags back, based on the ids.
    3. Now you have a @publication that will be good when the form is re-displayed.

  42. Andre
    November 23rd, 2009 at 10:15 | #42

    But if I delete all existing publication tags, won't the ones I want to add back be deleted as well? Am I missing something?

  43. November 23rd, 2009 at 10:19 | #43

    @Andre

    Delete first, then add back. The ids for the ones you want to add back are in the params Hash.

    Alternatively, you can compare the "incoming" id's, and delete/add as appropriate.

    There is a somewhat similar scenario in the ccc2 example: See PlaydatesController#update

  44. Qilong Xu
    November 23rd, 2009 at 17:06 | #44

    Help!

    If anyone can see the content of http://publications.plugh.org with or without login, please let me know, thank you!

    Qilong

  45. Ron Newman
    November 23rd, 2009 at 17:40 | #45

    Site looks fine to me.

  46. Qilong Xu
    November 23rd, 2009 at 18:47 | #46

    Thank you, Ron Newman, I will try again.

  47. November 23rd, 2009 at 19:36 | #47

    Qilong -- as I said before, try deleting your cookies.

    To my knowledge, you are the only student having an issue using the app. What browser are you using . . . ?

  48. Qilong Xu
    November 23rd, 2009 at 19:37 | #48

    Hi Ron Newman, I have been trying login for two days from linux, mac, to windows systems, no luck. The only thing I need to see html sources of link_to_amazon and link_to_safari generated in the site so that I could complete the assignment 4 soon. Would you please post them?

  49. November 23rd, 2009 at 19:50 | #49

    @Qilong Xu

    I just e-mailed you the username and password. Check your e-mail, PLEASE.

    Ron - Do not post any course passwords, etc.

  50. Qilong Xu
    November 23rd, 2009 at 19:52 | #50

    @john
    I am using Firefox and also tried Apple Safari, neither works. I had cleaned up everything-history, cache, cookes, etc. but it keeps poping up the authentication window, no matter which emails I used.

Comment pages
  1. No trackbacks yet.