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

A Rails Criticism

A Rails Criticism

A deep insight on why Ruby on Rails has revolutionized web development.

This talk will focus its attention on the Rails "Golden Path", the reasons to its success, the most common problems, and how its API can be improved.

We will learn to benefit from a tool as powerful as it is dangerous, how to mitigate architectural, design and testability implications for our applications and how to improve the quality of our code.

Luca Guidi

March 22, 2013
Tweet

More Decks by Luca Guidi

Other Decks in Programming

Transcript

  1. Luca Guidi March 22nd, 2013 A Rails Criticism how i

    learned to stop worrying about the Golden Path
  2. Why Rails has revolutionized web development? (IMHO) Convention Over Configuration

    Dynamic and innovative ecosystem Productivity and Developer Happiness :)
  3. ..but The Framework is almost ten years old A Lot

    of Legacy Code is around Upgrades to a major release are *painful*
  4. 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...
  5. 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...
  6. 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...
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. Callbacks abuse Active Record Non-persistence logic is tight to the

    persistence life cycle. Eg. Sending emails
  15. Testability issues Active Record A test is not a unit

    test if it talks to a database. Micheal Feathers
  16. Action Controller Action Controller It doesn’t affect too much your

    architecture, but it has strange OOP design.
  17. 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 !&  !! !& "! !&  !%! #!  "  %"!
  18. 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 !&  !! !& "! !&  !%! #!  "  %"!
  19. 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 !&  !! !& "! !&  !%! #!  "  %"!
  20. 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 !&  !! !& "! !&  !%! #!  "  %"!
  21. 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 !&  !! !& "! !&  !%! #!  "  %"!
  22. 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 !&  !! !& "! !&  !%! #!  "  %"!
  23. 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 $!! ! "&! " %"&!!%  !!!! $!!! %
  24. 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 $!! ! "&! " %"&!!%  !!!! $!!! %
  25. 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 $!! ! "&! " %"&!!%  !!!! $!!! %
  26. 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 $!! ! "&! " %"&!!%  !!!! $!!! %
  27. 1 class PostsController < ApplicationController 2 # ... 3 4

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

    def create 5 @post = Post.new(params[:post]) 6 # ... 7 end 8 end Encapsulation violations Action Controller $!! !  #$!! ! #$ 
  29. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  30. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  31. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  32. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  33. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  34. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  35. 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.
  36. 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.
  37. 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.
  38. Decouple your logic from ActiveRecord as much as possible. Solutions

    AR is focused on data, but OOP is about behavior.
  39. Don’t think in ActiveRecord terms. Solutions Database is a detail,

    forget about associations, scopes, validations..
  40. Keep methods and accessors private as much as possible. Solutions

    Public APIs are hard to maintain as the codebase and the team grows.
  41. 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.
  42. Use DIY presenters Solutions They will help you to keep

    your models clean from presentational logic.
  43. Q&A