Rails Cookbook by Rob Orsini The unconfirmed error reports are from readers. They have not yet been approved or disproved by the author or editor and represent solely the opinion of the reader. Here's a key to the markup: [page-number]: serious technical mistake {page-number}: minor technical mistake : important language/formatting problem (page-number): language change or minor formatting problem ?page-number?: reader question or request for clarification This page was updated April 14, 2008. UNCONFIRMED errors and comments from readers: ======================= {00} Recipe 3.16. Modeling a Threaded Forum with acts_as_nested_set; It would be DRYer with better encapsulation in this recipe to move some of the logic to the post model with a method like "is_viewable?", keeping the logic in one place instead of in the helper and view. Also you don't need to use a global variable in your recursive helper, and you need a block element (like

) to wrap each post if you want it to display as you have it in the screenshot. model: class Post < ActiveRecord::Base acts_as_nested_set def is_viewable? send(parent_column) != nil end end helper: module PostsHelper def get_indentation(post, n=0) return n unless post.is_viewable? parent = Post.find(post.send(post.parent_column)) get_indentation(parent, n += 1) end end view:

Threaded Forum

<% for post in @posts %>

<% get_indentation(post).times do %>_ <% end %> <%= post.subject %> [ <% if post.is_viewable? %> <%= link_to "view", :action => "view", :post => post.id %> | <% end %> <%= link_to "reply", :action => "new", :parent => post.id %> ]

