Anda di halaman 1dari 139

Fat Models Arent Enough

Jeff Casimir / @j3

learn by doing

Tuesday, May 17, 2011

Best Practices
Tuesday, May 17, 2011

Refactoring by Hamlet DArcy


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

Refactoring by Hamlet DArcy


Tuesday, May 17, 2011

Question, Object, & Suggest


Tuesday, May 17, 2011

Tuesday, May 17, 2011

Skinny Controllers, Fat Models


Jamis Buck, October 2006

Tuesday, May 17, 2011

Tuesday, May 17, 2011

When rst getting started with Rails, it is tempting to shove lots of logic in the view.

Tuesday, May 17, 2011

Tuesday, May 17, 2011

A better way is to move as much of the logic as possible into the controller.

Tuesday, May 17, 2011

Tuesday, May 17, 2011

If we push that code into the model, we... slim down the controller document the operation by naming the method

Tuesday, May 17, 2011

The Rails Stack

Tuesday, May 17, 2011

The Rails Stack


Request Response

Router

View

Controller

Model

Database

Tuesday, May 17, 2011

10

Beautiful Views are HTML


Tuesday, May 17, 2011

11

Beautiful Actions are 8 simple lines


Tuesday, May 17, 2011

12

Beautiful Models are Organized


Tuesday, May 17, 2011

13

Fat Models Arent Enough


Tuesday, May 17, 2011

Why Bother?

14

Tuesday, May 17, 2011

15

Ideas for discussion


Tuesday, May 17, 2011

Presenter Pattern Better Rails through Better Ruby


15

Ideas for discussion


Tuesday, May 17, 2011

The Presenter Pattern

Tuesday, May 17, 2011

17

Presenter Pattern, Formally


Tuesday, May 17, 2011

Adaptation of the Decorator Pattern Wrapper that adds non-mutator methods


17

Presenter Pattern, Formally


Tuesday, May 17, 2011

18

Presenter Pattern, As I See It


Tuesday, May 17, 2011

Object-based shepherd of presentation Understands the mixing of one or more objects

18

Presenter Pattern, As I See It


Tuesday, May 17, 2011

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}

Tuesday, May 17, 2011

1a. Build Presenter


/app/presenters/student_report.rb

20

Tuesday, May 17, 2011

1a. Build Presenter


/app/presenters/student_report.rb
class StudentReport attr_reader :student, :term, :report_type

20

Tuesday, May 17, 2011

1a. Build Presenter


/app/presenters/student_report.rb
class StudentReport attr_reader :student, :term, :report_type delegate :start_date, :end_date, :to => :term delegate :first_name, :last_name, :courses, :report_data_for_course, :to => :student delegate :report_title, :to => :report_type

20

Tuesday, May 17, 2011

1a. Build Presenter


/app/presenters/student_report.rb
class StudentReport attr_reader :student, :term, :report_type delegate :start_date, :end_date, :to => :term delegate :first_name, :last_name, :courses, :report_data_for_course, :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 end

20

Tuesday, May 17, 2011

1a. Build Presenter


/app/presenters/student_report.rb
class StudentReport attr_reader :student, :term, :report_type delegate :start_date, :end_date, :to => :term delegate :first_name, :last_name, :courses, :report_data_for_course, :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 end

20

Tuesday, May 17, 2011

1a. Build Presenter


/app/presenters/student_report.rb
class StudentReport delegate :start_date, :end_date, :to => :term delegate :first_name, :last_name, :courses, :report_data_for_course, :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 end

20

Tuesday, May 17, 2011

1b. Use the 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

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}

Tuesday, May 17, 2011

1b. Use the Presenter


/app/controllers/reports_controller.rb
def show @report = StudentReport.new(params) end

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}

Tuesday, May 17, 2011

1b. Use the Presenter


/app/controllers/reports_controller.rb
def show @report = StudentReport.new(params) end

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}

Tuesday, May 17, 2011

2b. Presenter as Engine


/app/presenters/student_report.rb

22

Tuesday, May 17, 2011

2b. Presenter as Engine


/app/presenters/student_report.rb

22

class StudentReport # ...initialize and term/course delegators delegate :first_name, :last_name, :courses, :to => :student

