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.4k
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
1.2k
O case da Plataformatec com o Elixir - Como uma empresa brasileira criou uma linguagem que é usada no mundo inteiro @ QCon SP 2018
plataformatec
1
240
Elixir @ iMasters Intercon 2016
plataformatec
1
270
GenStage and Flow by @josevalim at ElixirConf
plataformatec
17
2.9k
Elixir: Programação Funcional e Pragmática @ 2º Tech Day Curitiba
plataformatec
2
310
Elixir: Programação Funcional e Pragmática @ Encontro Locaweb 2016
plataformatec
4
310
What's ahead for Elixir: v1.2 and GenRouter
plataformatec
15
2.2k
Arquiteturas Comuns de Apps Rails @ RubyConf BR 2015
plataformatec
6
400
Pirâmide de testes, escrevendo testes com qualidade @ RubyConf 2015
plataformatec
10
2.5k
Other Decks in Technology
See All in Technology
茨城の思い出を振り返る ~CDKのセキュリティを添えて~ / 20260201 Mitsutoshi Matsuo
shift_evolve
PRO
1
150
インフラエンジニア必見!Kubernetesを用いたクラウドネイティブ設計ポイント大全
daitak
0
260
Oracle Cloud Observability and Management Platform - OCI 運用監視サービス概要 -
oracle4engineer
PRO
2
14k
Tebiki Engineering Team Deck
tebiki
0
23k
フルカイテン株式会社 エンジニア向け採用資料
fullkaiten
0
10k
分析画面のクリック操作をそのままコード化 ! エンジニアとビジネスユーザーが共存するAI-ReadyなBI基盤
ikumi
0
130
AI時代、1年目エンジニアの悩み
jin4
1
150
M&A 後の統合をどう進めるか ─ ナレッジワーク × Poetics が実践した組織とシステムの融合
kworkdev
PRO
1
310
Amazon S3 Vectorsを使って資格勉強用AIエージェントを構築してみた
usanchuu
3
420
Frontier Agents (Kiro autonomous agent / AWS Security Agent / AWS DevOps Agent) の紹介
msysh
3
130
2026年はチャンキングを極める!
shibuiwilliam
8
1.9k
開発メンバーが語るFindy Conferenceの裏側とこれから
sontixyou
2
550
Featured
See All Featured
Design in an AI World
tapps
0
140
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
170
A Modern Web Designer's Workflow
chriscoyier
698
190k
Navigating Team Friction
lara
192
16k
Skip the Path - Find Your Career Trail
mkilby
0
52
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
0
190
For a Future-Friendly Web
brad_frost
182
10k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.3k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
Accessibility Awareness
sabderemane
0
47
Reality Check: Gamification 10 Years Later
codingconduct
0
2k
Are puppies a ranking factor?
jonoalderson
1
2.7k
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