Share
Embed
Transcript
88 slides
-
palkan_tula palkan SouthEastRuby ‘18 –Matz “Ruby is designed to make
programmers happy.” 10 -
palkan_tula palkan SouthEastRuby ‘18 “The more expressive language the much
easier is to write terrible code” –Noah Gibbs 11 -
palkan_tula palkan SouthEastRuby ‘18 –Alan Kay “Simple things should be
simple, complex things should be possible.” 15 -
palkan_tula palkan SouthEastRuby ‘18 SIMPLE IS… 17 Less code Less
thinking when writing code Less thinking when reading code -
palkan_tula palkan SouthEastRuby ‘18 SIMPLE IS… 18 Less code Less
thinking when writing code Less thinking when reading code -
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 BOILERPLATE 21 HTTParty.get( "http: //example.com/index.json", {
limit: 10, page: 3 }, basic_auth: { username: "user", password: "pass" } ) -
palkan_tula palkan SouthEastRuby ‘18 COMPLEX POSSIBLE 22 HTTParty.get( 'http: //example.com/index.json',
{ limit: 10, page: 3 }, basic_auth: { ...}, headers: { ...}, open_timeout: 2, read_timeout: 3 ) -
palkan_tula palkan SouthEastRuby ‘18 23 Simple and elegant API on
top of the complex but powerful one -
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? -
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 -
palkan_tula palkan SouthEastRuby ‘18 SENSIBLE CONFIG 29 Cover the most
popular use cases with the default configuration -
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 REDIS_URL 31 def determine_redis_provider ENV[ENV['REDIS_PROVIDER'] ||
'REDIS_URL'] end self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379" # => No env support( ✅ Sidekiq ❌ Resque -
palkan_tula palkan SouthEastRuby ‘18 DATABASE_URL 32 def establish_connection( spec =
ENV["DATABASE_URL"]) … end url = ENV['DATABASE_URL'] config['url'] ||= url if url ✅ ActiveRecord ✅ SequelRails (but not Sequel ❌) -
palkan_tula palkan SouthEastRuby ‘18 SIMPLE IS… 34 Less code Less
thinking when writing code Less thinking when reading code -
palkan_tula palkan SouthEastRuby ‘18 LEAST SURPRISE 36 1.nonzero? => 1
0.nonzero? => nil 0.zero? => true 1.zero? => false -
palkan_tula palkan SouthEastRuby ‘18 NIL SURPRISE 40 # Always return
false! Why? def exists?(url) res = nil begin res = HTTParty.head(url, timeout: 2) rescue HTTParty ::Error => e end !res.nil? && res.ok? end -
palkan_tula palkan SouthEastRuby ‘18 NIL SURPRISE 41 Meaning Patching #
Always return false! Why? def exists?(url) res = nil begin res = HTTParty.head(url, timeout: 2) rescue HTTParty ::Error => e end !res.nil? && res.ok? end # From: /bundle/gems/httparty-0.15.5/lib/httparty/response.rb @ line 58: # Owner: HTTParty ::Response def nil? response.nil? || response.body.nil? || response.body.empty? end -
palkan_tula palkan SouthEastRuby ‘18 ACTIVESUPPORT 43 spec.add_runtime_dependency "activesupport" 78 %
22 % Yes No Top-5000 gems -
palkan_tula palkan SouthEastRuby ‘18 stop_active_support_everywhere 44 Not every Ruby application
is a Rails application Consider using Refinements instead -
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 -
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 -
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 MEANINGFUL ERRORS 54 # BAD def
update_as(type:, **params) raise ArgumentError, "Unknown type" unless TYPES.include?(type) ... end # GOOD def update_as(type:, **params) raise ArgumentError,"Unknown type: #{type}" unless TYPES.include?(type) ... end -
palkan_tula palkan SouthEastRuby ‘18 ACTIONABLE ERRORS 57 module ActionPolicy class
UnknownRule < Error # ... def suggest(policy, error) suggestion = ::DidYouMean ::SpellChecker.new( dictionary: policy.public_methods ).correct(error).first suggestion ? "\nDid you mean? #{suggestion}" : "" end end end authorize! Post, to: :creates? # => ActionPolicy ::UnknownRule: Couldn't find rule 'creates?' for PostPolicy # Did you mean? create? https://github.com/palkan/action_policy -
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 -
palkan_tula palkan SouthEastRuby ‘18 ADAPTERIZATION 64 # config/application.rb module YourApp
class Application < Rails ::Application config.active_job.queue_adapter = :sidekiq end end -
palkan_tula palkan SouthEastRuby ‘18 ADAPTERIZATION 65 Library Dep E.g. DB,
cache, API, other lib -
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 -
palkan_tula palkan SouthEastRuby ‘18 ADAPTERIZATION 67 Library Dep E.g. DB,
cache, API, other lib Adapter -
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 -
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 PLUGIN 72 class RubocopMarkdown < Rubocop
::Plugin default_config File.join( __dir __, "default.yml") enable_if ->(filename) { markdown?(filename) } pre_process_source RubocopMarkdown ::Preprocessor end https://github.com/rubocop-hq/rubocop/issues/6012 WIP -
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 76 Custom matchers / assertions
# Clowne specify do is_expected.to clone_association( :profile, clone_with: ProfileCloner ) end -
palkan_tula palkan SouthEastRuby ‘18 TESTABILITY 77 Custom matchers / assertions
# ActiveModelSerializers test "should render post serializer" do get :index assert_serializer "PostSerializer" end -
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 79 Test adapters # ActiveJob
config.active_job.queue_adapter = :test # ActionMailer config.action_mailer.delivery_method = :test -
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! -
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 -
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 -
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 -
palkan_tula palkan SouthEastRuby ‘18 DOCUMENTS 86 Readme Documentation (rubydoc.info, readthedocs.io,
whatever) Wiki Examples / Demo applications “How it works?” Changelog