Tuesday, May 17, 2011

2b. Presenter as Engine


/app/presenters/student_report.rb

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

Tuesday, May 17, 2011

2b. Defer to Engine


/app/views/reports/show.html.haml

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}

Tuesday, May 17, 2011

2b. Defer to Engine


/app/views/reports/show.html.haml

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

Tuesday, May 17, 2011

2b. Defer to Engine


/app/views/reports/show.html.haml

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

Tuesday, May 17, 2011

2b. Defer to Engine


/app/views/reports/show.html.haml

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

Tuesday, May 17, 2011

3a. Compute in Engine


/app/presenters/student_report.rb

24

Tuesday, May 17, 2011

3a. Compute in Engine


/app/presenters/student_report.rb

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

Tuesday, May 17, 2011

3a. Compute in Engine


/app/presenters/student_report.rb

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

Tuesday, May 17, 2011

3a. Compute in Engine


/app/presenters/student_report.rb

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

Tuesday, May 17, 2011

3a. Compute in Engine


/app/presenters/student_report.rb

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

Tuesday, May 17, 2011

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

Tuesday, May 17, 2011

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

Tuesday, May 17, 2011

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

Tuesday, May 17, 2011

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

Tuesday, May 17, 2011

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

Tuesday, May 17, 2011

5. End State Review

26

Tuesday, May 17, 2011

5. End State Review


/app/views/reports/show.html.haml
%h1= @report.student_name %h2= @report.title = render @report.course_reports

26

Tuesday, May 17, 2011

5. End State Review


/app/views/reports/show.html.haml
%h1= @report.student_name %h2= @report.title = render @report.course_reports

26

/app/controllers/reports_controller.rb
def show @report = StudentReport.new(params) end

Tuesday, May 17, 2011

5. End State

27

Tuesday, May 17, 2011

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

Tuesday, May 17, 2011

5. End State
/app/presenters/student_report.rb (second half)

28

Tuesday, May 17, 2011

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

Tuesday, May 17, 2011

Afterthoughts

29

Tuesday, May 17, 2011

Afterthoughts
Did you just obliterate MVC?

29

Tuesday, May 17, 2011

Afterthoughts
Did you just obliterate MVC? Why not just use helpers?

29

Tuesday, May 17, 2011

Afterthoughts
Did you just obliterate MVC? Why not just use helpers? They suck

29

Tuesday, May 17, 2011

Afterthoughts
Did you just obliterate MVC? Why not just use helpers? They suck Objects are not scary

29

Tuesday, May 17, 2011

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

Better Rails through Better Ruby

Tuesday, May 17, 2011

31

Tuesday, May 17, 2011

A signicant percentage of Rails programmers are not Ruby programmers.


(my theory)

31

Tuesday, May 17, 2011

Dont Repeat Yourself (DRY)

Tuesday, May 17, 2011

Dont Repeat Yourself (DRY)


Dont copy and paste code. Dave Thomas & Andy Hunt
Tuesday, May 17, 2011

Dont Repeat Yourself (DRY)


Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. Dave Thomas & Andy Hunt
Tuesday, May 17, 2011

Real-World DRY: Sort

33

Tuesday, May 17, 2011

Real-World DRY: Sort


class StudentsController < ApplicationController def index case params[:sort_by] when "last_name" then @students = Student.all.order(:last_name) when "first_name" then @students = Student.all.order(:first_name) else @students = Student.all end end end

33

Typical Param-Handling Action


Tuesday, May 17, 2011

Real-World DRY: Sort


class StudentsController < ApplicationController def index case params[:sort_by] when "last_name" then @students = Student.all.order(:last_name) when "first_name" then @students = Student.all.order(:first_name) else @students = Student.all end end end

33

Typical Param-Handling Action


Tuesday, May 17, 2011

Real-World DRY: Sort

34

Tuesday, May 17, 2011

Real-World DRY: Sort


class StudentsController < ApplicationController def index @students = case params[:sort_by] when "last_name" then Student.all.order(:last_name) when "first_name" then Student.all.order(:first_name) else Student.all end end end

34

Simple DRY Improvement


Tuesday, May 17, 2011

Real-World DRY: Sort

35

Tuesday, May 17, 2011