<% end %> [00] Recipe 3.18. Avoiding Race Conditions with Optimistic Locking; First sentence after Solution says: "There is no way to force Rails to lock a row for later update. This is commonly known as pessimistic locking or select for update. To lock a row with Active Record, you need to use optimistic locking." However, Rails does support pessimistic locking. See the API at: http://api.rubyonrails.com/classes/ActiveRecord/Locking/Pessimistic.html "Locking::Pessimistic provides support for row-level locking using SELECT FOR UPDATE and other lock types." (00) Recipe 3.22. Mixing Join Models and Polymorphism for Flexible Data Modeling; The second code example for magazine.rb says: has_many :subscribers, :through => :subscriptions but it should be: has_many :readers, :through => :subscriptions {00} Recipe 4.15. Tracking Information with Sessions; The square root of 4 is 2, not 16. Maybe you meant the 4 squared? { :question => "What's the square root of 4?", :options => ['16','2','8'], :answer => '2' }, {00} Recipe 4.16. Using Filters for Authentication; The UsersController needs to require the 'digest/sha1' module for calls to Digest::SHA1.hexdigest. app/controllers/users_controller.rb: require 'digest/sha1' class UsersController < ApplicationController {00} 5.11. Processing Dynamically Created Input Fields; The order of user.id and role.id is switched in the helper get_perm method, resulting in the controller not being able to update the permissions table. user_helper.rb: def get_perm(role_id, user_id) should be: def get_perm(user_id, role_id) (21) second paragraph from the bottom; The line: > ruby script\generate migration add_photos does not work for me ( lots of lines of errors ). So I tried: > rails script/generate migration add_photos and it appears to work. But then I got confused because the next bit says: edit the file db/migrate/add_photos.rb But it should read: edit the file db/migrate/001_create_photos.rb add_photos is the class within this .rb file (27) First full paragraph after the call app/controllers/language_controller.rb; First sentennce of the paragraph reads: Here, you are passing the scaffold method a symbol representing your model; :languages in this case. It should match the code just above it: scaffold :language. So the sentence should read: Here, you are passing the scaffold method a symbol representing your model; :language in this case. (43) Last paragraph (between headings and bulleted list); The descriptions of common RDoc formatting options should precede the corresponding examples, rather than trail them. E.g., Here are some other common formatting options: # = Heading One # # == Heading Two # # === Heading Three The following produces heading [sic] of various sizes: should be changed to something like: Here are some other common formatting options--the following produces headings of various sizes: # = Heading One # # == Heading Two # # === Heading Three etc. The same problem is repeated for all four examples on pp. 43-44. ====================== {50} 0th paragraph; This is from the January 2007 Printing. From text: "For example, a students table referencing another table named courses would contain a courses_id column" This should read: "...contain a course_id column" Note that the name of the foreign key field is in the singular. [51] Create table recipes and create table recipes_tags are not the same for mysql and postgres; the mysql definition is primary key of id, chapter_id, and title. the postgres definition is primary key of id. The mysql table is defined like this in the book: create table recipes ( id int not null auto_increment, chapter_id int not null, title varchar(255) not null, problem text not null, solution text not null, discussion text not null, see_also text null, sort_order int not null default 0, primary key (id, chapter_id, title), foreign key (chapter_id) references chapters(id) ) type=innodb; As such, would allow id to have many reuses of id where as the postgres version only allows id to occur 1 time in the table. I am assuming that the postgres version is what was intended and thus the mysql table would be defined as this: create table recipes ( id int not null auto_increment, chapter_id int not null, title varchar(255) not null, problem text not null, solution text not null, discussion text not null, see_also text null, sort_order int not null default 0, primary key (id), foreign key (chapter_id) references chapters(id) ) type=innodb; Also, the table recipes_tags for mysql would allow a recipe to have many tags and a tag to have many recipes... where as the postgres version has several flaws. It is defined in the book as this: create table recipes_tags ( recipe_id serial unique references recipes(id), tag_id serial unique references tags(id) ); Serial should not be used for a column that is being used for reference to another table. The first reason being that it creates a default of nextval for a column that is supposed to be using an id from another table. It should be integer. This insert would be valid given the serial definition for the colunm: insert into recipes_tags (recipe_id)values(1); as long as there was a recipe of id 1 and a tag of the id generated by the sequence. Also the unique definition is not the same for the mysql table recipes_tags and the postgres version. placing unique on each of the columns in the postgres version causes the table to only allow a recipe_id to be entered one time and likewise for the tag_id. Thus to be equivalent to the mysql table defined as this: create table recipes_tags ( recipe_id int not null, tag_id int not null, primary key (recipe_id, tag_id), foreign key (recipe_id) references recipes(id), foreign key (tag_id) references tags(id) ) type=innodb; I would suggest this definition for postgres: create table recipes_tags ( recipe_id integer not null references recipes(id), tag_id integer not null references tags(id), primary key (recipe_id, tag_id) ); These were tested against mysql 5.0.45 and postgres 8.2.4. {77} Recipe 3.10, Updating an Active Record Object; Since the book model specifies that you can have multiple inserts and these should be able to change, a better solution would be to use the following multiple select, or better yet a set of checkboxes, and allow ActiveRecord's update_attributes handle the model association updates for you: edit.rhtml: or better... books_controller.rb: (Less code and fewer calls to the database.) def update @book = Book.find(params[:id]) if @book.update_attributes(params[:book]) flash[:notice] = 'Book was successfully updated.' redirect_to :action => 'show', :id => @book else render :action => 'edit' end end ======================= {84-85} Recipe 3.12. Executing Custom Queries with find_by_sql; For the migration, if you explicitly set the genre_id for the movie using Movie.create, you need to pass in the genre id explicitly, not just the genre object, when you create the movie. Otherwise the movie will default to a genre_id of 1. db/migrate/001_build_db.rb: ... Movie.create :genre_id => genre1.id, :name => 'Mishi Kobe Niku', :sales => 234303.32, :released_on => '2006-11-01' ... Alternatively, and I think this is probably what you meant to do, you can set the genre attribute upon creation instead of explicitly setting the genre_id: Movie.create :genre => genre1, :name => 'Mishi Kobe Niku', :sales => 234303.32, :released_on => '2006-11-01' ======================= {92} Recipe 3.14. Adding Sort Capabilities to a Model with acts_as_list; Since you've defined order as position for the book model, then you don't need to do a separate lookup for @chapters in the controller, and instead you can reference @book.chapters from the view and they will be in the correct order (by position). Also, if you keep track of the chapters size in the controller, you can avoid extra calls to the database for chapter.first? and chapter.last?. Here's the code I'd recommend, with the old code commented out for comparison: controller: def list @book = Book.find(params[:id]) # @chapters = Chapter.find(:all, # :conditions => ["book_id = %d", params[:id]], # :order => "position") @chapters_size = @book.chapters.size end view:

