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

Learning to lib Again

Learning to lib Again

Making the most of the lib directory in a Rails app

Brett Chalupa

February 02, 2016
Tweet

More Decks by Brett Chalupa

Other Decks in Programming

Transcript

  1. Learning to lib Again Making the Most of the lib

    Directory in a Rails App Brett Chalupa pdx.rb February 2016
  2. 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
  3. pdx.rb, 2016-02-02 Learning to Lib Again 7 What the heck

    is this talk about? Let's talk about the lib directory.
  4. pdx.rb, 2016-02-02 Learning to Lib Again 8 Why? For years

    I thought the lib directory in Rails projects was just for rake tasks.
  5. pdx.rb, 2016-02-02 Learning to Lib Again 9 Why? These days,

    the lib directory is where I spend most of my time when working with Rails.
  6. pdx.rb, 2016-02-02 Learning to Lib Again 10 Why? It has

    changed the way I look at Rails, and it has made me a better Rubyist.
  7. pdx.rb, 2016-02-02 Learning to Lib Again 11 What lib is

    for In a gem, lib is where the code goes.
  8. pdx.rb, 2016-02-02 Learning to Lib Again 12 What lib is

    for A simple Ruby gem file structure:
  9. 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
  10. pdx.rb, 2016-02-02 Learning to Lib Again 14 What lib is

    for In a new Rails project, the lib dir is for rake tasks and assets.
  11. pdx.rb, 2016-02-02 Learning to Lib Again 15 What lib is

    for lib can be used for so much more than rake tasks and assets.
  12. pdx.rb, 2016-02-02 Learning to Lib Again 16 What lib is

    for lib is great for code that does not fit Rails' paradigms.
  13. pdx.rb, 2016-02-02 Learning to Lib Again 17 What lib is

    for Code that is not: • Models • Views • Controllers • Mailers • Jobs*
  14. pdx.rb, 2016-02-02 Learning to Lib Again 18 What lib is

    for So… lib can be used for just about anything!
  15. pdx.rb, 2016-02-02 Learning to Lib Again 19 When to use

    lib Here are some great times to put code in the lib directory.
  16. pdx.rb, 2016-02-02 Learning to Lib Again 20 Pre-gem development Put

    code in lib that feels like it could be its own gem.
  17. pdx.rb, 2016-02-02 Learning to Lib Again 21 Pre-gem development Is

    the code being written something that could potentially be used by other projects?
  18. 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.
  19. pdx.rb, 2016-02-02 Learning to Lib Again 24 Pre-gem development Faxly

    is a new service and does not have a Ruby gem.
  20. pdx.rb, 2016-02-02 Learning to Lib Again 25 Pre-gem development It

    would be nice there was a gem to make interacting with Faxly's API easier.
  21. 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?' )
  22. pdx.rb, 2016-02-02 Learning to Lib Again 28 Pre-gem development The

    Faxly client code is external to the domain logic of this hypothetical app.
  23. 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.
  24. 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
  25. pdx.rb, 2016-02-02 Learning to Lib Again 31 Pre-gem development It

    is easier to extract code and tests into a gem when they are independent of Rails.
  26. pdx.rb, 2016-02-02 Learning to Lib Again 32 Pre-gem development However,

    beware of extracting the code into a gem too early.
  27. 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?
  28. pdx.rb, 2016-02-02 Learning to Lib Again 34 Pre-gem development The

    overhead of maintaining a separate gem is usually not worth it from the start.
  29. pdx.rb, 2016-02-02 Learning to Lib Again 35 Pre-gem development Private

    gems require a private gem server or a private Git repo.
  30. 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.
  31. 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.
  32. pdx.rb, 2016-02-02 Learning to Lib Again 39 Pre-gem development Extract

    the code into a gem when it is stable, useful to others, and feels right!
  33. pdx.rb, 2016-02-02 Learning to Lib Again 41 Jobs The app/jobs

    dir is where jobs should go, especially with ActiveJob.
  34. 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.
  35. pdx.rb, 2016-02-02 Learning to Lib Again 43 Jobs class BackupFaxJob

    < ActiveJob::Base def perform(fax) fax.prep_for_pdf_transformation pdf = PDFWizard.new pdf.main_text = fax.body pdf.header = fax.recipient pdf.footer = fax.user fax_dir = “faxes/#{Date.today.strftime('%Y-%m-%d')}-#{SecureHex.random}.pdf” drop_cube_client = DropCube::Client.new('https://api.dropcube.cool/') drop_cube_client.put_file(pdf, dir: fax_dir) end end
  36. 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.
  37. 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
  38. pdx.rb, 2016-02-02 Learning to Lib Again 48 Jobs class BackupFaxJob

    < ActiveJob::Base def perform(fax) fax.prep_for_pdf_transformation pdf = PDFWizard.new pdf.main_text = fax.body pdf.header = fax.recipient pdf.footer = fax.user fax_dir = “faxes/#{Date.today.strftime('%Y-%m-%d')}-#{SecureHex.random}.pdf” drop_cube_client = DropCube::Client.new('https://api.dropcube.cool/') drop_cube_client.put_file(pdf, dir: fax_dir) end end
  39. 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
  40. 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.
  41. 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
  42. pdx.rb, 2016-02-02 Learning to Lib Again 52 Jobs Create action

    classes to handle the work for non-trivial jobs.
  43. pdx.rb, 2016-02-02 Learning to Lib Again 53 SCFM There is

    a way of thinking with Rails known as: Skinny controllers, fat models SCFM
  44. 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.
  45. pdx.rb, 2016-02-02 Learning to Lib Again 55 SCFM However, SCFM

    leads to models that become all-knowing, extremely complex, and hundreds of lines.
  46. pdx.rb, 2016-02-02 Learning to Lib Again 56 SCFM Instead of

    putting a bunch of methods into a model, make use of…
  47. 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.
  48. 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
  49. pdx.rb, 2016-02-02 Learning to Lib Again 60 New paradigms in

    app/ Try to avoid adding new paradigms in the app directory.
  50. pdx.rb, 2016-02-02 Learning to Lib Again 61 New paradigms in

    app/ Some app/ paradigms I seen before: • Services • Presenters • Forms • Queries
  51. pdx.rb, 2016-02-02 Learning to Lib Again 62 New paradigms in

    app/ They are all too generic for their own good.
  52. pdx.rb, 2016-02-02 Learning to Lib Again 63 New paradigms in

    app/ Don't worry about fitting code into a generic noun.
  53. 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.
  54. 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.
  55. pdx.rb, 2016-02-02 Learning to Lib Again 66 New paradigms in

    app/ When I started using Rails it took me years to understand MVC and what goes where.
  56. 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.
  57. 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.
  58. pdx.rb, 2016-02-02 Learning to Lib Again 69 How to use

    lib Using lib with Rails is pretty straightforward.
  59. 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.
  60. 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
  61. pdx.rb, 2016-02-02 Learning to Lib Again 72 How to use

    lib Add the following line to config/application.rb: config.paths.add 'lib', eager_load: true
  62. 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.
  63. 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
  64. 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
  65. pdx.rb, 2016-02-02 Learning to Lib Again 76 Organizing lib The

    lib dir is a blank canvas at the start, which can be intimidating.
  66. pdx.rb, 2016-02-02 Learning to Lib Again 77 Organizing lib Create

    directories (and namespaces) that correspond to different domains and concepts.
  67. pdx.rb, 2016-02-02 Learning to Lib Again 79 Testing code in

    lib Testing code in lib is very similar to models and other classes.
  68. 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
  69. 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
  70. 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
  71. pdx.rb, 2016-02-02 Learning to Lib Again 83 Testing code in

    lib The spec_helper is for general RSpec configuration that is not specific to Rails.
  72. 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'
  73. 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.
  74. 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
  75. 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
  76. 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.
  77. 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.
  78. 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
  79. 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.
  80. 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
  81. 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
  82. pdx.rb, 2016-02-02 Learning to Lib Again 94 Testing code in

    lib ~ 6.8x faster. Pretty good, right?
  83. 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
  84. pdx.rb, 2016-02-02 Learning to Lib Again 96 Testing code in

    lib Requiring rails_helper is a little better with Spring, but it is still ~4x slower.
  85. pdx.rb, 2016-02-02 Learning to Lib Again 97 Testing code in

    lib When doing TDD, a 4x speed increase makes a big difference.
  86. 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.
  87. pdx.rb, 2016-02-02 Learning to Lib Again 99 Testing code in

    lib Code and gems need to be explicitly required when using spec_helper.
  88. pdx.rb, 2016-02-02 Learning to Lib Again 101 A few recent

    uses Client for internal API with no gem library
  89. 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
  90. pdx.rb, 2016-02-02 Learning to Lib Again 104 A few recent

    uses Extract, Transform, Load (ETL) app
  91. 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
  92. pdx.rb, 2016-02-02 Learning to Lib Again 107 In conclusion It

    encourages splitting up code into smaller action classes.
  93. pdx.rb, 2016-02-02 Learning to Lib Again 108 In conclusion It

    feels a lot more like Ruby programming than most other aspects of Rails development.
  94. 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.
  95. pdx.rb, 2016-02-02 Learning to Lib Again 111 Thank you! Brett

    Chalupa @brettchalupa http://www.brettchalupa.com