Slide 1

Slide 1 text

Luca Guidi March 22nd, 2013 A Rails Criticism how i learned to stop worrying about the Golden Path

Slide 2

Slide 2 text

AGENDA intro

Slide 3

Slide 3 text

Luca Guidi Senior Developer at Litmus @jodosha - http//lucaguidi.com

Slide 4

Slide 4 text

litmus Beautiful Email previews Campaign analytics Spam filter tests HTML code analysis And many other..

Slide 5

Slide 5 text

Why Rails has revolutionized web development? (IMHO)

Slide 6

Slide 6 text

Why Rails has revolutionized web development? (IMHO) Convention Over Configuration :)

Slide 7

Slide 7 text

Why Rails has revolutionized web development? (IMHO) Convention Over Configuration Dynamic and innovative ecosystem :)

Slide 8

Slide 8 text

Why Rails has revolutionized web development? (IMHO) Convention Over Configuration Dynamic and innovative ecosystem Productivity and Developer Happiness :)

Slide 9

Slide 9 text

..but The Framework is almost ten years old

Slide 10

Slide 10 text

..but The Framework is almost ten years old A Lot of Legacy Code is around

Slide 11

Slide 11 text

..but The Framework is almost ten years old A Lot of Legacy Code is around Upgrades to a major release are *painful*

Slide 12

Slide 12 text

AGENDA intro problems

Slide 13

Slide 13 text

Active Record Active Record

Slide 14

Slide 14 text

Models != Records Active Record

Slide 15

Slide 15 text

Encapsulation violations Active Record 1 if article.state != 'published' 2 article.update_attribute(state: 'published') 3 end 1 unless article.published? 2 article.publish! 3 end 4 # not completely right...

Slide 16

Slide 16 text

Encapsulation violations Active Record 1 if article.state != 'published' 2 article.update_attribute(state: 'published') 3 end 1 unless article.published? 2 article.publish! 3 end 4 # not completely right...

Slide 17

Slide 17 text

Encapsulation violations Active Record 1 if article.state != 'published' 2 article.update_attribute(state: 'published') 3 end 1 unless article.published? 2 article.publish! 3 end 4 # not completely right...

Slide 18

Slide 18 text

Tell, Don’t Ask violations Active Record 1 unless article.published? 2 article.publish! 3 end 4 # not completely right... 1 article.publish! 2 # push the implementation 3 # into the model

Slide 19

Slide 19 text

Tell, Don’t Ask violations Active Record 1 unless article.published? 2 article.publish! 3 end 4 # not completely right... 1 article.publish! 2 # push the implementation 3 # into the model

Slide 20

Slide 20 text

Tell, Don’t Ask violations Active Record 1 unless article.published? 2 article.publish! 3 end 4 # not completely right... 1 article.publish! 2 # push the implementation 3 # into the model

Slide 21

Slide 21 text

Implicit vs Explicit API Active Record 1 Post.where(state:'published'). 2 order('created_at DESC'). 3 limit(5) 1 Post.most_recent_published 1 class Post < ActiveRecord::Base 2 def self.most_recent_published(limit = 5) 3 published.recent(limit).order('created_at DESC') 4 end 5 6 private 7 scope :published, ->() { where(state: 'published') } 8 scope :recent, ->(n) { limit(n) } 9 end

Slide 22

Slide 22 text

Implicit vs Explicit API Active Record 1 Post.where(state:'published'). 2 order('created_at DESC'). 3 limit(5) 1 Post.most_recent_published 1 class Post < ActiveRecord::Base 2 def self.most_recent_published(limit = 5) 3 published.recent(limit).order('created_at DESC') 4 end 5 6 private 7 scope :published, ->() { where(state: 'published') } 8 scope :recent, ->(n) { limit(n) } 9 end

Slide 23

Slide 23 text

Implicit vs Explicit API Active Record 1 Post.where(state:'published'). 2 order('created_at DESC'). 3 limit(5) 1 Post.most_recent_published 1 class Post < ActiveRecord::Base 2 def self.most_recent_published(limit = 5) 3 published.recent(limit).order('created_at DESC') 4 end 5 6 private 7 scope :published, ->() { where(state: 'published') } 8 scope :recent, ->(n) { limit(n) } 9 end

Slide 24

Slide 24 text

