palkan_tula
palkan SouthEastRuby ‘18
–Alan Kay
“Simple things should be simple,
complex things should be possible.”
15
Slide 16
Slide 16 text
FROM
COMPLEX
TO
SIMPLE
Part 1
Slide 17
Slide 17 text
palkan_tula
palkan SouthEastRuby ‘18
SIMPLE IS…
17
Less code
Less thinking when writing code
Less thinking when reading code
Slide 18
Slide 18 text
palkan_tula
palkan SouthEastRuby ‘18
SIMPLE IS…
18
Less code
Less thinking when writing code
Less thinking when reading code
Slide 19
Slide 19 text
WHY???
So much BOILERplate
Slide 20
Slide 20 text
palkan_tula
palkan SouthEastRuby ‘18
BOILERPLATE
20
uri = URI('http: //example.com/index.json')
params = { limit: 10, page: 3 }
uri.query = URI.encode_ www_form(params)
req = Net ::HTTP ::Get.new uri
req.basic_auth 'user', 'pass'
res = Net ::HTTP.start(uri.host, uri.port) do |http|
http.request(req)
end
if res.is_a?(Net ::HTTPSuccess)
JSON.parse(res.body)
end
palkan_tula
palkan SouthEastRuby ‘18
23
Simple and elegant API
on top of the complex
but powerful one
Slide 24
Slide 24 text
SENSIBLE DEFAULTS
Slide 25
Slide 25 text
palkan_tula
palkan SouthEastRuby ‘18
SENSIBLE DEFAULTS
25
class Post < ActiveRecord ::Base
# in the world with no defaults
self.table_name = "posts"
belongs_to :user,
foreign_key: :user_id,
class_name: "User",
primary_key: :id
end
Sensible?
Slide 26
Slide 26 text
palkan_tula
palkan SouthEastRuby ‘18
SENSIBLE DEFAULTS
26
class Post < ActiveRecord ::Base
belongs_to :user
end
CONVENTION
OVER
CONFIGURATION
palkan_tula
palkan SouthEastRuby ‘18
SENSIBLE DEFAULTS
28
# With Pundit
class ProductsController < ApplicationController
def create
authorize Product
end
end
# With ActionPolicy
class ProductsController < ApplicationController
def create
# target class is inferred from controller
authorize!
end
end
Slide 29
Slide 29 text
palkan_tula
palkan SouthEastRuby ‘18
SENSIBLE CONFIG
29
Cover the most popular use cases with the
default configuration
Slide 30
Slide 30 text
palkan_tula
palkan SouthEastRuby ‘18
SENSIBLE CONFIG
30
Cover the most popular use cases with the
default configuration
Support popular environment variables out-of-
the-box
palkan_tula
palkan SouthEastRuby ‘18
stop_active_support_everywhere
44
Not every Ruby application is a Rails
application
Consider using Refinements instead
Slide 45
Slide 45 text
No content
Slide 46
Slide 46 text
palkan_tula
palkan SouthEastRuby ‘18
REFINEMENTS
46
module Anyway ::HashExt
refine Hash do
def stringify_keys!
keys.each do |key|
val = delete(key)
val.stringify_keys! if val.is_a?(Hash)
self[key.to_s] = val
end
end
end
end
https://github.com/palkan/anyway_config
Slide 47
Slide 47 text
palkan_tula
palkan SouthEastRuby ‘18
REFINEMENTS
47
class Anyway ::Config
using Anyway ::Ext ::DeepDup
using Anyway ::Ext ::Hash
def self.attr_config(*args, **hargs)
@defaults = hargs.deep_dup
defaults.stringify_keys!
@config_attributes = args + defaults.keys
attr_accessor(*@config_attributes)
end
end
https://github.com/palkan/anyway_config
Slide 48
Slide 48 text
palkan_tula
palkan SouthEastRuby ‘18
REFINEMENTS
48
unless "".respond_to?(:safe_constantize)
require "action_policy/ext/string_constantize"
using ActionPolicy ::Ext ::StringConstantize
end
https://github.com/palkan/action_policy
palkan_tula
palkan SouthEastRuby ‘18
SIMPLE IS…
50
Less code
Less thinking when writing code
Less thinking when reading code
Less thinking when resolving issues
palkan_tula
palkan SouthEastRuby ‘18
“The adapter pattern is classified
as a structural pattern that allows
a piece of code talk to another
piece of code that it is not directly
compatible with.”
63
https://dev.to/kylegalbraith/how-to-use-the-excellent-adapter-pattern-and-why-you-should-2c31
Slide 64
Slide 64 text
palkan_tula
palkan SouthEastRuby ‘18
ADAPTERIZATION
64
# config/application.rb
module YourApp
class Application < Rails ::Application
config.active_job.queue_adapter = :sidekiq
end
end
Slide 65
Slide 65 text
palkan_tula
palkan SouthEastRuby ‘18
ADAPTERIZATION
65
Library Dep
E.g. DB, cache,
API, other lib
Slide 66
Slide 66 text
palkan_tula
palkan SouthEastRuby ‘18
ADAPTERIZATION
66
module BeforeAll
module RSpec
def before_all(&block)
before(:all) do
ActiveRecord ::Base.connection.begin_transaction(joinable: false)
instance_eval(&block)
end
after(:all) { ActiveRecord ::Base.connection.rollback_transaction }
end
end
end
https://test-prof.evilmartians.io/#/before_all
Slide 67
Slide 67 text
palkan_tula
palkan SouthEastRuby ‘18
ADAPTERIZATION
67
Library Dep
E.g. DB, cache,
API, other lib
Adapter
Slide 68
Slide 68 text
palkan_tula
palkan SouthEastRuby ‘18
ADAPTERIZATION
68
module BeforeAll
class << self
attr_accessor :adapter
def begin_transaction
adapter.begin_transaction
end
end
module RSpec
def before_all(&block)
before(:all) do
BeforelAll.begin_transaction
instance_eval(&block)
end
end
end
end
https://github.com/palkan/test-prof/pull/81
palkan_tula
palkan SouthEastRuby ‘18
Library
70
Core API
Middleware API
Application
Plugin B
Plugin A
MIDDLEWARE
Slide 71
Slide 71 text
palkan_tula
palkan SouthEastRuby ‘18
MIDDLEWARE
71
# config.ru
require './my_app'
use Rack ::Debug
run MyApp.new
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add Sidekiq ::OinkMiddleware,
logger: :new_relic
end
end
palkan_tula
palkan SouthEastRuby ‘18
CASE: ACTIONCABLE
74
No test adapter
https://github.com/rails/rails/pull/23211
No unit-testing support
https://github.com/rails/rails/pull/27191
palkan_tula
palkan SouthEastRuby ‘18
TESTABILITY
77
Custom matchers / assertions
# ActiveModelSerializers
test "should render post serializer" do
get :index
assert_serializer "PostSerializer"
end
Slide 78
Slide 78 text
palkan_tula
palkan SouthEastRuby ‘18
TESTABILITY
78
Test helpers / mocking
# Devise
test "should be success" do
sign_in user
get :index
assert_equal 200, response.status
end
# Fog
Fog.mock!
palkan_tula
palkan SouthEastRuby ‘18
TESTABILITY
80
Test mode / configuration
CarrierWave.configure do |config|
config.enable_processing = false
end
Devise.setup do |config|
config.stretches = Rails.env.test? ? 1 : 11
end
Sidekiq ::Testing.fake!
Slide 81
Slide 81 text
palkan_tula
palkan SouthEastRuby ‘18
CASE: WRAPPER
81
module Resolver
class << self
def resolve(host)
return "1.2.3.4" if @test == true
Resolv.getaddress(host)
end
def enable_test!
@test = true
end
end
end
Slide 82
Slide 82 text
palkan_tula
palkan SouthEastRuby ‘18
CASE: ACTION POLICY
82
class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
authorize! @post
# ...
end
end
describe PostsController do
subject { patch :update, id: post.id, params: params }
it "is authorized" do
expect { subject }.to be_authorized_to(:update?, post)
.with(PostPolicy)
end
end
https://actionpolicy.evilmartians.io/#/testing
Slide 83
Slide 83 text
palkan_tula
palkan SouthEastRuby ‘18
CASE: ACTION POLICY
83
https://actionpolicy.evilmartians.io/#/testing
module PerThreadCache
# ...
# Turn off by default in test env
self.enabled = !(ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test")
end
Slide 84
Slide 84 text
WHAT ELSE?
Slide 85
Slide 85 text
SUPPORTING
DOCUMENTS
Slide 86
Slide 86 text
palkan_tula
palkan SouthEastRuby ‘18
DOCUMENTS
86
Readme
Documentation (rubydoc.info, readthedocs.io, whatever)
Wiki
Examples / Demo applications
“How it works?”
Changelog