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.4k
5
Share
Rails & SOLID by José Valim - RailsConf 2011
Plataformatec
March 08, 2012
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
280
GenStage and Flow by @josevalim at ElixirConf
plataformatec
17
2.9k
Elixir: Programação Funcional e Pragmática @ 2º Tech Day Curitiba
plataformatec
2
320
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
410
Pirâmide de testes, escrevendo testes com qualidade @ RubyConf 2015
plataformatec
10
2.5k
Other Decks in Technology
See All in Technology
Databricks Appsで実現する社内向けAIアプリ開発の効率化
r_miura
0
320
機能・非機能の学びを一つに!Agent Skillsで月間レポート作成始めてみた / Unifying Bug & Infra Insights — Building Monthly Quality Reports with Agent Skills
bun913
5
3.4k
AI時代に新卒採用、はじめました/junior-engineer-never-die
dmnlk
0
170
Oracle AI Database@AWS:サービス概要のご紹介
oracle4engineer
PRO
4
2.1k
OpenClaw初心者向けセミナー / OpenClaw Beginner Seminar
cmhiranofumio
0
340
Microsoft Fabricで考える非構造データのAI活用
ryomaru0825
0
690
Data Enabling Team立ち上げました
sansantech
PRO
0
280
本番環境でPHPコードに触れずに「使われていないコード」を調べるにはどうしたらよいか?
egmc
1
180
プロダクトを育てるように生成AIによる開発プロセスを育てよう
kakehashi
PRO
1
770
Even G2 クイックスタートガイド(日本語版)
vrshinobi1
0
210
Strands Agents × Amazon Bedrock AgentCoreで パーソナルAIエージェントを作ろう
yokomachi
2
180
Claude Teamプランの選定と、できること/できないこと
rfdnxbro
1
1.1k
Featured
See All Featured
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
ラッコキーワード サービス紹介資料
rakko
1
2.9M
Large-scale JavaScript Application Architecture
addyosmani
515
110k
New Earth Scene 8
popppiees
2
2k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
120
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.6k
The SEO Collaboration Effect
kristinabergwall1
0
410
Principles of Awesome APIs and How to Build Them.
keavy
128
17k
Context Engineering - Making Every Token Count
addyosmani
9
790
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
500
Everyday Curiosity
cassininazir
0
190
Are puppies a ranking factor?
jonoalderson
1
3.2k
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