Real-World DRY: Sort


class StudentsController < ApplicationController def index @students = Student.all_by(params[:sort_by]) end end

35

Tuesday, May 17, 2011

Real-World DRY: Sort


class StudentsController < ApplicationController def index @students = Student.all_by(params[:sort_by]) end end

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

Real DRY Progress


Tuesday, May 17, 2011

Give me a minute.

Real-World DRY: Setters

36

Tuesday, May 17, 2011

Real-World DRY: Setters


class StudentsController < ApplicationController def create @student = Student.new(params[:student]) @student.created_by = current_user @student.lunch_id = LunchTracker.new @student.lunch_balance = 0 # redirects, etc end end

36

Action Setting Attributes


Tuesday, May 17, 2011

Real-World DRY: Setters

37

Tuesday, May 17, 2011

Real-World DRY: Setters


class StudentsController < ApplicationController def create @student = Student.new(params, current_user) # redirects, etc end end

37

Tuesday, May 17, 2011

Real-World DRY: Setters


class StudentsController < ApplicationController def create @student = Student.new(params, current_user) # redirects, etc end end class Student < ActiveRecord::Base def initialize(params, creator) self.created_by = creator self.lunch_id = LunchTracker.new self.lunch_balance = LunchTracker::STARTING_BALANCE super end end

37

Trying to Encapsulate Creation


Tuesday, May 17, 2011

Real-World DRY: Setters

38

Tuesday, May 17, 2011

Real-World DRY: Setters


class StudentsController < ApplicationController def create @student = current_user.students.new(params) # redirects, etc end end

38

Build Through the Relationship


Tuesday, May 17, 2011

Real-World DRY: Setters


class StudentsController < ApplicationController def create @student = current_user.students.new(params) # redirects, etc end end class Student < ActiveRecord::Base def initialize(params = nil) self.lunch_id = LunchTracker.new self.lunch_balance = LunchTracker::STARTING_BALANCE super end end

38

Build Through the Relationship


Tuesday, May 17, 2011

39

Single Responsibility Principle


Tuesday, May 17, 2011

Every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.

39

Single Responsibility Principle


Tuesday, May 17, 2011

Real-World DRY: Setters

40

Tuesday, May 17, 2011

Real-World DRY: Setters


class Student < ActiveRecord::Base def initialize(params = nil) self.lunch_id = LunchTracker.new self.lunch_balance = LunchTracker::STARTING_BALANCE super end end

40

SRP Cleanup
Tuesday, May 17, 2011

Real-World DRY: Setters


class Student < ActiveRecord::Base def initialize(params = nil) self.lunch_id = LunchTracker.new self.lunch_balance = LunchTracker::STARTING_BALANCE super end end

40

SRP Cleanup
Tuesday, May 17, 2011

Real-World DRY: Setters


class Student < ActiveRecord::Base def initialize(params = nil) self.lunch_id = LunchTracker.new super end end

40

self.lunch_balance = LunchTracker::STARTING_BALANCE

SRP Cleanup
Tuesday, May 17, 2011

Real-World DRY: Setters


class Student < ActiveRecord::Base def initialize(params = nil) self.lunch_id = LunchTracker.new super end end

40

SRP Cleanup
Tuesday, May 17, 2011

Real-World DRY: Setters

41

Tuesday, May 17, 2011

Real-World DRY: Setters


class Student < ActiveRecord::Base def initialize(params=nil) self.lunch_id = LunchTracker.new super end end

41

More Repetition
Tuesday, May 17, 2011

Real-World DRY: Setters


class Student < ActiveRecord::Base has_one :lunch, :class_name => 'LunchTracker' def initialize(params=nil) self.lunch_id = LunchTracker.new super end end

41

More Repetition
Tuesday, May 17, 2011

Real-World DRY: Setters


class Student < ActiveRecord::Base has_one :lunch, :class_name => 'LunchTracker' def initialize(params=nil) self.lunch_id = LunchTracker.new super end end

41

More Repetition
Tuesday, May 17, 2011

Real-World DRY: Setters


class Student < ActiveRecord::Base has_one :lunch, :class_name => 'LunchTracker' def initialize(params=nil) self.build_lunch super end end

41

