Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Beyond Convention Over Configuration

Beyond Convention Over Configuration

This is my first public Ruby talk that I gave at the Dallas.rb user group on 6/7/2011.

The talk is an overview of Rails best practices and utilizing some of the new features in Rails 3 to write cleaner code. The bulk of the "best practices" presented here are opinion with some opinions leading to more cleaner code than others.

Topics covered by this talk are:

Emaciated controllers
Scopes
Custom Models
Cached Counters
Security
Using Delegates
Presenters
Tips and Tricks for Views

Jesse Dearing

June 08, 2011
Tweet

More Decks by Jesse Dearing

Other Decks in Programming

Transcript

  1. What I’ll Cover • Emaciated controllers • Scopes • Custom

    Models • Cached Counters • Security • Using Delegates • Presenters • Tips and Tricks for Views
  2. Rails 3 ActiveRecord Query Interface • Options hash on find()

    will be deprecated in Rails 3.1 • Also find(:first) and find(:all) • Lazy loading • Fluent • Clauses are broken out into chain-able method calls
  3. ActiveRecord Example Queries Post.where(:id => 1) "SELECT `posts`.* FROM `posts`

    WHERE `posts`.`id` = 1" .order(“id DESC”) ORDER BY id DESC
  4. ActiveRecord Example Queries Post.where(:id => 1) "SELECT `posts`.* FROM `posts`

    WHERE `posts`.`id` = 1" .where(:can_comment => true) AND `posts`.`can_comment` = 1 .order(“id DESC”) ORDER BY id DESC
  5. Emaciated Controllers • “Skinny controller, fat model.” - Jamis Buck

    • Controllers should only be setting up data for the view • I.e. Your controller should only be setting instance/session/cookie/flash variables
  6. Emaciated Controllers 1 class AssignmentsController < ApplicationController 2 def drop_assignment

    3 assignment = Assignment.find(params[:id]) 4 5 if !current_user.is_teacher? 6 flash[:notice] = "You must be a teacher to drop this assignment" 7 elsif assignment.dropped? 8 flash[:notice] = "You've already dropped this assignment" 9 else 10 assignment.dropped = true 11 assignment.save 12 flash[:notice] = "Assignment dropped" 13 end 14 15 redirect_to assignment 16 end 17 end
  7. Emaciated Controllers 1 class AssignmentsController < ApplicationController 2 def drop_assignment

    3 assignment = Assignment.find(params[:id]) 4 flash[:notice] = assignment.drop(current_user) 5 redirect_to assignment 6 end 7 end 8 9 class Assignment < ActiveRecord::Base 10 def drop(current_user) 11 if !current_user.is_teacher? 12 "You must be a teacher to drop this assignment" 13 elsif self.dropped? 14 "You've already dropped this assignment" 15 else 16 self.dropped = true 17 self.save 18 "Assignment dropped" 19 end 20 end 21 end
  8. Filters • Filters should be used for authorization, logging, wizards,

    or anything that should happen before or after a controller action executes • You should not be defining instance variables in filters
  9. Filters 1 class GradeBooksController < ApplicationController 2 before_filter :load_gradebook, :only

    => [:show, :edit, :update, :destroy] 3 def index 4 # ... 5 end 6 def show 7 # ... 8 end 9 def edit 10 # ... 11 end 12 def update 13 # ... 14 end 15 def destroy 16 # ... 17 end 18 private 19 def load_gradebook 20 @gradebook = GradeBook.find(params[:id]) 21 end 22 end 23
  10. Filters 1 class GradeBooksController < ApplicationController 2 before_filter :load_gradebook, :only

    => [:show, :edit, :update, :destroy] 3 def index 4 # ... 5 end 6 def show 7 # ... 8 end 9 def edit 10 # ... 11 end 12 def update 13 # ... 14 end 15 def destroy 16 # ... 17 end 18 private 19 def load_gradebook 20 @gradebook = GradeBook.find(params[:id]) 21 end 22 end 23 Why?
  11. Filters 1 class GradeBooksController < ApplicationController 2 before_filter :load_gradebook, :only

    => [:show, :edit, :update, :destroy] 3 def index 4 # ... 5 end 6 def show 7 # ... 8 end 9 def edit 10 # ... 11 end 12 def update 13 # ... 14 end 15 def destroy 16 # ... 17 end 18 private 19 def load_gradebook 20 @gradebook = GradeBook.find(params[:id]) 21 end 22 end 23 Why? Magic instance variables
  12. Filters 1 class GradeBooksController < ApplicationController 2 before_filter :load_gradebook, :only

    => [:show, :edit, :update, :destroy] 3 def index 4 # ... 5 end 6 def show 7 # ... 8 end 9 def edit 10 # ... 11 end 12 def update 13 # ... 14 end 15 def destroy 16 # ... 17 end 18 private 19 def load_gradebook 20 @gradebook = GradeBook.find(params[:id]) 21 end 22 end 23
  13. Filters 1 class GradeBooksController < ApplicationController 2 def index 3

    @gradebook = load_gradebook(params[:id]) 4 end 5 def show 6 @gradebook = load_gradebook(params[:id]) 7 end 8 def edit 9 @gradebook = load_gradebook(params[:id]) 10 end 11 def update 12 @gradebook = load_gradebook(params[:id]) 13 end 14 def destroy 15 @gradebook = load_gradebook(params[:id]) 16 end 17 private 18 def load_gradebook(gb_id) 19 GradeBook.find(gb_id) 20 end 21 end
  14. Filters 1 class GradeBooksController < ApplicationController 2 def index 3

    @gradebook = load_gradebook(params[:id]) 4 end 5 def show 6 @gradebook = load_gradebook(params[:id]) 7 end 8 def edit 9 @gradebook = load_gradebook(params[:id]) 10 end 11 def update 12 @gradebook = load_gradebook(params[:id]) 13 end 14 def destroy 15 @gradebook = load_gradebook(params[:id]) 16 end 17 private 18 def load_gradebook(gb_id) 19 GradeBook.find(gb_id) 20 end 21 end
  15. DRY • “Every piece of knowledge must have a single,

    unambiguous, authoritative representation within a system.” - Dave Thomas and Andy Hunt, The Pragmatic Programmer • The method is the representation, not the instance variable.
  16. Global Filters • If you set a before_filter or after_filter

    in the ApplicationController it will execute on all of the controllers that inherit from it • If you would like to have your controller not execute the filter use the skip_before_filter or skip_after_filter • This situation is ideal for login pages where you would redirect infinitely
  17. 1 class AssignmentsController < ApplicationController 2 def index 3 @assignments

    = Assignment.all 4 respond_to do |format| 5 format.html 6 format.xml { render :xml => @assignments.to_xml } 7 format.json { render :json => @assignments.to_json } 8 end 9 end 10 11 def show 12 @assignment = Assignment.find(params[:id]).includes(:problems).all 13 14 respond_to do |format| 15 format.html 16 format.xml { render :xml => @assignment.to_xml } 17 format.json { render :json => @assignment.to_json } 18 end 19 end 20 end Rails 3 respond_to Rails 3 respond_to has changed to make it easier to respond to different content types
  18. Rails 3 respond_to Rails 3 respond_to has changed to make

    it easier to respond to different content types 1 class AssignmentsController < ApplicationController 2 respond_to :html, :xml, :json 3 4 def index 5 @assignments = Assignment.all 6 respond_with @assignments 7 end 8 9 def show 10 @assignment = Assignment.find(params[:id]).includes(:problems).all 11 respond_with @assignment 12 end 13 end
  19. Scopes • Named scopes in ActiveRecord increase readability of your

    queries • Default scope lets you define how your model should present data • Lambdas can be used to allow values to be passed to scopes and to bypass the ActiveRecord query cache
  20. 1 class GradeBooksController < ApplicationController 2 def index 3 current_classes

    = ClassPeriod.where(:completed => false).map {|c| c.id} 4 @classes = GradeBook.where(:class_id => current_classes) 5 end 6 end Named Scope
  21. 1 class GradeBooksController < ApplicationController 2 def index 3 current_classes

    = ClassPeriod.where(:completed => false).map {|c| c.id} 4 @classes = GradeBook.where(:class_id => current_classes) 5 end 6 end Named Scope 1 class GradeBooksController < ApplicationController 2 def index 3 current_classes = ClassPeriod.in_progress.map {|c| c.id} 4 @classes = GradeBook.classes(current_classes) 5 end 6 end 1 class ClassPeriod < ActiveRecord::Base 2 scope :in_progress, where(:completed => false) 3 end 1 class GradeBook < ActiveRecord::Base 2 scope :classes, lambda {|current_classes| where(:class_id => current_classes)} 3 end
  22. Scopes vs. Methods • Some advocate using plain ol’ Ruby

    methods instead of using the scope method • Use methods for when passing variables into a scope because lambdas are less readable
  23. Scopes vs. Methods • Some advocate using plain ol’ Ruby

    methods instead of using the scope method • Use methods for when passing variables into a scope because lambdas are less readable IMHO
  24. Helper vs. Plain Ruby 1 class ClassPeriod < ActiveRecord::Base 2

    scope :in_progress, where(:completed => false) 3 end 1 class ClassPeriod < ActiveRecord::Base 2 def self.in_progress 3 where(:completed => false) 4 end 5 end vs
  25. Helper vs. Plain Ruby 1 class ClassPeriod < ActiveRecord::Base 2

    scope :during_timeframe, lambda {|trange| where(:start_time => trange)} 3 end 1 class ClassPeriod < ActiveRecord::Base 2 def self.during_timeframe(trange) 3 where(:start_time => trange) 4 end 5 end vs
  26. Single Table Inheritance • Kind of like a scope with

    class inheritance • Based on Martin Fowler’s STI pattern in Patterns of Enterprise Application Architecture • Doing a find with base class returns proper type • Adds ‘type’ column
  27. 1 Person.find(1) 2 => #<Student first_name: "Jesse", last_name: "Dearing", ...>

    3 4 Person.find(2) 5 => #<Teacher first_name: "Katie", last_name: "Dearing", department: "Geography", ...> STI in Code
  28. Custom Models • Rails’ ActiveModel gives you all the awesomeness

    of ActiveRecord without requiring SQL • Validations • Helper methods
  29. 1 class SupportIncident 2 include ActiveModel::Validations 3 include ActiveModel::Conversion 4

    5 attr_accessor :name, :email, :customer_id, :description 6 validates_presence_of :name, :email, :description 7 validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i 8 validates_format_of :customer_id, :with => /\d{8}/i 9 10 def initialize(attributes = {}) 11 attributes.each do |name, value| 12 send("#{name}=", value) 13 end 14 end 15 16 def persisted? 17 false 18 end 19 end Needs to have persisted? method return false ActiveModel
  30. 1 class SupportIncident 2 include ActiveModel::Validations 3 include ActiveModel::Conversion 4

    5 attr_accessor :name, :email, :customer_id, :description 6 validates_presence_of :name, :email, :description 7 validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i 8 validates_format_of :customer_id, :with => /\d{8}/i 9 10 def initialize(attributes = {}) 11 attributes.each do |name, value| 12 send("#{name}=", value) 13 end 14 end 15 16 def persisted? 17 false 18 end 19 end Needs to have persisted? method return false ActiveModel
  31. 1 class Problem < ActiveRecord::Base 2 belongs_to :assignment, :counter_cache =>

    true 3 end Adding Counter Caches 1 class Assignment < ActiveRecord::Base 2 has_many :problems 3 end
  32. Adding Counter Caches 1 <table> 2 <thead> 3 <th> 4

    <td> 5 Homework Assignment Name 6 </td> 7 <td> 8 Number of Problems 9 </td> 10 </th> 11 </thead> 12 <tbody> 13 <% @assignments.each do |assignment| %> 14 <tr> 15 <td> 16 <%= assignment.name %> 17 </td> 18 <td> 19 <%= assignment.problems.size %> 20 </td> 21 </tr> 22 <% end %> 23 </tbody> 24 </table>
  33. Adding Counter Caches 1 <table> 2 <thead> 3 <th> 4

    <td> 5 Homework Assignment Name 6 </td> 7 <td> 8 Number of Problems 9 </td> 10 </th> 11 </thead> 12 <tbody> 13 <% @assignments.each do |assignment| %> 14 <tr> 15 <td> 16 <%= assignment.name %> 17 </td> 18 <td> 19 <%= assignment.problems.size %> 20 </td> 21 </tr> 22 <% end %> 23 </tbody> 24 </table> size? WTF?
  34. Makin’ It Rain Extraneous SQL Queries assignment.problems.length Pulls all records

    and then .length Pulls all records and then .length assignment.problems.count COUNT(*) query COUNT(*) query assignment.problems.size COUNT(*) query No SQL Uses cache column Without Cache With Cache
  35. Makin’ It Rain Extraneous SQL Queries assignment.problems.length Pulls all records

    and then .length Pulls all records and then .length assignment.problems.count COUNT(*) query COUNT(*) query assignment.problems.size COUNT(*) query No SQL Uses cache column Without Cache With Cache
  36. Makin’ It Rain Extraneous SQL Queries assignment.problems.length Pulls all records

    and then .length Pulls all records and then .length assignment.problems.count COUNT(*) query COUNT(*) query assignment.problems.size COUNT(*) query No SQL Uses cache column Without Cache With Cache
  37. 1 desc 'Export Student data' 2 3 task :export_data =>

    :environment do 4 csv = File.open("#{Rails.root}/export.csv") 5 Student.all.each do |p| 6 row = [s.id, s.first_name, s.last_name, s.dob, s.current_average] 7 csv.write("#{row.join(",")}\n") 8 end 9 csv.close 10 end Whuddup batches?
  38. 1 desc 'Export Student data' 2 3 task :export_data =>

    :environment do 4 csv = File.open("#{Rails.root}/export.csv") 5 Student.all.each do |p| 6 row = [s.id, s.first_name, s.last_name, s.dob, s.current_average] 7 csv.write("#{row.join(",")}\n") 8 end 9 csv.close 10 end Whuddup batches? Pulls down all records!
  39. Whuddup batches? 1 desc 'Export Student data' 2 3 task

    :export_data => :environment do 4 csv = File.open("#{Rails.root}/export.csv") 5 Student.find_each do |p| 6 row = [s.id, s.first_name, s.last_name, s.dob, s.current_average] 7 csv.write("#{row.join(",")}\n") 8 end 9 csv.close 10 end
  40. Whuddup batches? 1 desc 'Export Student data' 2 3 task

    :export_data => :environment do 4 csv = File.open("#{Rails.root}/export.csv") 5 Student.find_each(:batch_size => 200) do |s| 6 row = [s.id, s.first_name, s.last_name, s.dob, s.current_average] 7 csv.write("#{row.join(",")}\n") 8 end 9 csv.close 10 end
  41. Don’t Implicitly Trust params • An attacker can insert something

    by adding a simple user[is_admin] =true to the query parameters when they create an account. • attr_protected and attr_accessible were created for this
  42. Get Your Attributes on the List 1 class User <

    ActiveRecord::Base 2 attr_protected :is_admin 3 end The is_admin key-value will be ignored in a hash passed over in new or update_attributes
  43. Get Your Attributes on the List 1 class User <

    ActiveRecord::Base 2 attr_accessible :username, :password, :email 3 end This allows only the keys listed here to be used by update_attributes and new
  44. Prevent SQL Injection This is 2011, everyone should be parameterizing

    SQL statements 1 Student.where("last_name = ?", params[:last_name])
  45. Prevent SQL Injection This is 2011, everyone should be parameterizing

    SQL statements 1 Student.where("last_name = :last_name AND dob <= :birthdate", 2 {:last_name => params[:last_name], :birthdate => params [:birthdate].to_date}) 1 Student.where(:last_name => params[:last_name]) 1 Student.where(:dob => 2 (params[:begin_dob].to_date)..(params[:end_dob].to_date))
  46. Delegates • Delegates allow you to pass method calls to

    another property • Why not just call the method directly? • Law of Demeter • “Don’t talk to strangers”
  47. Law of Demeter • Referrals can reference Students • Students

    can reference Attendance and ParentContacts • Referrals should not access ParentContacts ParentContact Student Referral Attendance
  48. 1 class Referral < ActiveRecord::Base 2 def email_parent 3 parent_email

    = self.student.parent_contact.work_email 4 # ... 5 end 6 end I’m not lazy, I’m delegating Referral doesn’t need to know about ParentContact
  49. I’m not lazy, I’m delegating Referral doesn’t need to know

    about ParentContact 1 class Student < ActiveRecord::Base 2 has_one :parent_contact 3 delegate :work_email, :to => :parent_contact 4 end 1 class Referral < ActiveRecord::Base 2 def email_parent 3 parent_email = self.student.work_email 4 # ... 5 end 6 end
  50. I’m not lazy, I’m delegating Referral doesn’t need to know

    about ParentContact 1 class Student < ActiveRecord::Base 2 has_one :parent_contact 3 delegate :work_email, :to => :parent_contact 4 end 1 class Referral < ActiveRecord::Base 2 def email_parent 3 parent_email = self.student.work_email 4 # ... 5 end 6 end What if parent_contact is nil?
  51. I’m not lazy, I’m delegating Referral doesn’t need to know

    about ParentContact 1 class Referral < ActiveRecord::Base 2 def email_parent 3 parent_email = self.student.work_email 4 # ... 5 end 6 end 1 class Student < ActiveRecord::Base 2 has_one :parent_contact 3 delegate :work_email, :to => :parent_contact, :allow_nil => true 4 end Now just returns nil if parent_contact is nil
  52. The Presenter Pattern • Benefits include • Smaller controllers •

    Less confusing views • Testable • More elegant than view helpers • Stored in app/presenters • config.autoload_paths += [config.root.join("app/ presenters")]
  53. 1 class ProgressReportController < ApplicationController 2 def index 3 @classroom

    = Classroom.where(:classroom_location => params[:classroom_location], 4 :period => params[:period]).include(:students).first 5 @semester = Semester.where(:semester_number => params[:semester_number], 6 :year => params[:semester_year]).first 7 end 8 end Stand and Present
  54. View to a Kill 1 <div class="prefix_10 grid_2"> 2 <%=

    @semester.name %> <%= @semester.year %> 3 </div> 4 <div class="clear"></div> 5 <div class="prefix_1 suffix_1 grid_10"> 6 <table class="full_width"> 7 <thead> 8 <tr> 9 <th> 10 First Name 11 </th> 12 <th> 13 Last Name 14 </th> 15 <th> 16 Student ID 17 </th> 18 <th> 19 Current Average 20 </th> 21 <th> 22 Cumulative Average 23 </th> 24 </td> 25 </thead>
  55. View to a Kill 18 <th> 19 Current Average 20

    </th> 21 <th> 22 Cumulative Average 23 </th> 24 </td> 25 </thead> 26 <tbody> 27 <% @classroom.students.each do |student| %> 28 <tr> 29 <td> 30 <%= student.first_name %> 31 </td> 32 <td> 33 <%= student.last_name %> 34 </td> 35 <td> 36 <%= student.student_id %> 37 </td> 38 <td> 39 <%= student.current_average(@semester) %> 40 </td> 41 <td> 42 <%= student.cumulative_average(@semester) %> 43 </td> 44 </tr> 45 <% end %> 46 </tbody> 47 </table> 48 </div> 49 <div class="clear"></div>
  56. Stand and Present 1 class ProgressReportController < ApplicationController 2 def

    index 3 @classroom = Classroom.where(:classroom_location => params[:classroom_location], 4 :period => params[:period]).include(:students).first 5 @semester = Semester.where(:semester_number => params[:semester_number], 6 :year => params[:semester_year]).first 7 end 8 end
  57. 1 class ProgressReportController < ApplicationController 2 def index 3 @progress_report

    = ProgressReportPresenter.new(params) 4 end 5 end Stand and Present 1 class ProgressReportPresenter 2 delegate :students, :to => :classroom 3 delegate :year, :name, :to => :semester, :prefix => true 4 5 def initialize(params) 6 @classroom = Classroom.where(:classroom_location => params[:classroom_location], 7 :period => params[:period]).include(:students).first 8 @semester = Semester.where(:semester_number => params[:semester_number], 9 :year => params[:semester_year]).first 10 end 11 12 def get_cumulative_average_for_student(student) 13 student.cumulative_average(@semester) 14 end 15 16 def get_current_average_for_student(student) 17 student.current_average(@semester) 18 end 19 end
  58. View to a Kill 1 <div class="prefix_10 grid_2"> 2 <%=

    @progress_report.semester_name %> <%= @progress_report.semester_year %> 3 </div> 4 <div class="clear"></div> 5 <div class="prefix_1 suffix_1 grid_10"> 6 <table class="full_width"> 7 <thead> 8 <tr> 9 <th> 10 First Name 11 </th> 12 <th> 13 Last Name 14 </th> 15 <th> 16 Student ID 17 </th> 18 <th> 19 Current Average 20 </th> 21 <th> 22 Cumulative Average 23 </th> 24 </td> 25 </thead>
  59. View to a Kill 18 <th> 19 Current Average 20

    </th> 21 <th> 22 Cumulative Average 23 </th> 24 </td> 25 </thead> 26 <tbody> 27 <% @progress_report.students.each do |student| %> 28 <tr> 29 <td> 30 <%= student.first_name %> 31 </td> 32 <td> 33 <%= student.last_name %> 34 </td> 35 <td> 36 <%= student.student_id %> 37 </td> 38 <td> 39 <%= @progress_report.get_current_average_for_student(student) %> 40 </td> 41 <td> 42 <%= @progress_report.get_cumulative_average_for_student(student) %> 43 </td> 44 </tr> 45 <% end %> 46 </tbody> 47 </table> 48 </div> 49 <div class="clear"></div> 50
  60. View to a Kill 1 <div class="prefix_10 grid_2"> 2 <%=

    @progress_report.semester_name %> <%= @progress_report.semester_year %> 3 </div> 4 <div class="clear"></div> 5 <div class="prefix_1 suffix_1 grid_10"> 6 <%= render :partial => 'students', :collection => @progress_report.students %> 7 </div> 8 <div class="clear"></div>
  61. View to a Kill 1 <div class="prefix_10 grid_2"> 2 <%=

    @progress_report.semester_name %> <%= @progress_report.semester_year %> 3 </div> 4 <div class="clear"></div> 5 <div class="prefix_1 suffix_1 grid_10"> 6 <%= render :partial => 'students', :collection => @progress_report.students %> 7 </div> 8 <div class="clear"></div> 1 <div class="prefix_10 grid_2"> 2 <%= @progress_report.header %> 3 </div> 4 <div class="clear"></div> 5 <div class="prefix_1 suffix_1 grid_10"> 6 <%= render @progress_report.students %> 7 </div> 8 <div class="clear"></div>
  62. 1 class ExamHelper 2 def exam_info(exam) 3 str = "<div

    id=\"exam_#{exam.id}\">" + 4 link_to(exam) + 5 "</div>" 6 raw(str) 7 end 8 end Stringy Helpers
  63. Stringy Helpers 1 class ExamHelper 2 def exam_info(exam) 3 content_tag

    :div, :id => "exam_#{exam.id}" do 4 raw( 5 link_to(exam) 6 ) 7 end 8 end 9 end
  64. 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>GradeBook.com<%= "-

    #{@title}" unless @title.nil? %></title> 5 </head> 6 <body> 7 <%= yield %> 8 </body> 9 </html> Yielding for Content
  65. Yielding for Content 1 <!DOCTYPE html> 2 <html> 3 <head>

    4 <title>GradeBook.com<%= yield(:title) %></title> 5 </head> 6 <body> 7 <%= yield %> 8 </body> 9 </html> 1 <% content_for(:title) do %> 2 - Classes 3 <% end %> 4 5 <!-- ... -->
  66. 1 <%= @exam.name.titleize %> Give it the Good Ol’ College

    Try If name is nil then the call to the titleize method will throw an exception
  67. Give it the Good Ol’ College Try 1 <%= @exam.name.try(:titleize)

    || "Untitled Exam" %> Calling try will return a nil if the attribute is nil, otherwise it will call the method
  68. Winning at Rails • Controllers should be as small as

    possible • Move code into models or presenters to slim down controllers • Filters should perform an action, not set up instance variables
  69. Winning at Rails • Scopes can make ActiveRecord queries more

    readable and understandable • Single Table Inheritance allows you to use one table for multiple types that inherit from each other
  70. Winning at Rails • ActiveModel allows you to create your

    own models without ActiveRecord • You get validations and form_for conversions with just a couple mix-ins
  71. Winning at Rails • Cache counters allow you to use

    database counter cache columns that return with the size method • Only size uses the column
  72. Winning at Rails • Don’t implicitly trust params • Attackers

    can hijack those • Add attr_protected or attr_accessible to secure those properties • Whitelists are usually better (attr_accessible)
  73. Winning at Rails • Law of Demeter states that classes

    should not know about classes in a 2nd level has-a relationship • Delegates fix that
  74. Winning at Rails • The Presenter pattern gives you a

    way to compose view data without cluttering your controller or view with composition logic
  75. Winning at Rails • Don’t use string concatenation in your

    view helpers • content_tag gives you a nice block syntax instead • content_for and yield blocks let you substitute content blocks