learn by doing
Best Practices
Tuesday, May 17, 2011
Don't touch anything that doesn't have [good test] coverage. Otherwise, you're not refactoring; you're just changing shit.
3
When rst getting started with Rails, it is tempting to shove lots of logic in the view.
A better way is to move as much of the logic as possible into the controller.
If we push that code into the model, we... slim down the controller document the operation by naming the method
Router
View
Controller
Model
Database
10
11
12
13
Why Bother?
14
15
17
18
18
In Need of a Presenter
/app/controllers/reports_controller.rb
def show @student = Student.find params[:student_id] @term = Term.find params[:term_id] @report_type = ReportType.find params[:report_type_id] end
19
/app/views/reports/show.html.haml
%h1= "#{@student.last_name}, #{@student.first_name}" %h2= "#{@report_type.report_title} for #{@term.start_date} to #{@term.end_date}" - @student.courses.each do |course| - course_report = @student.report_data_for_course(course) = render :partial => "course_report", :locals => {:course_report => course_report}
20
20
20
20
20
20
21
/app/views/reports/show.html.haml
%h1= "#{@student.last_name}, #{@student.first_name}" %h2= "#{@report_type.report_title} for #{@term.start_date} to #{@term.end_date}" - @student.courses.each do |course| - course_report = @student.report_data_for_course(course) = render :partial => "course_report", :locals => {:course_report => course_report}
21
/app/views/reports/show.html.haml
%h1= "#{@student.last_name}, #{@student.first_name}" %h2= "#{@report_type.report_title} for #{@term.start_date} to #{@term.end_date}" - @student.courses.each do |course| - course_report = @student.report_data_for_course(course) = render :partial => "course_report", :locals => {:course_report => course_report}
21
/app/views/reports/show.html.haml
%h1= "#{@report.last_name}, #{@report.first_name}" %h2= "#{@report.report_title} for #{@report.start_date} to # {@report.end_date}" - @report.courses.each do |course| - course_report = @report.report_data_for_course(course) = render :partial => "course_report", :locals => {:course_report => course_report}
22
22
class StudentReport # ...initialize and term/course delegators delegate :first_name, :last_name, :courses, :to => :student
22
class StudentReport # ...initialize and term/course delegators delegate :first_name, :last_name, :courses, :to => :student def course_reports courses.collect do |c| @student.report_data_for_course(c) end end end
23
%h1= "#{@report.last_name}, #{@report.first_name}" %h2= "#{@report.report_title} for #{@report.start_date} to # {@report.end_date}" - @report.courses.each do |course| - course_report = @report.report_data_for_course(course) = render :partial => "course_report", :locals => {:course_report => course_report}
23
%h1= "#{@report.last_name}, #{@report.first_name}" %h2= "#{@report.report_title} for #{@report.start_date} to # {@report.end_date}" - @report.courses.each do |course| - course_report = @report.report_data_for_course(course) = render :partial => "course_report", :locals => {:course_report => course_report} = render :partial => 'course_report', :collection => @report.course_reports
23
%h1= "#{@report.last_name}, #{@report.first_name}" %h2= "#{@report.report_title} for #{@report.start_date} to # {@report.end_date}" = render :partial => 'course_report', :collection => @report.course_reports
23
%h1= "#{@report.last_name}, #{@report.first_name}" %h2= "#{@report.report_title} for #{@report.start_date} to # {@report.end_date}" = render @report.course_reports
Asks each element for its ClassName Renders the partial with that name and sets the local variable
24
24
class StudentReport # ...initialize and term/course delegators delegate :first_name, :last_name, :courses, :to => :student def course_reports courses.collect do |c| @student.report_data_for_course(c) end end
end
24
class StudentReport # ...initialize and term/course delegators delegate :first_name, :last_name, :courses, :to => :student def course_reports courses.collect do |c| @student.report_data_for_course(c) end end
end
24
class StudentReport # ...initialize and term/course delegators delegate :first_name, :last_name, :courses, :to => :student def course_reports courses.collect do |c| @student.report_data_for_course(c) end end def report_data_for_course(course) # Compute the report data end end
24
class StudentReport # ...initialize and term/course delegators delegate :first_name, :last_name, :courses, :to => :student def course_reports courses.collect do |c| report_data_for_course(c) end end def report_data_for_course(course) # Compute the report data end end
4. Canonical Data
/app/views/reports/show.html.haml
%h1= "#{@report.last_name}, #{@report.first_name}" %h2= "#{@report.report_title} for #{@report.start_date} to #{@report.end_date}" = render @report.course_reports
25
/app/presenters/student_report.rb
class StudentReport # ...initialize, delegates, data calculators
@report.name
end
4. Canonical Data
/app/views/reports/show.html.haml
%h1= "#{@report.last_name}, #{@report.first_name}" %h2= "#{@report.report_title} for #{@report.start_date} to #{@report.end_date}" = render @report.course_reports
25
/app/presenters/student_report.rb
class StudentReport # ...initialize, delegates, data calculators def student_name [last_name, first_name].join(", ") end @report.name
end
4. Canonical Data
/app/views/reports/show.html.haml
%h1= @report.student_name %h2= "#{@report.report_title} for #{@report.start_date} to #{@report.end_date}" = render @report.course_reports
25
/app/presenters/student_report.rb
class StudentReport # ...initialize, delegates, data calculators def student_name [last_name, first_name].join(", ") end @report.name
end
4. Canonical Data
/app/views/reports/show.html.haml
%h1= @report.student_name %h2= "#{@report.report_title} for #{@report.start_date} to #{@report.end_date}" = render @report.course_reports
25
/app/presenters/student_report.rb
class StudentReport # ...initialize, delegates, data calculators def student_name [last_name, first_name].join(", ") end @report.name def title "#{report_title} for #{start_date} to #{end_date}" end end
4. Canonical Data
/app/views/reports/show.html.haml
%h1= @report.student_name %h2= @report.title = render @report.course_reports
25
/app/presenters/student_report.rb
class StudentReport # ...initialize, delegates, data calculators Or maybe a decorator? def student_name [last_name, first_name].join(", ") end @report.name def title "#{report_title} for #{start_date} to #{end_date}" end end
26
26
26
/app/controllers/reports_controller.rb
def show @report = StudentReport.new(params) end
5. End State
27
5. End State
/app/presenters/student_report.rb ( rst half)
27
class StudentReport delegate :start_date, :end_date, :to => :term delegate :first_name, :last_name, :courses, :to => :student delegate :report_title, :to => :report_type def initialize(params) @student = Student.find params[:student_id] @term = Term.find params[:term_id] @report_type = ReportType.find params[:report_type_id] end def student_name [last_name, first_name].join(", ") end #...continued
5. End State
/app/presenters/student_report.rb (second half)
28
5. End State
/app/presenters/student_report.rb (second half)
#...continued def title "#{report_title} for #{start_date} to #{end_date}" end def course_reports courses.collect do |c| report_data_for_course(c) end end private def report_data_for_course(course) # Compute the report data end end
28
Afterthoughts
29
Afterthoughts
Did you just obliterate MVC?
29
Afterthoughts
Did you just obliterate MVC? Why not just use helpers?
29
Afterthoughts
Did you just obliterate MVC? Why not just use helpers? They suck
29
Afterthoughts
Did you just obliterate MVC? Why not just use helpers? They suck Objects are not scary
29
Afterthoughts
Did you just obliterate MVC? Why not just use helpers? They suck Objects are not scary Using Rails helpers is ok!
Tuesday, May 17, 2011
29
31
31
33
33
33
34
34
35
35
35
class Student < ActiveRecord::Base def self.all_by(param) case param when "last_name" then order(:last_name) when "first_name" then order(:first_name) else order(:id) end end Thinking about scope? end
Give me a minute.
36
36
37
37
37
38
38
38
39
Every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
39
40
40
SRP Cleanup
Tuesday, May 17, 2011
40
SRP Cleanup
Tuesday, May 17, 2011
40
self.lunch_balance = LunchTracker::STARTING_BALANCE
SRP Cleanup
Tuesday, May 17, 2011
40
SRP Cleanup
Tuesday, May 17, 2011
41
41
More Repetition
Tuesday, May 17, 2011
41
More Repetition
Tuesday, May 17, 2011
41
More Repetition
Tuesday, May 17, 2011
41
More Repetition
Tuesday, May 17, 2011
42
42
42
43
Methods that do too much Symptom: Your method is longer than 8 lines Solution: refactor with extract method
43
Methods that do too much Symptom: Your method is longer than 8 lines Solution: refactor with extract method
43
Methods located in the wrong object Symptom: reaching too deeply into child objects / Law of Demeter Solution: refactor with move method
Methods that do too much Symptom: Your method is longer than 8 lines Solution: refactor with extract method
43
Methods located in the wrong object Symptom: reaching too deeply into child objects / Law of Demeter Solution: refactor with move method Magic Numbers, copy text, and conguration data Solution: introduce constants or a cong object
45
46
46
A Gotcha Scenario
47
A Gotcha Scenario
Frank creates a migration adding the inactive eld to Student Frank writes a scope like this:
scope :active, where(:inactive => false)
47
Frank checks in his code, woohoo! Lucy checks out Franks code Lucy runs rake db:migrate
A Gotcha Scenario
Frank creates a migration adding the inactive eld to Student Frank writes a scope like this:
scope :active, where(:inactive => false)
47
Frank checks in his code, woohoo! Lucy checks out Franks code Lucy runs rake db:migrate
d in xe
!!! sh ra
2 3.0.
48
48
Lambda ZOMG!
Tuesday, May 17, 2011
49
50
50
50
51
51
Wrap Up
52
Wrap Up
Use presenters as an abstraction layer between views and models DRY isnt about copy & paste, its about knowledge Forget scopes, you dont need them! Embrace Ruby
52
learn by doing
53
54
54
55
55
55
55
55
Method Naming
56
Method Naming
56
Names matter Express meaning, imply the return type and quantity
Method Naming
56
Names matter Express meaning, imply the return type and quantity Follow Ruby naming conventions Solutions: if it changes the data, end in ! if it returns a boolean, end in ?
Method Naming
56
Names matter Express meaning, imply the return type and quantity Follow Ruby naming conventions Solutions: if it changes the data, end in ! if it returns a boolean, end in ? Physically organize methods within a model...
57
57
def to_s # Instance Method 1 end def <=>(other) # Instance Method 2 end end