More Repetition
Tuesday, May 17, 2011

Real-World DRY: Setters

42

Tuesday, May 17, 2011

Real-World DRY: Setters


class StudentsController < ApplicationController def create @student = Student.new(params[:student]) @student.created_by = current_user @student.lunch_id = LunchTracker.new @student.lunch_balance = 0 # redirects, etc end end

42

Start / End Review


Tuesday, May 17, 2011

Real-World DRY: Setters


class StudentsController < ApplicationController def create @student = current_user.students.new(params) # redirects, etc end end class Student < ActiveRecord::Base has_one :lunch, :class_name => 'LunchTracker' def initialize(params = nil) self.build_lunch super end end

42

Start / End Review


Tuesday, May 17, 2011

Signs of SRP Violations

43

Tuesday, May 17, 2011

Signs of SRP Violations

Methods that do too much Symptom: Your method is longer than 8 lines Solution: refactor with extract method

43

Tuesday, May 17, 2011

Signs of SRP Violations

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

Tuesday, May 17, 2011

Signs of SRP Violations

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

Tuesday, May 17, 2011

Scopes are Dead

Tuesday, May 17, 2011

Why We Loved Scopes

45

Tuesday, May 17, 2011

Simple Scope in Rails 3

46

Tuesday, May 17, 2011

Simple Scope in Rails 3


class Student < ActiveRecord::Base scope :active, where(:inactive => false) end

46

Quick and Easy


Tuesday, May 17, 2011

A Gotcha Scenario

47

Tuesday, May 17, 2011

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

Tuesday, May 17, 2011

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.

Tuesday, May 17, 2011

Scope with Parameters

48

Tuesday, May 17, 2011

Scope with Parameters


class Student < ActiveRecord::Base scope :starts_with, lambda { |fragment| where("last_name LIKE ?%", fragment) } end

48

Lambda ZOMG!
Tuesday, May 17, 2011

AREL and Lazy Evaluation

49

Tuesday, May 17, 2011

Scopes & Lambdas

50

Tuesday, May 17, 2011

Scopes & Lambdas

50

Tuesday, May 17, 2011

Scopes & Lambdas

50

Tuesday, May 17, 2011

We dont need scopes, just write good Ruby.

51

Tuesday, May 17, 2011

We dont need scopes, just write good Ruby.


class Student < ActiveRecord::Base def self.starts_with(fragment) where("last_name LIKE ?%", fragment) end end

51

Class Methods > Scopes


Tuesday, May 17, 2011

Wrap Up

52

Tuesday, May 17, 2011

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

Jeff Casimir @j3

Tuesday, May 17, 2011

53

Tuesday, May 17, 2011

54

Writing Better Instructions


Tuesday, May 17, 2011

Every instruction added is technical debt accumulated.

54

Writing Better Instructions


Tuesday, May 17, 2011

Less code is less debt

55

Tuesday, May 17, 2011

Less code is less debt

self is rarely necessary

55

Tuesday, May 17, 2011

Less code is less debt



self is rarely necessary return is rarely necessary

55

Tuesday, May 17, 2011

Less code is less debt



self is rarely necessary return is rarely necessary Master collection operations including: each, collect/map, select, detect, merge

55

Tuesday, May 17, 2011

Less code is less debt



self is rarely necessary return is rarely necessary Master collection operations including: each, collect/map, select, detect, merge Take advantage of return values from all statements, even if, case, while

55

Tuesday, May 17, 2011

Method Naming

56

Tuesday, May 17, 2011

Method Naming

56

Names matter Express meaning, imply the return type and quantity

Tuesday, May 17, 2011

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 ?

Tuesday, May 17, 2011

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...

Tuesday, May 17, 2011

Collecting Class Methods


class Student class << self def search(params) # Class Method 1 end def random # Class Method 2 end end def to_s # Instance Method 1 end def <=>(other) # Instance Method 2 end end

57

Tuesday, May 17, 2011

Collecting Class Methods


class Student class << self def search(params) # Class Method 1 end def random # Class Method 2 end end

57

def to_s # Instance Method 1 end def <=>(other) # Instance Method 2 end end

Dene class methods within a single subclass

Tuesday, May 17, 2011

Anda mungkin juga menyukai