pdx.rb, 2016-02-02 Learning to Lib Again 4 Intro ● Programmer for 10 years ● Ruby and Rails programmer for 5 years ● Organized the Burlington Ruby Conference for three years
pdx.rb, 2016-02-02 Learning to Lib Again 13 What lib is for class Jikan # Prints the time in a way that is not awful. # # Example: # >> Jikan.time # => The time is 6:14 PM # The date is February 7, 2013 def self.time puts "The time is #{Time.now.strftime("%I:%M %p")}." puts "The date is #{Time.now.strftime("%B %d, %Y")}." end end
pdx.rb, 2016-02-02 Learning to Lib Again 23 Pre-gem development Let's say an app needs to interface with a third- party API to send faxes, called Faxly.
pdx.rb, 2016-02-02 Learning to Lib Again 26 Pre-gem development For example: faxly_client = Faxly::Client.new( key: 'some-key', secret: 'some-secret' ) faxly_client.send_fax( to: recipient.fax_number, body: 'Hey! Who faxes documents anymore?' )
pdx.rb, 2016-02-02 Learning to Lib Again 29 Pre-gem development Create a directory in lib called faxly. Within that dir, it is just like building out a Ruby gem.
pdx.rb, 2016-02-02 Learning to Lib Again 30 Pre-gem development # lib/faxly/client.rb module Faxly class Client def initialize(key:, secret:) # get a token for making requests end def send_fax(to:, body:) # make the HTTP request to send the fax end end end
pdx.rb, 2016-02-02 Learning to Lib Again 33 Pre-gem development Well… why not just start the building the Faxly client library as a gem from the start?
pdx.rb, 2016-02-02 Learning to Lib Again 37 Pre-gem development Building a more general use gem for anyone to use is a lot more time consuming and distracting than writing the code that you need.
pdx.rb, 2016-02-02 Learning to Lib Again 38 Pre-gem development A separate codebase for a gem means more steps to make changes and get them into the app.
pdx.rb, 2016-02-02 Learning to Lib Again 42 Jobs In our app, users can connect the DropCube file sync service to automatically back-up their faxes before they get sent.
pdx.rb, 2016-02-02 Learning to Lib Again 46 Jobs How would one write tests for that? The #perform method is doing a lot, which makes it difficult to test and debug.
pdx.rb, 2016-02-02 Learning to Lib Again 47 Jobs The job should make use of action classes in lib to make the job simpler. class BackupFaxJob < ActiveJob::Base def perform(fax) pdf = FaxBackup::CreatesPDF.new(fax).create FaxBackup::SyncsToDropCube.new(pdf).sync end end
pdx.rb, 2016-02-02 Learning to Lib Again 49 Jobs class BackupFaxJob < ActiveJob::Base def perform(fax) pdf = FaxBackup::CreatesPDF.new(fax).create FaxBackup::SyncsToDropCube.new(pdf).sync end end
pdx.rb, 2016-02-02 Learning to Lib Again 50 Jobs Now testing #perform is about expecting a class to create a PDF and another class to sync it. It is up to the tests of each of those classes to determine how that is handled.
pdx.rb, 2016-02-02 Learning to Lib Again 51 Jobs Those two files for backing up faxes could live at: ● lib/fax_backup/creates_pdf.rb ● lib/fax_backup/syncs_to_drop_cube.rb
pdx.rb, 2016-02-02 Learning to Lib Again 54 SCFM The gist of SCFM is that controllers in Rails apps can become very complex at times. SCFM is about removing the complexities of a controller and delegating them to a model.
pdx.rb, 2016-02-02 Learning to Lib Again 58 SCFM Similar to the complexities of jobs, controllers should delegate to action classes in lib to do the work.
pdx.rb, 2016-02-02 Learning to Lib Again 59 Tickets::Completer module Tickets class Completer def initialize(ticket) @ticket = ticket @project = ticket.project end def complete! return false if ticket.complete? ticket.complete! update_project_status end end end
pdx.rb, 2016-02-02 Learning to Lib Again 64 New paradigms in app/ Create action classes that specifically describe what the code does and the domain it encompasses.
pdx.rb, 2016-02-02 Learning to Lib Again 65 New paradigms in app/ Once more and more similar action classes get created, look into refactoring to a more generic approach.
pdx.rb, 2016-02-02 Learning to Lib Again 67 New paradigms in app/ New generic classifications of code are more difficult to understand than using specific classes.
pdx.rb, 2016-02-02 Learning to Lib Again 68 When to use lib Try putting code that doesn't fit nicely into the existing Rails paradigms in lib and see how it goes.
pdx.rb, 2016-02-02 Learning to Lib Again 70 How to use lib The various classes and modules could be required where needed, but that can be a bit tedious.
pdx.rb, 2016-02-02 Learning to Lib Again 71 How to use lib require 'secure_hash_generator' require 'fax_formatter' class Fax < ActiveRecord::Base def format FaxFormatter.new( SecureHashGenerator.new.generate ) end end
pdx.rb, 2016-02-02 Learning to Lib Again 73 How to use lib That autoloads and eagerloads all of the code in the lib directory so it is accessible everywhere.
pdx.rb, 2016-02-02 Learning to Lib Again 74 How to use lib require 'secure_hash_generator' require 'fax_formatter' class Fax < ActiveRecord::Base def format FaxFormatter.new( SecureHashGenerator.new.generate ) end end
pdx.rb, 2016-02-02 Learning to Lib Again 75 How to use lib class Fax < ActiveRecord::Base def format FaxFormatter.new( SecureHashGenerator.new.generate ) end end
pdx.rb, 2016-02-02 Learning to Lib Again 80 Testing code in lib For RSpec, the spec for lib/faxly/client.rb would live at spec/lib/faxly/client_spec.rb
pdx.rb, 2016-02-02 Learning to Lib Again 81 Testing code in lib require 'rails_helper' describe Faxly::Client do subject { described_class.new(creds) } describe '#send_fax' do it 'makes an HTTP request' do expect(HTTPal).to receive(:make_request).with(some_json_blob) subject.send_fax( to: '1-800-123-4567', body: 'Serious business info' ) end end end
pdx.rb, 2016-02-02 Learning to Lib Again 82 Testing code in lib With RSpec, running rails generate rspec:install creates two files: 1. spec/spec_helper.rb 2. spec/rails_helper.rb
pdx.rb, 2016-02-02 Learning to Lib Again 84 Testing code in lib The rails_helper is for Rails specific RSpec configuration. It requires the spec_helper: require 'spec_helper'
pdx.rb, 2016-02-02 Learning to Lib Again 85 Testing code in lib Requiring rails_helper loads the entire Rails environment and every gem required in the Gemfile.
pdx.rb, 2016-02-02 Learning to Lib Again 86 Testing code in lib Gemfile: gem 'brakeman' – loads the gem automatically gem 'brakeman', require: false – does not load the gem automatically
pdx.rb, 2016-02-02 Learning to Lib Again 87 Testing code in lib require 'rails_helper' describe FaxesController do describe '#create' do it 'creates a fax record' do end end end
pdx.rb, 2016-02-02 Learning to Lib Again 88 Testing code in lib Loading up the entire Rails environment is slow and unnecessary when testing code in lib.
pdx.rb, 2016-02-02 Learning to Lib Again 89 Testing code in lib When unit testing code in lib that does not depend on Rails, require the spec_helper instead.
pdx.rb, 2016-02-02 Learning to Lib Again 90 Testing code in lib require 'spec_helper' require 'faxly/client' describe Faxly::Client do describe '#send_fax' do it 'sends a fax' do end end end
pdx.rb, 2016-02-02 Learning to Lib Again 91 Testing code in lib This means faster tests for that file. But how much faster? Let's see with: $ time bundle exec rspec spec/path/to/spec.rb * The app has 28 required gems in the Gemfile.
pdx.rb, 2016-02-02 Learning to Lib Again 92 Testing code in lib With rails_helper: ..... Finished in 0.07432 seconds (files took 5.6 seconds to load) 5 examples, 0 failures real 0m6.598s user 0m2.962s sys 0m0.597s
pdx.rb, 2016-02-02 Learning to Lib Again 93 Testing code in lib With spec_helper: ..... Finished in 0.04742 seconds (files took 0.29089 seconds to load) 5 examples, 0 failures real 0m0.964s user 0m0.830s sys 0m0.127s
pdx.rb, 2016-02-02 Learning to Lib Again 95 Testing code in lib With rails_helper with Spring: ..... Finished in 0.05533 seconds (files took 3.17 seconds to load) 5 examples, 0 failures real 0m3.875s user 0m2.720s sys 0m0.558s
pdx.rb, 2016-02-02 Learning to Lib Again 98 Testing code in lib Note: requiring spec_helper does not make a difference when running the whole test suite because Rails has to be loaded anyways.
pdx.rb, 2016-02-02 Learning to Lib Again 103 A few recent uses Light-weight AWS S3 client wrapper module Storage class S3File def initialize(key) bucket = ENV['some-bucket'] client = Aws::S3::Client.new @object = Aws::S3::Object.new(bucket_name: bucket, key: key, client: client) end def url object.presigned_url :get end def upload(source_path) object.upload_file source_path end end end
pdx.rb, 2016-02-02 Learning to Lib Again 105 Open Source References ● https://github.com/chef/supermarket ● https://github.com/bbatsov/rubocop & any gem
pdx.rb, 2016-02-02 Learning to Lib Again 109 In conclusion The structure and style of code in lib is totally up to the developer, which can feel liberating.