Implicit vs Explicit API Active Record 1 Post.where(state:'published'). 2 order('created_at DESC'). 3 limit(5) 1 Post.most_recent_published 1 class Post < ActiveRecord::Base 2 def self.most_recent_published(limit = 5) 3 published.recent(limit).order('created_at DESC') 4 end 5 6 private 7 scope :published, ->() { where(state: 'published') } 8 scope :recent, ->(n) { limit(n) } 9 end

Slide 25

Slide 25 text

Callbacks abuse Active Record Non-persistence logic is tight to the persistence life cycle. Eg. Sending emails

Slide 26

Slide 26 text

Testability issues Active Record Micheal Feathers

Slide 27

Slide 27 text

Testability issues Active Record A test is not a unit test if it talks to a database. Micheal Feathers

Slide 28

Slide 28 text

Action Controller Action Controller It doesn’t affect too much your architecture, but it has strange OOP design.

Slide 29

Slide 29 text

Frankenstein Controllers Action Controller 1 class PostsController < ApplicationController 2 before_filter :authenticate 3 4 def new 5 end 6 7 def create 8 @post = Post.new(params[:post]) 9 10 if @post.save 11 redirect_to post_url(@post), notice: 'Yay!' 12 else 13 render :new 14 end 15 end 16 end !& !! !& "! !& !%! #! " %"!

Slide 30

Slide 30 text

Frankenstein Controllers Action Controller 1 class PostsController < ApplicationController 2 before_filter :authenticate 3 4 def new 5 end 6 7 def create 8 @post = Post.new(params[:post]) 9 10 if @post.save 11 redirect_to post_url(@post), notice: 'Yay!' 12 else 13 render :new 14 end 15 end 16 end !& !! !& "! !& !%! #! " %"!

Slide 31

Slide 31 text

Frankenstein Controllers Action Controller 1 class PostsController < ApplicationController 2 before_filter :authenticate 3 4 def new 5 end 6 7 def create 8 @post = Post.new(params[:post]) 9 10 if @post.save 11 redirect_to post_url(@post), notice: 'Yay!' 12 else 13 render :new 14 end 15 end 16 end !& !! !& "! !& !%! #! " %"!

Slide 32

Slide 32 text

Frankenstein Controllers Action Controller 1 class PostsController < ApplicationController 2 before_filter :authenticate 3 4 def new 5 end 6 7 def create 8 @post = Post.new(params[:post]) 9 10 if @post.save 11 redirect_to post_url(@post), notice: 'Yay!' 12 else 13 render :new 14 end 15 end 16 end !& !! !& "! !& !%! #! " %"!

Slide 33

Slide 33 text

Frankenstein Controllers Action Controller 1 class PostsController < ApplicationController 2 before_filter :authenticate 3 4 def new 5 end 6 7 def create 8 @post = Post.new(params[:post]) 9 10 if @post.save 11 redirect_to post_url(@post), notice: 'Yay!' 12 else 13 render :new 14 end 15 end 16 end !& !! !& "! !& !%! #! " %"!

Slide 34

Slide 34 text

Frankenstein Controllers Action Controller 1 class PostsController < ApplicationController 2 before_filter :authenticate 3 4 def new 5 end 6 7 def create 8 @post = Post.new(params[:post]) 9 10 if @post.save 11 redirect_to post_url(@post), notice: 'Yay!' 12 else 13 render :new 14 end 15 end 16 end !& !! !& "! !& !%! #! " %"!

Slide 35

Slide 35 text

1 class PostsController < ApplicationController 2 # ... 3 4 def create 5 @post = Post.new(params[:post]) 6 7 if @post.save 8 # ... 9 else 10 # ... 11 end 12 end 13 end Odd classes Action Controller $!! ! "&! " %"&!!% !!!! $!!! %

Slide 36

Slide 36 text

1 class PostsController < ApplicationController 2 # ... 3 4 def create 5 @post = Post.new(params[:post]) 6 7 if @post.save 8 # ... 9 else 10 # ... 11 end 12 end 13 end Odd classes Action Controller $!! ! "&! " %"&!!% !!!! $!!! %

Slide 37

Slide 37 text

1 class PostsController < ApplicationController 2 # ... 3 4 def create 5 @post = Post.new(params[:post]) 6 7 if @post.save 8 # ... 9 else 10 # ... 11 end 12 end 13 end Odd classes Action Controller $!! ! "&! " %"&!!% !!!! $!!! %

