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.

76aa5b311fb40fdc2bb70c0282d66af3?s=128

Luca Guidi

March 22, 2013
Tweet

Transcript

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

    learned to stop worrying about the Golden Path
  2. AGENDA intro

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

  4. litmus Beautiful Email previews Campaign analytics Spam filter tests HTML

    code analysis And many other..
  5. Why Rails has revolutionized web development? (IMHO)

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

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

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

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

  10. ..but The Framework is almost ten years old A Lot

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

    of Legacy Code is around Upgrades to a major release are *painful*
  12. AGENDA intro problems

  13. Active Record Active Record

  14. Models != Records Active Record

  15. 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...
  16. 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...
  17. 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...
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. Callbacks abuse Active Record Non-persistence logic is tight to the

    persistence life cycle. Eg. Sending emails
  26. Testability issues Active Record Micheal Feathers

  27. Testability issues Active Record A test is not a unit

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

    architecture, but it has strange OOP design.
  29. 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 !&  !! !& "! !&  !%! #!  "  %"!
  30. 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 !&  !! !& "! !&  !%! #!  "  %"!
  31. 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 !&  !! !& "! !&  !%! #!  "  %"!
  32. 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 !&  !! !& "! !&  !%! #!  "  %"!
  33. 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 !&  !! !& "! !&  !%! #!  "  %"!
  34. 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 !&  !! !& "! !&  !%! #!  "  %"!
  35. 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 $!! ! "&! " %"&!!%  !!!! $!!! %
  36. 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 $!! ! "&! " %"&!!%  !!!! $!!! %
  37. 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 $!! ! "&! " %"&!!%  !!!! $!!! %
  38. 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 $!! ! "&! " %"&!!%  !!!! $!!! %
  39. 1 class PostsController < ApplicationController 2 # ... 3 4

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

    def create 5 @post = Post.new(params[:post]) 6 # ... 7 end 8 end Encapsulation violations Action Controller $!! !  #$!! ! #$ 
  41. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  42. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  43. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  44. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  45. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  46. 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   !!! ! &!!! % $ !"!! !!!   !!' ! ( ! !!!!
  47. Action View Action View

  48. 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.
  49. 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.
  50. 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.
  51. Ruby on Rails Ruby on Rails

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

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

  54. AGENDA intro problems solutions

  55. Decouple your logic from ActiveRecord as much as possible. Solutions

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

    forget about associations, scopes, validations..
  57. Let your public API to declare intents. Solutions Let your

    design to emerge via TDD.
  58. Keep methods and accessors private as much as possible. Solutions

    Public APIs are hard to maintain as the codebase and the team grows.
  59. Skinny controllers and skinny models. Solutions Use service objects or

    DCI, they are easier and faster to test.
  60. 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.
  61. Use DIY presenters Solutions They will help you to keep

    your models clean from presentational logic.
  62. Refactor, refactor, refactor. Solutions

  63. AGENDA intro problems solutions conclusion

  64. Q&A

  65. me@lucaguidi.com @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/
  66.