Luca Guidi March 22nd, 2013 A Rails Criticism how i learned to stop worrying about the Golden Path
Sep 12, 2014
Luca Guidi
March 22nd, 2013
A Rails Criticismhow i learned to stop worrying about the Golden Path
AGENDA
intro
Luca GuidiSenior Developer at Litmus
@jodosha - http//lucaguidi.com
litmusBeautiful Email previews
Campaign analytics
Spam filter tests
HTML code analysis
And many other..
Why Railshas revolutionized web
development?(IMHO)
Why Railshas revolutionized web
development?
(IMHO)
Convention Over Configuration
:)
Why Railshas revolutionized web
development?
(IMHO)
Convention Over Configuration
Dynamic and innovativeecosystem
:)
Why Railshas revolutionized web
development?
(IMHO)
Convention Over Configuration
Dynamic and innovativeecosystem
Productivity andDeveloper Happiness
:)
..but
The Framework is almost ten years old
..but
The Framework is almost ten years old
A Lot of Legacy Codeis around
..but
The Framework is almost ten years old
A Lot of Legacy Codeis around
Upgrades to a major release are *painful*
AGENDA
intro problems
ActiveRecord
Active
Record
Models != Records
Active
Record
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 end4 # not completely right...
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 end4 # not completely right...
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 end4 # not completely right...
Tell, Don’t Askviolations
Active
Record
1 unless article.published?2 article.publish!3 end4 # not completely right...
1 article.publish!2 # push the implementation3 # into the model
Tell, Don’t Askviolations
Active
Record
1 unless article.published?2 article.publish!3 end4 # not completely right...
1 article.publish!2 # push the implementation3 # into the model
Tell, Don’t Askviolations
Active
Record
1 unless article.published?2 article.publish!3 end4 # not completely right...
1 article.publish!2 # push the implementation3 # into the model
Implicit vs Explicit API
Active
Record
1 Post.where(state:'published').2 order('created_at DESC').3 limit(5)
1 Post.most_recent_published1 class Post < ActiveRecord::Base2 def self.most_recent_published(limit = 5)3 published.recent(limit).order('created_at DESC')4 end5 6 private7 scope :published, ->() { where(state: 'published') }8 scope :recent, ->(n) { limit(n) }9 end
Implicit vs Explicit API
Active
Record
1 Post.where(state:'published').2 order('created_at DESC').3 limit(5)
1 Post.most_recent_published1 class Post < ActiveRecord::Base2 def self.most_recent_published(limit = 5)3 published.recent(limit).order('created_at DESC')4 end5 6 private7 scope :published, ->() { where(state: 'published') }8 scope :recent, ->(n) { limit(n) }9 end
Implicit vs Explicit API
Active
Record
1 Post.where(state:'published').2 order('created_at DESC').3 limit(5)
1 Post.most_recent_published1 class Post < ActiveRecord::Base2 def self.most_recent_published(limit = 5)3 published.recent(limit).order('created_at DESC')4 end5 6 private7 scope :published, ->() { where(state: 'published') }8 scope :recent, ->(n) { limit(n) }9 end
Implicit vs Explicit API
Active
Record
1 Post.where(state:'published').2 order('created_at DESC').3 limit(5)
1 Post.most_recent_published1 class Post < ActiveRecord::Base2 def self.most_recent_published(limit = 5)3 published.recent(limit).order('created_at DESC')4 end5 6 private7 scope :published, ->() { where(state: 'published') }8 scope :recent, ->(n) { limit(n) }9 end
Callbacks abuse
Active
Record
Non-persistence logic is tight to the persistence life cycle.
Eg. Sending emails
Testability issues
Active
Record
Micheal Feathers
Testability issues
Active
Record
A test is not a unit test if it talks to a database.
Micheal Feathers
ActionController
Action
Controller
It doesn’t affect too much your architecture, but it has strange OOP design.
Frankenstein Controllers
Action
Controller
1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end
�!& ��������� !��!��
�!& �����"!��
�!& �����������
����!%���������!�������#����!� ���������"������ �
�%�����"�!�
Frankenstein ControllersAction
Controller
1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end
�!& ��������� !��!��
�!& �����"!��
�!& �����������
����!%���������!�������#����!� ���������"������ �
�%�����"�!�
Frankenstein ControllersAction
Controller
1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end
�!& ��������� !��!��
�!& �����"!��
�!& �����������
����!%���������!�������#����!� ���������"������ �
�%�����"�!�
Frankenstein ControllersAction
Controller
1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end
�!& ��������� !��!��
�!& �����"!��
�!& �����������
����!%���������!�������#����!� ���������"������ �
�%�����"�!�
Frankenstein ControllersAction
Controller
1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end
�!& ��������� !��!��
�!& �����"!��
�!& �����������
����!%���������!�������#����!� ���������"������ �
�%�����"�!�
Frankenstein ControllersAction
Controller
1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end
�!& ��������� !��!��
�!& �����"!��
�!& �����������
����!%���������!�������#����!� ���������"������ �
�%�����"�!�
1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 7 if @post.save8 # ...9 else10 # ...11 end12 end13 end
Odd classes
Action
Controller
��$�!����������!�� ������!������
��"����&!�����" ��%�"����&!������!�%��� !��!��!�������!���������$�!�������!�!�� ����������%�
1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 7 if @post.save8 # ...9 else10 # ...11 end12 end13 end
Odd classesAction
Controller
��$�!����������!�� ������!������
��"����&!�����" ��%�"����&!������!�%��� !��!��!�������!���������$�!�������!�!�� ����������%�
1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 7 if @post.save8 # ...9 else10 # ...11 end12 end13 end
Odd classesAction
Controller
��$�!����������!�� ������!������
��"����&!�����" ��%�"����&!������!�%��� !��!��!�������!���������$�!�������!�!�� ����������%�
1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 7 if @post.save8 # ...9 else10 # ...11 end12 end13 end
Odd classesAction
Controller
��$�!����������!�� ������!������
��"����&!�����" ��%�"����&!������!�%��� !��!��!�������!���������$�!�������!�!�� ����������%�
1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 # ...7 end8 end
Encapsulation violations
Action
Controller
��$�!����������!�� ������!��������� ��#���$��������������!��!� ! �����#��$ ��
1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 # ...7 end8 end
Encapsulation violationsAction
Controller
��$�!����������!�� ������!��������� ��#���$��������������!��!� ! �����#��$ ��
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
��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��
��!������������!�!���
��� �� �����������!��!�' !��� (!��� !�!�������!������!�������
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 issuesAction
Controller
��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��
��!������������!�!���
��� �� �����������!��!�' !��� (!��� !�!�������!������!�������
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 issuesAction
Controller
��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��
��!������������!�!���
��� �� �����������!��!�' !��� (!��� !�!�������!������!�������
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 issuesAction
Controller
��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��
��!������������!�!���
��� �� �����������!��!�' !��� (!��� !�!�������!������!�������
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 issuesAction
Controller
��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��
��!������������!�!���
��� �� �����������!��!�' !��� (!��� !�!�������!������!�������
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 issuesAction
Controller
��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��
��!������������!�!���
��� �� �����������!��!�' !��� (!��� !�!�������!������!�������
ActionView
Action
View
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.
Helpers arefunctional programming
Action
View
1 def user_full_name(user)2 [ user.first_name, user.last_name ].join(' ')3 end4 5 url_for6 # vs7 Url.for8 9 posts_url
Half-assed way to solvepresentational problems in ActionView.
Helpers arefunctional programming
Action
View
1 def user_full_name(user)2 [ user.first_name, user.last_name ].join(' ')3 end4 5 url_for6 # vs7 Url.for8 9 posts_url
Half-assed way to solvepresentational problems in ActionView.
Ruby onRails
Ruby on
Rails
Rails isn’t a framework,but an application
template
Ruby on
Rails
Rails is multi-paradigmas Ruby is.
Ruby on
Rails
AGENDA
intro problems solutions
Decouple your logic fromActiveRecord as much as
possible.
Solutions
AR is focused on data, but OOP is about behavior.
Don’t think in ActiveRecord terms.
Solutions
Database is a detail, forget about associations,scopes, validations..
Let your public APIto declare intents.
Solutions
Let your design to emerge via TDD.
Keep methods and accessors private as
much as possible.
Solutions
Public APIs are hard to maintain as the codebase and the team grows.
Skinny controllersand
skinny models.
Solutions
Use service objects or DCI, they are easier and faster to test.
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.
Use DIY presenters
Solutions
They will help you to keep your models clean from presentational logic.
Refactor, refactor, refactor.
Solutions
AGENDA
intro problems solutions conclusion
Q&A
[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/
��������