Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Rails & SOLID by José Valim - RailsConf 2011
Search
Plataformatec
March 08, 2012
Technology
5
5.3k
Rails & SOLID by José Valim - RailsConf 2011
Plataformatec
March 08, 2012
Tweet
Share
More Decks by Plataformatec
See All by Plataformatec
O case da Plataformatec com o Elixir - Como uma empresa brasileira criou uma linguagem que é usada no mundo inteiro @ Elixir Brasil 2019
plataformatec
5
970
O case da Plataformatec com o Elixir - Como uma empresa brasileira criou uma linguagem que é usada no mundo inteiro @ QCon SP 2018
plataformatec
1
220
Elixir @ iMasters Intercon 2016
plataformatec
1
260
GenStage and Flow by @josevalim at ElixirConf
plataformatec
17
2.7k
Elixir: Programação Funcional e Pragmática @ 2º Tech Day Curitiba
plataformatec
2
290
Elixir: Programação Funcional e Pragmática @ Encontro Locaweb 2016
plataformatec
4
280
What's ahead for Elixir: v1.2 and GenRouter
plataformatec
15
2k
Arquiteturas Comuns de Apps Rails @ RubyConf BR 2015
plataformatec
6
370
Pirâmide de testes, escrevendo testes com qualidade @ RubyConf 2015
plataformatec
10
2.3k
Other Decks in Technology
See All in Technology
インフラをつくるとはどういうことなのか、 あるいはPlatform Engineeringについて
nwiizo
5
2.4k
転生CISOサバイバル・ガイド / CISO Career Transition Survival Guide
kanny
3
920
技術的負債解消の取り組みと専門チームのお話 #技術的負債_Findy
bengo4com
1
1.2k
【Developers Summit 2025】プロダクトエンジニアから学ぶ、 ユーザーにより高い価値を届ける技術
niwatakeru
2
1.2k
エンジニアのためのドキュメント力基礎講座〜構造化思考から始めよう〜(2025/02/15jbug広島#15発表資料)
yasuoyasuo
16
6.3k
データの品質が低いと何が困るのか
kzykmyzw
6
1.1k
開発組織のための セキュアコーディング研修の始め方
flatt_security
3
1.4k
現場の種を事業の芽にする - エンジニア主導のイノベーションを事業戦略に装着する方法 -
kzkmaeda
2
1.8k
表現を育てる
kiyou77
1
200
Developer Summit 2025 [14-D-1] Yuki Hattori
yuhattor
19
5.8k
Swiftの “private” を テストする / Testing Swift "private"
yutailang0119
0
120
Data-centric AI入門第6章:Data-centric AIの実践例
x_ttyszk
1
390
Featured
See All Featured
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.3k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
120k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
3
320
The World Runs on Bad Software
bkeepers
PRO
67
11k
Keith and Marios Guide to Fast Websites
keithpitt
411
22k
Rebuilding a faster, lazier Slack
samanthasiow
80
8.8k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
44
9.4k
How to Think Like a Performance Engineer
csswizardry
22
1.3k
The Language of Interfaces
destraynor
156
24k
KATA
mclloyd
29
14k
The Art of Programming - Codeland 2020
erikaheidi
53
13k
Making the Leap to Tech Lead
cromwellryan
133
9.1k
Transcript
Design Principles behind
I am José Valim @josevalim
I work at blog.plataformatec.co
Core Team Member
Rails 3
SOLID Design
None
•Single Responsibility Principle •Open/closed Principle •Liskov Substitution
Single Responsibility Principle
“A class should have one, and only one, reason to
change” - Uncle Bob
Example: Views Refactoring
0.x ActionView::Base tracking details finding templates compiling templates rendering templates
rendering context Controller Render Contents
1.0 ActionView::Base tracking details finding templates rendering templates rendering context
Controller Render Contents Template Template handler compiling templates
builder, rjs, haml...
2.2 ActionView::Base tracking details rendering templates rendering context Controller Render
Contents Template Template handler compiling templates View paths hold filesystem paths finding templates
Rails Engines!
3.0 ActionView::Base tracking details rendering templates rendering context Controller Render
Contents Template Template handler compiling templates View paths hold resolvers Resolvers finding templates
The resolver object no longer restricts templates to the filesystem
class BasicController < ActionController::Base self.view_paths = [ ActionView::FixtureResolver.new( "basic/hello_world.html.erb" =>
"Hello world!" ) ] def hello_world render :action => "hello_world" end end
You can create your own abstractions, allowing you to read
templates from the database!
3.0 ActionView::Base tracking details rendering templates rendering context Controller Render
Contents Template Template handler compiling templates View paths hold resolvers Resolvers finding templates
3.0 ActionView::Base rendering templates rendering context Controller Render Contents Template
Template handler compiling templates View paths hold resolvers Resolvers finding templates Lookup context tracking details
3.1 Controller Template Template handler compiling templates View paths hold
resolvers Resolvers finding templates Lookup context tracking details AV::Renderer rendering templates ActionView::Base rendering context
def lookup_context ActionView::LookupContext.new(self.class._view_paths) end def view_renderer ActionView::Renderer.new(lookup_context) end def _render_template(options)
view_renderer.render(view_context, options) end
def view_context ActionView::Base.new(view_renderer, view_assigns, self) end
Maybe templates could be rendered in the controller context?
class BasicController < ActionController::Base include ActionView::Context # STEP 1 before_filter
:_prepare_context # STEP 2 def hello_world @value = "Hello" end protected def view_context # STEP 3 self end def __controller_method__ "controller context!" end end
# app/views/basic/hello_world.html.erb <%= @value %> from <%= __controller_method__ %> #
would return... Hello from controller context!
3.1 Controller Template Template handler compiling templates View paths hold
resolvers Resolvers finding templates Lookup context tracking details AV::Renderer rendering templates ActionView::Base rendering context
Top-down comprehension Worse Maintainability Better Focused comprehension Much Better Extensibility
POSSIBLE!
Open/closed Principle
“You should be able to extend a class behavior without
modifying it” - Uncle Bob
Open for extension, closed for modification
class ApplicationController < ActionController::Base end
Easy to follow because Ruby classes are all open for
extensions...
... but easy to violate because they are not closed
for modification.
# In your initializer ... config.active_record.table_name_prefix = “foo” # Then
... ActiveRecord::Base.table_name_prefix #=> “foo”
class ApplicationModel < ActiveRecord::Base end
class MyApp::ApplicationModel < ActiveRecord::Base end
Dependency Inversion
“Depend on abstractions, not on concretions” - Uncle Bob
def initialize(app:
def initialize(app)
@rack_app.call
1) Define
match “/foo”, to:
match “/foo”, to: match “/foo”, to: PostsController.action
3.1 Controller Template Template handler compiling templates View paths hold
resolvers Resolvers finding templates Lookup context tracking details AV::Renderer rendering templates ActionView::Base rendering context
@resolver.find_all( name, prefix, partial, details, key, locals
@haml_handler.call
2) Remove hardcoded dependencies
class PostsController < ApplicationController use ActiveRecord::IdentityMap::Middleware, :only => :index #
... end # And this builds the middleware stack ... PostsController.action(:index)
def self.action(name) middleware_stack.build(name.to_s) do |env| new.dispatch(name, ActionDispatch::Request.new(env)) end end
def self.action(name, klass = ActionDispatch::Request) middleware_stack.build(name.to_s) do |env| new.dispatch(name, klass.new(env))
end end
3) Define hooks
1) Load console on sandbox rails console --sandbox 2) Internally...
require “rails/console/ sandbox”
ActiveRecord::Base.connection.increment_open_transactions ActiveRecord::Base.connection.begin_db_transaction at_exit do ActiveRecord::Base.connection.rollback_db_transaction ActiveRecord::Base.connection.decrement_open_transactions end
Instead provide a hook...
class ActiveRecord::Railtie < Rails::Railtie console do |sandbox| if sandbox require
"active_record/railties/console_sandbox" end ActiveRecord::Base.logger = Logger.new(STDERR) end end
Liskov Substitution Principle
“Derived classes must be substitutable for their base classes” -
Uncle Bob
“Derived classes must be substitutable for their base classes” -
Uncle Bob
@rack_app.call •Receives a hash •Returns an array with status code,
headers and an object that responds
Define substitutable but also don’t violate the principle
Datamapper x Active Record
Active Model User.model_name user.persisted? user.valid? user.errors user.to_key user.to_param
How do we ensure substitutability?
ActiveModel::Lint::Tests
class LintTest < ActiveSupport::TestCase include ActiveModel::Lint::Tests def setup @model =
SomeDatamapperModel.new end end
Interface Segregation Principle
“Make fine grained interfaces that are client specific” - Uncle
Bob
“Clients should depend on as narrow protocol as possible” -
Jim Weirich
Active Model User.model_name user.persisted? user.valid? user.errors user.to_key user.to_param
How do we ensure a narrow protocol?
class MyPost < ActiveRecord::Base end my_post = MyPost.new assert_equal "my_posts/1",
url_for(my_post)
my_post = Mock.new my_post.stubs(:model_name).returns("MyPost") my_post.stubs(:to_param).returns(2) assert_equal "my_posts/1", url_for(my_post)
Code to well- defined and narrow protocols
None
None
Questions? José Valim @josevalim blog.plataformatec.com
Questions? blog twitter ID José Valim @josevalim blog.plataformatec.com