Slide 38

Slide 38 text

1 class PostsController < ApplicationController 2 # ... 3 4 def create 5 @post = Post.new(params[:post]) 6 7 if @post.save 8 # ... 9 else 10 # ... 11 end 12 end 13 end Odd classes Action Controller $!! ! "&! " %"&!!% !!!! $!!! %

Slide 39

Slide 39 text

1 class PostsController < ApplicationController 2 # ... 3 4 def create 5 @post = Post.new(params[:post]) 6 # ... 7 end 8 end Encapsulation violations Action Controller $!! ! #$!! ! #$

Slide 40

Slide 40 text

1 class PostsController < ApplicationController 2 # ... 3 4 def create 5 @post = Post.new(params[:post]) 6 # ... 7 end 8 end Encapsulation violations Action Controller $!! ! #$!! ! #$

Slide 41

Slide 41 text

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end Testability issues Action Controller !!! ! &!!! % $ !"!! !!! !!' ! ( ! !!!!

Slide 42

Slide 42 text

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end Testability issues Action Controller !!! ! &!!! % $ !"!! !!! !!' ! ( ! !!!!

Slide 43

Slide 43 text

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end Testability issues Action Controller !!! ! &!!! % $ !"!! !!! !!' ! ( ! !!!!

Slide 44

Slide 44 text

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end Testability issues Action Controller !!! ! &!!! % $ !"!! !!! !!' ! ( ! !!!!

Slide 45

Slide 45 text

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end Testability issues Action Controller !!! ! &!!! % $ !"!! !!! !!' ! ( ! !!!!

Slide 46

Slide 46 text

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end Testability issues Action Controller !!! ! &!!! % $ !"!! !!! !!' ! ( ! !!!!

Slide 47

Slide 47 text

Action View Action View

Slide 48

Slide 48 text

Views aren’t views Action View (but templates with logic) Without “real” views (or presenters), we’re tempted to push presentational methods into the models. In an ideal world we shouldn’t test our templates.

Slide 49

Slide 49 text

Helpers are functional programming Action View 1 def user_full_name(user) 2 [ user.first_name, user.last_name ].join(' ') 3 end 4 5 url_for 6 # vs 7 Url.for 8 9 posts_url Half-assed way to solve presentational problems in ActionView.

Slide 50

Slide 50 text

Helpers are functional programming Action View 1 def user_full_name(user) 2 [ user.first_name, user.last_name ].join(' ') 3 end 4 5 url_for 6 # vs 7 Url.for 8 9 posts_url Half-assed way to solve presentational problems in ActionView.

Slide 51

Slide 51 text

Ruby on Rails Ruby on Rails

Slide 52

Slide 52 text

Rails isn’t a framework, but an application template Ruby on Rails

Slide 53

Slide 53 text

Rails is multi-paradigm as Ruby is. Ruby on Rails

Slide 54

Slide 54 text

AGENDA intro problems solutions

Slide 55

Slide 55 text

Decouple your logic from ActiveRecord as much as possible. Solutions AR is focused on data, but OOP is about behavior.

Slide 56

Slide 56 text

Don’t think in ActiveRecord terms. Solutions Database is a detail, forget about associations, scopes, validations..

Slide 57

Slide 57 text

Let your public API to declare intents. Solutions Let your design to emerge via TDD.

Slide 58

Slide 58 text

Keep methods and accessors private as much as possible. Solutions Public APIs are hard to maintain as the codebase and the team grows.

Slide 59

Slide 59 text

Skinny controllers and skinny models. Solutions Use service objects or DCI, they are easier and faster to test.

Slide 60

Slide 60 text

Don’t be afraid to extract ad-hoc classes for specific responsibilities. Solutions Inner classes are your friends, they help you with details, and aren’t part of your public API.

Slide 61

Slide 61 text

Use DIY presenters Solutions They will help you to keep your models clean from presentational logic.

Slide 62

Slide 62 text

Refactor, refactor, refactor. Solutions

Slide 63

Slide 63 text

AGENDA intro problems solutions conclusion

Slide 64

Slide 64 text

Q&A

Slide 65

Slide 65 text

[email protected] @jodosha https://speakerdeck.com/jodosha/a-rails-criticism Except where otherwise noted, this work is licensed under: http://creativecommons.org/licenses/by-nc-sa/3.0/

Slide 66

Slide 66 text