<%= @book.name %> Contents:

    <%# for chapter in @chapters %> <% for chapter in @book.chapters %>
  1. <%= chapter.name %> [ move: <%# unless chapter.first? %> <% unless chapter.position == 1 %> <%= link_to "up", { :action => "move", :method => "move_higher", :id => params["id"], :ch_id => chapter.id } %> <%= link_to "top", { :action => "move", :method => "move_to_top", :id => params["id"], :ch_id => chapter.id } %> <% end %> <%# unless chapter.last? %> <% unless chapter.position == @chapters_size %> <%= link_to "down", { :action => "move", :method => "move_lower", :id => params["id"], :ch_id => chapter.id } %> <%= link_to "bottom", { :action => "move", :method => "move_to_bottom", :id => params["id"], :ch_id => chapter.id } %> <% end %> ]
  2. <% end %>
======================= {100} Recipe 3.16. Modeling a Threaded Forum with acts_as_nested_set; It would be DRYer with better encapsulation in this recipe to move some of the logic to the post model with a method like "is_viewable?", keeping the logic in one place instead of in the helper and view. Also you don't need to use a global variable in your recursive helper, and you need a block element (like

) to wrap each post if you want it to display as you have it in the screenshot. model: class Post < ActiveRecord::Base acts_as_nested_set def is_viewable? send(parent_column) != nil end end helper: module PostsHelper def get_indentation(post, n=0) return n unless post.is_viewable? parent = Post.find(post.send(post.parent_column)) get_indentation(parent, n += 1) end end view:

Threaded Forum

<% for post in @posts %>

<% get_indentation(post).times do %>_ <% end %> <%= post.subject %> [ <% if post.is_viewable? %> <%= link_to "view", :action => "view", :post => post.id %> | <% end %> <%= link_to "reply", :action => "new", :parent => post.id %> ]

<% end %> ======================= [107] Recipe 3.18. Avoiding Race Conditions with Optimistic Locking; First sentence after Solution says: "There is no way to force Rails to lock a row for later update. This is commonly known as pessimistic locking or select for update. To lock a row with Active Record, you need to use optimistic locking." However, Rails does support pessimistic locking. See the API at: http://api.rubyonrails.com/classes/ActiveRecord/Locking/Pessimistic.html "Locking::Pessimistic provides support for row-level locking using SELECT FOR UPDATE and other lock types." ======================= {114} bottom example code; Has a mistake were is write ":type =>" the correct is ":location =>" . The "type" field doesn't exist on table "phone_numbers". change: :type => "fake") and :type => "Fake office line") to: :location => "fake") and :location => "Fake office line") ====================== (115) Recipe 3.22. Mixing Join Models and Polymorphism for Flexible Data Modeling; The second code example for magazine.rb says: has_many :subscribers, :through => :subscriptions but it should be: has_many :readers, :through => :subscriptions ====================== {130} First sentence at top of page; "The form submits to the Check controller..." should be "The form submits to the check action..." {130} Bottom; "...and his password is displayed...": The success.rhtml code doesn't display the password (in params[:pass]). {142} Discussion section for Recipe 4.12; The first paragraph of the Discussion states that "Protected methods can be invoked by other objects of the same class and its subclasses. Private methods can be invoked only by an object on itself." Yet in the third paragraph of the Discussion, the private method double_bonus is stated to be "...available only to other methods in the Employee controller or its subclasses." If double_bonus is private, it is not available to subclasses of the Employee controller (according to the first paragraph). [257] test/unit/book_test_crud.rb example; If the book model contains a validates_uniqueness_of check on :isbn (it *should*) or :title (it *could*), then the first assertion: assert lisp_cookbook.save will fail, since it's trying to save a new record in the db with the same title and ISBN as the first record in the books.yml fixtures file. I think the first operation in the test_book_CRUD method should be creating a new, unique book--not one that already exists--then do the rest of the checks against the new book. {279} 3rd 4th paragraph; <% form_tag({ :action => "add" }, :id => id, :enctype => "multipart/form-data") do %> recommended :id => object_id to avoid the warning when you run mongrel_rails start -d (279) In code index.rhtml; <%= link_to_remote "Add field", :update => "files", :url => { :action => "add_field" }, :position => "after" %> ; <-- Extra ; [278] Adding DOM; Does not work in IE.. does work in Firefox [280] 2nd 3rd paragraph; undefined method `full_orginal_filename' for # [281] Below the datbase schema at the top of the page; This example gives you the database schema to be used by the example from db/schema.rb, but fails to mention you must "ruby script\generate model customer" after the database is created. Without the model generated the example code will throw an uninitialized constant exception when you execute. In the file report.rhtml around line #3 <% for column in Customer.column_names %> Customer.column_names will throw the exception because the Customer object will not be instantiated when called without the model generated. [306] 4th paragraph; NameError in Search#index Showing app/views/search/_search_results.rhtml where line #2 raised: undefined local variable or method `article_body' for #<#:0x3e27f7c> Extracted source (around line #2): 1:

