Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[SouthEastRuby 2018] The Gem Check: writing better Ruby gems

[SouthEastRuby 2018] The Gem Check: writing better Ruby gems

Vladimir Dementyev

August 02, 2018
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. palkan_tula palkan SouthEastRuby ‘18 “The more expressive language the much

    easier is to write terrible code” –Noah Gibbs 11
  2. palkan_tula palkan SouthEastRuby ‘18 SIMPLE IS… 17 Less code Less

    thinking when writing code Less thinking when reading code
  3. palkan_tula palkan SouthEastRuby ‘18 SIMPLE IS… 18 Less code Less

    thinking when writing code Less thinking when reading code
  4. 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
  5. palkan_tula palkan SouthEastRuby ‘18 BOILERPLATE 21 HTTParty.get( "http: //example.com/index.json", {

    limit: 10, page: 3 }, basic_auth: { username: "user", password: "pass" } )
  6. 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 )
  7. 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?
  8. palkan_tula palkan SouthEastRuby ‘18 SENSIBLE DEFAULTS 26 class Post <

    ActiveRecord ::Base belongs_to :user end CONVENTION OVER CONFIGURATION
  9. 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
  10. palkan_tula palkan SouthEastRuby ‘18 SENSIBLE CONFIG 29 Cover the most

    popular use cases with the default configuration
  11. 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
  12. 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
  13. 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 ❌)
  14. palkan_tula palkan SouthEastRuby ‘18 SIMPLE IS… 34 Less code Less

    thinking when writing code Less thinking when reading code
  15. palkan_tula palkan SouthEastRuby ‘18 LEAST SURPRISE 36 1.nonzero? => 1

    0.nonzero? => nil 0.zero? => true 1.zero? => false
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. palkan_tula palkan SouthEastRuby ‘18 ADAPTERIZATION 64 # config/application.rb module YourApp

    class Application < Rails ::Application config.active_job.queue_adapter = :sidekiq end end
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. palkan_tula palkan SouthEastRuby ‘18 TESTABILITY 76 Custom matchers / assertions

    # Clowne specify do is_expected.to clone_association( :profile, clone_with: ProfileCloner ) end
  31. palkan_tula palkan SouthEastRuby ‘18 TESTABILITY 77 Custom matchers / assertions

    # ActiveModelSerializers test "should render post serializer" do get :index assert_serializer "PostSerializer" end
  32. 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!
  33. palkan_tula palkan SouthEastRuby ‘18 TESTABILITY 79 Test adapters # ActiveJob

    config.active_job.queue_adapter = :test # ActionMailer config.action_mailer.delivery_method = :test
  34. 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!
  35. 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
  36. 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
  37. 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
  38. palkan_tula palkan SouthEastRuby ‘18 DOCUMENTS 86 Readme Documentation (rubydoc.info, readthedocs.io,

    whatever) Wiki Examples / Demo applications “How it works?” Changelog