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
1k
O case da Plataformatec com o Elixir - Como uma empresa brasileira criou uma linguagem que é usada no mundo inteiro @ QCon SP 2018
plataformatec
1
230
Elixir @ iMasters Intercon 2016
plataformatec
1
260
GenStage and Flow by @josevalim at ElixirConf
plataformatec
17
2.8k
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
300
What's ahead for Elixir: v1.2 and GenRouter
plataformatec
15
2.1k
Arquiteturas Comuns de Apps Rails @ RubyConf BR 2015
plataformatec
6
390
Pirâmide de testes, escrevendo testes com qualidade @ RubyConf 2015
plataformatec
10
2.4k
Other Decks in Technology
See All in Technology
Bet "Bet AI" - Accelerating Our AI Journey #BetAIDay
layerx
PRO
4
1.5k
Vision Language Modelと自動運転AIの最前線_20250730
yuyamaguchi
3
1.1k
LLM開発を支えるエヌビディアの生成AIエコシステム
acceleratedmu3n
0
370
AWS DDoS攻撃防御の最前線
ryutakondo
0
120
人に寄り添うAIエージェントとアーキテクチャ #BetAIDay
layerx
PRO
8
2k
バクラクによるコーポレート業務の自動運転 #BetAIDay
layerx
PRO
1
840
alecthomas/kong はいいぞ
fujiwara3
6
1.4k
Segment Anything Modelの最新動向:SAM2とその発展系
tenten0727
0
410
「Roblox」の開発環境とその効率化 ~DAU9700万人超の巨大プラットフォームの開発 事始め~
keitatanji
0
100
LLMでAI-OCR、実際どうなの? / llm_ai_ocr_layerx_bet_ai_day_lt
sbrf248
0
430
Rubyの国のPerlMonger
anatofuz
3
730
Perlアプリケーションで トレースを実装するまでの 工夫と苦労話
masayoshi
1
410
Featured
See All Featured
GraphQLの誤解/rethinking-graphql
sonatard
71
11k
Git: the NoSQL Database
bkeepers
PRO
431
65k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
Code Reviewing Like a Champion
maltzj
524
40k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
3k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
139
34k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
31
1.3k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
26k
Reflections from 52 weeks, 52 projects
jeffersonlam
351
21k
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