2: <%= highlight(article_body, search_text, 3: '\1') %> 5:

Trace of template inclusion: /app/views/search/index.rhtml RAILS_ROOT: ./script/../config/.. Application Trace | Framework Trace | Full Trace #{RAILS_ROOT}/app/views/search/_search_results.rhtml:2:in `_run_rhtml_47app47views47search47_search_results46rhtml' #{RAILS_ROOT}/app/views/search/index.rhtml:11:in `_run_rhtml_47app47views47search47index46rhtml' it happens the first time you access the search_controller (308) db/migrate/001_create_terms.rb; The Term.create entry for the definition of ISDN has a "v" before existing. It reads: Basically a way to move data over vexisting regular phone lines. (322) First paragraph in Solution section; ActionMailer::Base.server_settings = {...} has been renamed to ActionMailer::Base.smtp_settings = {...} [353] 6th paragraph; The change to edit.rhtml should be (and it took me a day to figure this out since Im such a newbie) <%= start_form_tag({:action => 'update', :id => @user}, :multipart => true) %> You need to get the :id in there or the standard update method in the controler can't figure out how to do the @user = User.find(params[:id]) This assumes that one is using the scaffolding of rails to generate the controller. {373} 1st paragraph; att_accessible should be attr_accessible {404} middle of the page; This line is missing the destination directory for the cp command: $ sudo cp /usr/lib/ruby/gems/1.8/gems/\ >mongrel_cluster-0.2.0/resources/mongrel_cluster It should read: $ sudo cp /usr/lib/ruby/gems/1.8/gems/\ >mongrel_cluster-0.2.0/resources/mongrel_cluster /etc/init.d/ [428] In config/mongrel_cluster.yml; Page 428: (In config/mongrel_cluster.yml): cwd: /var/www/cookbook/current should be: cwd: /var/www/apps/cookbook/current Everywhere else the recipe refers to the app being located at /var/www/apps/#{application} [456] acts_as_dictionary/init.rb code; It's entirely possible I'm wrong, as I'm fairly new to Rails, but the line ActiveRecord::Base.send(:include,ActiveRecord::Acts::Reportable) should (?) read ActiveRecord::Base.send(:include,Cookbook::Acts::Reportable) when I used the published line I got `load_missing_constant': uninitialized constant ActiveRecord::Acts::Reportable (NameError) when starting mongrel mongrel starts fine if I do the replacement above, using my own namespace {461} second example; The example ActionView::Base.send :include, XhtmlValidationHelper should read ActionView::Base.send :include, W3cValidationHelper or webrick will not start {474} Discussion paragraph; FYI, this recipe does *NOT* work with Rails 1.2. Please see the plugin author's comments: http://www.rails-engines.org/news/2007/01/23/farewell-login_engine-/ It should be clearly noted that Rails 1.2 does not support this recipe since most users will be using 1.2. (It was beyond frustrating spending hours trying to figure out why the recipe did not work on any of my systems!) ======================