Slide 1

Slide 1 text

Sandi Metz @sandimetz Feb 2013 SOLID Thursday, February 28, 13

Slide 2

Slide 2 text

@sandimetz Feb 2013 Your application will change. Thursday, February 28, 13

Slide 3

Slide 3 text

@sandimetz Feb 2013 Your application will change. How will that work out? Thursday, February 28, 13

Slide 4

Slide 4 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 5

Slide 5 text

@sandimetz Feb 2013 Rigid Every change forces a cascade of related changes. Thursday, February 28, 13

Slide 6

Slide 6 text

@sandimetz Feb 2013 Rigid Thursday, February 28, 13

Slide 7

Slide 7 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 8

Slide 8 text

@sandimetz Feb 2013 Fragile Each change breaks distant and apparently unrelated things. Thursday, February 28, 13

Slide 9

Slide 9 text

@sandimetz Feb 2013 Fragile Thursday, February 28, 13

Slide 10

Slide 10 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 11

Slide 11 text

@sandimetz Feb 2013 Immobile The code is hopelessly entangled; reuse is impossible. Thursday, February 28, 13

Slide 12

Slide 12 text

@sandimetz Feb 2013 Immobile Thursday, February 28, 13

Slide 13

Slide 13 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 14

Slide 14 text

@sandimetz Feb 2013 Viscous Behaving badly is the most attractive alternative. Thursday, February 28, 13

Slide 15

Slide 15 text

@sandimetz Feb 2013 Viscous Thursday, February 28, 13

Slide 16

Slide 16 text

@sandimetz Feb 2013 It didn’t start that way. Thursday, February 28, 13

Slide 17

Slide 17 text

@sandimetz Feb 2013 In the beginning, your application was perfect. Thursday, February 28, 13

Slide 18

Slide 18 text

@sandimetz Feb 2013 And then it changed. Thursday, February 28, 13

Slide 19

Slide 19 text

@sandimetz Feb 2013 Dependencies are killing you. Thursday, February 28, 13

Slide 20

Slide 20 text

@sandimetz Feb 2013 Dependencies are killing you. Design can save you. Thursday, February 28, 13

Slide 21

Slide 21 text

@sandimetz Feb 2013 http://www.martinfowler.com/bliki/DesignStaminaHypothesis.html Design Stamina Hypothesis Thursday, February 28, 13

Slide 22

Slide 22 text

@sandimetz Feb 2013 If your project succeeds, it will continue to cost you money. $$$ Thursday, February 28, 13

Slide 23

Slide 23 text

@sandimetz Feb 2013 Design Principles and Design Patterns Robert Martin http://www.objectmentor.com Thursday, February 28, 13

Slide 24

Slide 24 text

@sandimetz Feb 2013 Single Responsibility Open/Closed Liskov Substitution Interface Segregation Dependency Inversion Principles Thursday, February 28, 13

Slide 25

Slide 25 text

@sandimetz Feb 2013 Single Responsibility A class should serve a single purpose. Thursday, February 28, 13

Slide 26

Slide 26 text

@sandimetz Feb 2013 Open/Closed A module should be open for extension but closed for modi cation. Thursday, February 28, 13

Slide 27

Slide 27 text

@sandimetz Feb 2013 Liskov Substitution Subclasses should be substitutable for their base classes. Thursday, February 28, 13

Slide 28

Slide 28 text

@sandimetz Feb 2013 Liskov Substitution Subclasses should be substitutable for their base classes. Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T. Thursday, February 28, 13

Slide 29

Slide 29 text

@sandimetz Feb 2013 Interface Segregation Many client speci c interfaces are better than one general purpose interface. Thursday, February 28, 13

Slide 30

Slide 30 text

@sandimetz Feb 2013 Dependency Inversion Depend upon abstractions, do not depend upon concretions. Thursday, February 28, 13

Slide 31

Slide 31 text

@sandimetz Feb 2013 Design is all about dependencies. Thursday, February 28, 13

Slide 32

Slide 32 text

@sandimetz Feb 2013 When you know something you depend on it. Thursday, February 28, 13

Slide 33

Slide 33 text

@sandimetz Feb 2013 If it changes you might change. Thursday, February 28, 13

Slide 34

Slide 34 text

@sandimetz Feb 2013 Changeable Apps Loosely Coupled Highly Cohesive Easily Composable Context Independent Thursday, February 28, 13

Slide 35

Slide 35 text

@sandimetz Feb 2013 Achieving Independence Single Responsibility Dependency Inversion Open/Closed Liskov Substitution Interface Segregation Thursday, February 28, 13

Slide 36

Slide 36 text

@sandimetz Feb 2013 Example App Thursday, February 28, 13

Slide 37

Slide 37 text

@sandimetz Feb 2013 Get a CSV le from an FTP server. Store it in a local database. Remote Patents Patent Job Local Patents Thursday, February 28, 13

Slide 38

Slide 38 text

@sandimetz Feb 2013 FTP Server host:       localhost login:       anon password:   anon path:       Public/prod/ filename:patents.csv Thursday, February 28, 13

Slide 39

Slide 39 text

@sandimetz Feb 2013 person_id,name,title cycler1,"Anti-­‐Gravity  Simulator","A  device  to  make  riding  uphill  easier" cycler1,"Exo-­‐Skello  Jello","An  after  dinner  treat  to  bulk  up  the  feeble" sleeper2,"Nap  Compressor","A  device  which  allows  a  30  minute  nap  is  just  3  minutes" Test Data FTP Server host:       localhost login:       anon password:   anon path:       Public/prod/ filename:patents.csv Thursday, February 28, 13

Slide 40

Slide 40 text

@sandimetz Feb 2013 person_id,name,title cycler1,"Anti-­‐Gravity  Simulator","A  device  to  make  riding  uphill  easier" cycler1,"Exo-­‐Skello  Jello","An  after  dinner  treat  to  bulk  up  the  feeble" sleeper2,"Nap  Compressor","A  device  which  allows  a  30  minute  nap  is  just  3  minutes" Test Data FTP Server host:       localhost login:       anon password:   anon path:       Public/prod/ filename:patents.csv Patent model object Thursday, February 28, 13

Slide 41

Slide 41 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "downloads  the  csv  file  from  the  ftp  server"          it  "asks  Patent  to  overwrite  existing  patents" end Thursday, February 28, 13

Slide 42

Slide 42 text

@sandimetz Feb 2013 PatentJob  Spec    it  "downloads  the  csv  file  from  the  ftp  server"  do        job  =  PatentJob.new        f  =  File.read(job.download_file)        f.should  have(250).characters        f.should  include("just  3  minutes")    end Thursday, February 28, 13

Slide 43

Slide 43 text

@sandimetz Feb 2013 PatentJob  Spec    it  "downloads  the  csv  file  from  the  ftp  server"  do        job  =  PatentJob.new        f  =  File.read(job.download_file)        f.should  have(250).characters        f.should  include("just  3  minutes")    end Thursday, February 28, 13

Slide 44

Slide 44 text

@sandimetz Feb 2013 PatentJob  Spec    it  "downloads  the  csv  file  from  the  ftp  server"  do        job  =  PatentJob.new        f  =  File.read(job.download_file)        f.should  have(250).characters        f.should  include("just  3  minutes")    end Thursday, February 28, 13

Slide 45

Slide 45 text

@sandimetz Feb 2013 PatentJob  Spec        it  "asks  Patent  to  overwrite  existing  patents"  do        rows  =[{"person_id"=>"cycler1",  "name"=>"Anti-­‐Gravity  Simu                      {"person_id"=>"cycler1",  "name"=>"Exo-­‐Skello  Jello"                      {"person_id"=>"sleeper2",  "name"=>"Nap  Compressor",        job  =  PatentJob.new        Patent.should_receive(:overwrite).with(rows)        job.run    end Thursday, February 28, 13

Slide 46

Slide 46 text

@sandimetz Feb 2013 PatentJob  Spec        it  "asks  Patent  to  overwrite  existing  patents"  do        rows  =[{"person_id"=>"cycler1",  "name"=>"Anti-­‐Gravity  Simu                      {"person_id"=>"cycler1",  "name"=>"Exo-­‐Skello  Jello"                      {"person_id"=>"sleeper2",  "name"=>"Nap  Compressor",        job  =  PatentJob.new        Patent.should_receive(:overwrite).with(rows)        job.run    end Thursday, February 28, 13

Slide 47

Slide 47 text

@sandimetz Feb 2013 PatentJob  Spec        it  "asks  Patent  to  overwrite  existing  patents"  do        rows  =[{"person_id"=>"cycler1",  "name"=>"Anti-­‐Gravity  Simu                      {"person_id"=>"cycler1",  "name"=>"Exo-­‐Skello  Jello"                      {"person_id"=>"sleeper2",  "name"=>"Nap  Compressor",        job  =  PatentJob.new        Patent.should_receive(:overwrite).with(rows)        job.run    end Thursday, February 28, 13

Slide 48

Slide 48 text

@sandimetz Feb 2013 PatentJob  Spec    it  "downloads  the  csv  file  from  the  ftp  server"  do        job  =  PatentJob.new        f  =  File.read(job.download_file)        f.should  have(250).characters        f.should  include("just  3  minutes")    end    it  "asks  Patent  to  overwrite  existing  patents"  do        rows  =[{"person_id"=>"cycler1",  "name"=>"Anti-­‐Gravity  Simu                      {"person_id"=>"cycler1",  "name"=>"Exo-­‐Skello  Jello"                      {"person_id"=>"sleeper2",  "name"=>"Nap  Compressor",        job  =  PatentJob.new        Patent.should_receive(:overwrite).with(rows)        job.run    end Thursday, February 28, 13

Slide 49

Slide 49 text

@sandimetz Feb 2013 class  Patent  <  ActiveRecord::Base    def  self.overwrite(rows)        #  lots  of  real  database  i/o    end end Thursday, February 28, 13

Slide 50

Slide 50 text

@sandimetz Feb 2013 class  PatentJob      def  run        temp  =  download_file        rows  =  parse(temp)        update_patents(rows)    end      def  download_file      def  parse(temp)      def  update_patents(rows) end Thursday, February 28, 13

Slide 51

Slide 51 text

@sandimetz Feb 2013 class  PatentJob      def  run        temp  =  download_file        rows  =  parse(temp)        update_patents(rows)    end      def  download_file      def  parse(temp)      def  update_patents(rows) end Thursday, February 28, 13

Slide 52

Slide 52 text

@sandimetz Feb 2013 class  PatentJob      def  run        temp  =  download_file        rows  =  parse(temp)        update_patents(rows)    end      def  download_file      def  parse(temp)      def  update_patents(rows) end Thursday, February 28, 13

Slide 53

Slide 53 text

@sandimetz Feb 2013 class  PatentJob      def  run        update_patents(parse(download_file))    end      def  download_file      def  parse(temp)      def  update_patents(rows) end Thursday, February 28, 13

Slide 54

Slide 54 text

@sandimetz Feb 2013 class  PatentJob      def  run        update_patents(parse(download_file))    end      def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end Thursday, February 28, 13

Slide 55

Slide 55 text

@sandimetz Feb 2013 class  PatentJob    def  run        update_patents(parse(download_file))    end        def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end      def  parse(temp)        CSV.read(temp,  :headers  =>  true).map(&:to_hash)    end      def  update_patents(rows)        Patent.overwrite(rows)    end   end Entire PatentJob class Thursday, February 28, 13

Slide 56

Slide 56 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Thursday, February 28, 13

Slide 57

Slide 57 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 58

Slide 58 text

@sandimetz Feb 2013 I’m uneasy About the code What if the ftp host/user/password changes? What if other ftp’ing jobs are required? About the test I hate ftp’ing the le in every test Thursday, February 28, 13

Slide 59

Slide 59 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 60

Slide 60 text

@sandimetz Feb 2013 General Rule Leave it until you need it Thursday, February 28, 13

Slide 61

Slide 61 text

@sandimetz Feb 2013 Resistance is a Resource Listen to your code Embrace the friction Fix the problem Thursday, February 28, 13

Slide 62

Slide 62 text

@sandimetz Feb 2013 Testing If testing seems hard, examine your design. Thursday, February 28, 13

Slide 63

Slide 63 text

@sandimetz Feb 2013 4 refactorings ... Thursday, February 28, 13

Slide 64

Slide 64 text

@sandimetz Feb 2013 Red Green Refactor Is it DRY? Does it have a one responsibility? Does everything in it change at the same rate? Does it depend on more stable things? Thursday, February 28, 13

Slide 65

Slide 65 text

@sandimetz Feb 2013 Red Green Refactor Is it DRY? Does it have a one responsibility? Does everything in it change at the same rate? Does it depend on more stable things? Thursday, February 28, 13

Slide 66

Slide 66 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "downloads  the  csv  file  from  the  ftp  server"          it  "asks  Patent  to  overwrite  existing  patents" end Thursday, February 28, 13

Slide 67

Slide 67 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "downloads  the  csv  file  from  the  ftp  server"          it  "asks  Patent  to  overwrite  existing  patents" end downloads and asks Thursday, February 28, 13

Slide 68

Slide 68 text

@sandimetz Feb 2013 Separate the Responsibilities Remote Patents Patent Job Local Patents Downloader Thursday, February 28, 13

Slide 69

Slide 69 text

@sandimetz Feb 2013 Separate the Responsibilities Remote Patents Patent Job Local Patents Patent Downloader Thursday, February 28, 13

Slide 70

Slide 70 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Patent Downloader Change PatentJob to use PatentDownloader Thursday, February 28, 13

Slide 71

Slide 71 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "asks  Patent  to  overwrite  existing  patents"  do        f        =  './spec/fixtures/patents.csv'        rows  =  CSV.read(f,  :headers  =>  true).map(&:to_hash)          downldr  =  double("Downloader")        downldr.stub(:download_file).and_return(f)          job  =  PatentJob.new(downldr)        Patent.should_receive(:overwrite).with(rows)        job.run    end end Thursday, February 28, 13

Slide 72

Slide 72 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "asks  Patent  to  overwrite  existing  patents"  do        f        =  './spec/fixtures/patents.csv'        rows  =  CSV.read(f,  :headers  =>  true).map(&:to_hash)          downldr  =  double("Downloader")        downldr.stub(:download_file).and_return(f)          job  =  PatentJob.new(downldr)        Patent.should_receive(:overwrite).with(rows)        job.run    end end Thursday, February 28, 13

Slide 73

Slide 73 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "asks  Patent  to  overwrite  existing  patents"  do        f        =  './spec/fixtures/patents.csv'        rows  =  CSV.read(f,  :headers  =>  true).map(&:to_hash)          downldr  =  double("Downloader")        downldr.stub(:download_file).and_return(f)          job  =  PatentJob.new(downldr)        Patent.should_receive(:overwrite).with(rows)        job.run    end end Thursday, February 28, 13

Slide 74

Slide 74 text

@sandimetz Feb 2013 Dependency Injection creates a Seam Remote Patents Patent Job Local Patents Patent Downloader Thursday, February 28, 13

Slide 75

Slide 75 text

@sandimetz Feb 2013 Patent Job Downloader Test Double Thursday, February 28, 13

Slide 76

Slide 76 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "asks  Patent  to  overwrite  existing  patents"  do        f        =  './spec/fixtures/patents.csv'        rows  =  CSV.read(f,  :headers  =>  true).map(&:to_hash)          downldr  =  double("Downloader")        downldr.stub(:download_file).and_return(f)          job  =  PatentJob.new(downldr)        Patent.should_receive(:overwrite).with(rows)        job.run    end end Thursday, February 28, 13

Slide 77

Slide 77 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "asks  Patent  to  overwrite  existing  patents"  do        f        =  './spec/fixtures/patents.csv'        rows  =  CSV.read(f,  :headers  =>  true).map(&:to_hash)          downldr  =  double("Downloader")        downldr.stub(:download_file).and_return(f)          job  =  PatentJob.new(downldr)        Patent.should_receive(:overwrite).with(rows)        job.run    end end Thursday, February 28, 13

Slide 78

Slide 78 text

@sandimetz Feb 2013 PatentJob  Spec describe  PatentJob  do    it  "asks  Patent  to  overwrite  existing  patents"  do        f        =  './spec/fixtures/patents.csv'        rows  =  CSV.read(f,  :headers  =>  true).map(&:to_hash)          downldr  =  double("Downloader")        downldr.stub(:download_file).and_return(f)          job  =  PatentJob.new(downldr)        Patent.should_receive(:overwrite).with(rows)        job.run    end end Thursday, February 28, 13

Slide 79

Slide 79 text

@sandimetz Feb 2013    def  run        temp  =  download_file        rows  =  parse(temp)        update_patents(rows) Thursday, February 28, 13

Slide 80

Slide 80 text

@sandimetz Feb 2013    def  run        temp  =  download_file    def  run        temp  =  PatentDownloader.new.download_file Thursday, February 28, 13

Slide 81

Slide 81 text

@sandimetz Feb 2013    def  run        temp  =  download_file    def  run        temp  =  PatentDownloader.new.download_file create a dependency Thursday, February 28, 13

Slide 82

Slide 82 text

@sandimetz Feb 2013    def  run        temp  =  download_file    def  run        temp  =  PatentDownloader.new.download class  PatentJob    attr_reader  :downloader      def  initialize(downloader=PatentDownloader.new)        @downloader  =  downloader    end      def  run        temp  =  downloader.download_file Thursday, February 28, 13

Slide 83

Slide 83 text

@sandimetz Feb 2013    def  run        temp  =  download_file    def  run        temp  =  PatentDownloader.new.download class  PatentJob    attr_reader  :downloader      def  initialize(downloader=PatentDownloader.new)        @downloader  =  downloader    end      def  run        temp  =  downloader.download_file inject a dependency Thursday, February 28, 13

Slide 84

Slide 84 text

@sandimetz Feb 2013    def  run        temp  =  download_file    def  run        temp  =  PatentDownloader.new.download class  PatentJob    attr_reader  :downloader      def  initialize(downloader=PatentDownloader.new)        @downloader  =  downloader    end      def  run        temp  =  downloader.download_file depend on the message Thursday, February 28, 13

Slide 85

Slide 85 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Patent Downloader Write PatentDownloader Thursday, February 28, 13

Slide 86

Slide 86 text

@sandimetz Feb 2013 PatentDownloader  Spec describe  PatentDownloader  do    it  "downloads  the  csv  file  from  the  ftp  server"  do        upload_test_file('localhost',  'anon',  'anon',  'patents.csv                downldr  =  PatentDownloader.new        f  =  File.read(downldr.download_file)        f.should  have(250).characters        f.should  include("just  3  minutes")    end end Thursday, February 28, 13

Slide 87

Slide 87 text

@sandimetz Feb 2013 PatentDownloader  Spec describe  PatentDownloader  do    it  "downloads  the  csv  file  from  the  ftp  server"  do        upload_test_file('localhost',  'anon',  'anon',  'patents.csv                downldr  =  PatentDownloader.new        f  =  File.read(downldr.download_file)        f.should  have(250).characters        f.should  include("just  3  minutes")    end end Copied from PatentJob Spec Thursday, February 28, 13

Slide 88

Slide 88 text

@sandimetz Feb 2013 class  PatentDownloader    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end Thursday, February 28, 13

Slide 89

Slide 89 text

@sandimetz Feb 2013 class  PatentDownloader    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end Copied from PatentJob Class Thursday, February 28, 13

Slide 90

Slide 90 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Patent Downloader Thursday, February 28, 13

Slide 91

Slide 91 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 92

Slide 92 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 93

Slide 93 text

@sandimetz Feb 2013 Red Green Refactor Is it DRY? Does it have a one responsibility? Does everything in it change at the same rate? Does it depend on more stable things? Thursday, February 28, 13

Slide 94

Slide 94 text

@sandimetz Feb 2013 Red Green Refactor Is it DRY? Does it have a one responsibility? Does everything in it change at the same rate? Does it depend on more stable things? Thursday, February 28, 13

Slide 95

Slide 95 text

@sandimetz Feb 2013 class  PatentDownloader    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end Thursday, February 28, 13

Slide 96

Slide 96 text

@sandimetz Feb 2013 class  PatentDownloader    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end FTP Thursday, February 28, 13

Slide 97

Slide 97 text

@sandimetz Feb 2013 class  PatentDownloader    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end FTP Configuration Thursday, February 28, 13

Slide 98

Slide 98 text

@sandimetz Feb 2013 Triangle of Responsibility Refactoring Extract Inject Refactor Thursday, February 28, 13

Slide 99

Slide 99 text

@sandimetz Feb 2013 Separate the Responsibilities Remote Patents Patent Job Local Patents Patent Downloader Con g Thursday, February 28, 13

Slide 100

Slide 100 text

@sandimetz Feb 2013 Separate the Responsibilities Remote Patents Patent Job Local Patents Patent Downloader Patent Con g Thursday, February 28, 13

Slide 101

Slide 101 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Patent Downloader Patent Con g Use double at this seam? Thursday, February 28, 13

Slide 102

Slide 102 text

@sandimetz Feb 2013 Write PatentConfig Remote Patents Patent Job Local Patents Patent Downloader Patent Con g Thursday, February 28, 13

Slide 103

Slide 103 text

@sandimetz Feb 2013 PatentConfig  Spec    it  "knows  the  common  configuration  values"  do        conf  =  PatentConfig.new        conf.host.should          eql('localhost')        conf.filename.should  eql('patents.csv')        conf.login.should        eql('anon')        conf.password.should  eql('anon')    end Thursday, February 28, 13

Slide 104

Slide 104 text

@sandimetz Feb 2013 PatentConfig  Spec    it  "knows  the  common  configuration  values"  do        conf  =  PatentConfig.new        conf.host.should          eql('localhost')        conf.filename.should  eql('patents.csv')        conf.login.should        eql('anon')        conf.password.should  eql('anon')    end Thursday, February 28, 13

Slide 105

Slide 105 text

@sandimetz Feb 2013 PatentConfig  Spec    describe  "knows  the  correct  path  for"  do        it  "production"    do            conf  =  PatentConfig.new('production')            conf.path.should  eql('Public/prod')        end          it  "test"  do            conf  =  PatentConfig.new('test')            conf.path.should  eql('Public/test')        end    end Thursday, February 28, 13

Slide 106

Slide 106 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :env      def  initialize(env='production')        @env  =  env    end      def  host        'localhost'    end      def  path        "Public/"  +  ((env  ==  'production')  ?  'prod'  :  'test')    end      def  login        'anon'    end    #  ... Thursday, February 28, 13

Slide 107

Slide 107 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :env      def  initialize(env='production')        @env  =  env    end      def  host        'localhost'    end      def  path        "Public/"  +  ((env  ==  'production')  ?  'prod'  :  'test')    end      def  login        'anon'    end    #  ... Yuck Thursday, February 28, 13

Slide 108

Slide 108 text

@sandimetz Feb 2013 Refactor, not because you know the abstraction, but because you want to nd it. Thursday, February 28, 13

Slide 109

Slide 109 text

@sandimetz Feb 2013 defaults:  &defaults    host:                              localhost    login:                            anon    password:                      anon    filename:                      patents.csv    path:                              Public/test   test:    <<:  *defaults   development:    <<:  *defaults   production:    <<:  *defaults    path:                            Public/prod patent.yml Thursday, February 28, 13

Slide 110

Slide 110 text

@sandimetz Feb 2013 PatentConfig  Spec    describe  "knows  the  correct  path  for"  do        it  "production"    do            conf  =  PatentConfig.new('production')            conf.path.should  eql('Public/prod')        end          it  "test"  do            conf  =  PatentConfig.new('test')            conf.path.should  eql('Public/test')        end    end Thursday, February 28, 13

Slide 111

Slide 111 text

@sandimetz Feb 2013 PatentConfig  Spec    describe  "knows  the  correct  path  for"  do        it  "production"    do            conf  =  PatentConfig.new({env:  'production'})            conf.path.should  eql('Public/prod')        end          it  "test"  do            conf  =  PatentConfig.new({env:  'test'})            conf.path.should  eql('Public/test')        end    end Thursday, February 28, 13

Slide 112

Slide 112 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename]))    end    #  ...    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end Thursday, February 28, 13

Slide 113

Slide 113 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename]))    end    #  ...    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end Thursday, February 28, 13

Slide 114

Slide 114 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename]))    end    #  ...    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end Thursday, February 28, 13

Slide 115

Slide 115 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename]))    end    #  ...    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end Thursday, February 28, 13

Slide 116

Slide 116 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename]))    end    #  ...    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end Thursday, February 28, 13

Slide 117

Slide 117 text

@sandimetz Feb 2013 class  PatentConfig      #  ...    def  host        data[env]['host']    end      def  login        data[env]['login']    end      def  password        data[env]['password']    end      def  path        data[env]['path']    end    #  ...   end Thursday, February 28, 13

Slide 118

Slide 118 text

@sandimetz Feb 2013 class  PatentConfig      #  ...    def  host        data[env]['host']    end      def  login        data[env]['login']    end      def  password        data[env]['password']    end      def  path        data[env]['path']    end    #  ...   end Yuck Thursday, February 28, 13

Slide 119

Slide 119 text

@sandimetz Feb 2013    def  host        data[env]['host']    end        def  define_methods_for_environment        data[env].each  do  |name,  value|            instance_eval  <<-­‐EOS                def  #{name}                                  #  def  host                    "#{value}"                                #      “localhost”                end                                                  #  end            EOS        end    end Thursday, February 28, 13

Slide 120

Slide 120 text

@sandimetz Feb 2013    def  host        data[env]['host']    end        def  define_methods_for_environment        data[env].each  do  |name,  value|            instance_eval  <<-­‐EOS                def  #{name}                                  #  def  host                    "#{value}"                                #      “localhost”                end                                                  #  end            EOS        end    end Thursday, February 28, 13

Slide 121

Slide 121 text

@sandimetz Feb 2013    def  host        data[env]['host']    end        def  define_methods_for_environment        data[env].each  do  |name,  value|            instance_eval  <<-­‐EOS                def  #{name}                                  #  def  host                    "#{value}"                                #      “localhost”                end                                                  #  end            EOS        end    end Thursday, February 28, 13

Slide 122

Slide 122 text

@sandimetz Feb 2013    def  host        data[env]['host']    end        def  define_methods_for_environment        data[env].each  do  |name,  value|            instance_eval  <<-­‐EOS                def  #{name}                                  #  def  host                    "#{value}"                                #      “localhost”                end                                                  #  end            EOS        end    end Thursday, February 28, 13

Slide 123

Slide 123 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})    def  define_methods_for_environment    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end Thursday, February 28, 13

Slide 124

Slide 124 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})    def  define_methods_for_environment    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end Thursday, February 28, 13

Slide 125

Slide 125 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})    def  define_methods_for_environment    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end PatentConfig has nothing to do with Patents. Thursday, February 28, 13

Slide 126

Slide 126 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})    def  define_methods_for_environment    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'patent.yml'}    end end Thursday, February 28, 13

Slide 127

Slide 127 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})    def  define_methods_for_environment    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'config.yml'}    end end Thursday, February 28, 13

Slide 128

Slide 128 text

@sandimetz Feb 2013 class  PatentConfig    attr_reader  :data,  :env      def  initialize(args={})    def  define_methods_for_environment    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'config.yml'}    end end Thursday, February 28, 13

Slide 129

Slide 129 text

@sandimetz Feb 2013 class  Configuration    attr_reader  :data,  :env      def  initialize(args={})    def  define_methods_for_environment    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'config.yml'}    end end Thursday, February 28, 13

Slide 130

Slide 130 text

@sandimetz Feb 2013 class  Configuration    attr_reader  :data,  :env    def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename]))        define_methods_for_environment    end    def  define_methods_for_environment        data[env].each  do  |name,  value|            instance_eval  <<-­‐EOS                def  #{name}                                  #  def  host                    "#{value}"                                #      “localhost”                end                                                  #  end            EOS        end    end    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'config.yml'}    end end Entire Configuration class Thursday, February 28, 13

Slide 131

Slide 131 text

@sandimetz Feb 2013 Configuration is an abstraction has a single responsibility is open for extension and closed for modi cation Thursday, February 28, 13

Slide 132

Slide 132 text

@sandimetz Feb 2013 Use Configuration in PatentDownloader Remote Patents Patent Job Local Patents Patent Downloader Con guration Thursday, February 28, 13

Slide 133

Slide 133 text

@sandimetz Feb 2013 class  PatentDownloader    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end Last we saw Thursday, February 28, 13

Slide 134

Slide 134 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end Thursday, February 28, 13

Slide 135

Slide 135 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end Thursday, February 28, 13

Slide 136

Slide 136 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end end Thursday, February 28, 13

Slide 137

Slide 137 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                tempname)        end        tempname    end end Thursday, February 28, 13

Slide 138

Slide 138 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Patent Downloader Con guration Thursday, February 28, 13

Slide 139

Slide 139 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                tempname)        end        tempname    end end Thursday, February 28, 13

Slide 140

Slide 140 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                tempname)        end        tempname    end end PatentDownloader has nothing to do with Patents. Thursday, February 28, 13

Slide 141

Slide 141 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                tempname)        end        tempname    end end Thursday, February 28, 13

Slide 142

Slide 142 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                tempname)        end        tempname    end end Thursday, February 28, 13

Slide 143

Slide 143 text

@sandimetz Feb 2013 class  PatentDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                tempname)        end        tempname    end end Thursday, February 28, 13

Slide 144

Slide 144 text

@sandimetz Feb 2013 class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                tempname)        end        tempname    end end Thursday, February 28, 13

Slide 145

Slide 145 text

@sandimetz Feb 2013 class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new({filename:  'patent.yml'}))        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                tempname)        end        tempname    end end ??? Thursday, February 28, 13

Slide 146

Slide 146 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Patent Downloader Con guration Thursday, February 28, 13

Slide 147

Slide 147 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Con guration Thursday, February 28, 13

Slide 148

Slide 148 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Ftp Downloader Con guration Thursday, February 28, 13

Slide 149

Slide 149 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 150

Slide 150 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 151

Slide 151 text

@sandimetz Feb 2013 Red Green Refactor Is it DRY? Does it have a one responsibility? Does everything in it change at the same rate? Does it depend on more stable things? Thursday, February 28, 13

Slide 152

Slide 152 text

@sandimetz Feb 2013 Red Green Refactor Is it DRY? Does it have a one responsibility? Does everything in it change at the same rate? Does it depend on more stable things? Thursday, February 28, 13

Slide 153

Slide 153 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader    def  initialize(downloader=FtpDownloader.new)        @downloader  =  downloader    end class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new(                                                  {filename:  'patent.yml'}))        @config  =  config    end class  Configuration    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename])) patent.yml v Thursday, February 28, 13

Slide 154

Slide 154 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader    def  initialize(downloader=FtpDownloader.new)        @downloader  =  downloader    end class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new(                                                  {filename:  'patent.yml'}))        @config  =  config    end class  Configuration    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename])) patent.yml v PatentJob depends on FtpDownloader Thursday, February 28, 13

Slide 155

Slide 155 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader    def  initialize(downloader=FtpDownloader.new)        @downloader  =  downloader    end class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new(                                                  {filename:  'patent.yml'}))        @config  =  config    end class  Configuration    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename])) patent.yml v FtpDownloader depends on Configuration Thursday, February 28, 13

Slide 156

Slide 156 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader    def  initialize(downloader=FtpDownloader.new)        @downloader  =  downloader    end class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new(                                                  {filename:  'patent.yml'}))        @config  =  config    end class  Configuration    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename])) patent.yml v FtpDownloader  also depends on patent.yml Thursday, February 28, 13

Slide 157

Slide 157 text

@sandimetz Feb 2013 Less More Likelihood of Change Configuration Ftp Downloader PatentJob patent.yml Thursday, February 28, 13

Slide 158

Slide 158 text

@sandimetz Feb 2013 Less More Likelihood of Change Configuration PatentJob patent.yml Always depend on things to your left Ftp Downloader Thursday, February 28, 13

Slide 159

Slide 159 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader    def  initialize(downloader=FtpDownloader.new)        @downloader  =  downloader    end class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new(                                                  {filename:  'patent.yml'}))        @config  =  config    end class  Configuration    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename])) patent.yml v Thursday, February 28, 13

Slide 160

Slide 160 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader    def  initialize(downloader=FtpDownloader.new)        @downloader  =  downloader    end class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new(                                                  {filename:  'patent.yml'}))        @config  =  config    end class  Configuration    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename])) patent.yml v Thursday, February 28, 13

Slide 161

Slide 161 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader    def  initialize(downloader=FtpDownloader.new)        @downloader  =  downloader    end class  FtpDownloader    attr_reader  :config    def  initialize(config=Configuration.new(                                                  {filename:  'patent.yml'}))        @config  =  config    end class  Configuration    attr_reader  :data,  :env      def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename])) patent.yml v Thursday, February 28, 13

Slide 162

Slide 162 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end Thursday, February 28, 13

Slide 163

Slide 163 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end Thursday, February 28, 13

Slide 164

Slide 164 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end Thursday, February 28, 13

Slide 165

Slide 165 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end Thursday, February 28, 13

Slide 166

Slide 166 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end Thursday, February 28, 13

Slide 167

Slide 167 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end Thursday, February 28, 13

Slide 168

Slide 168 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Ftp Downloader Con guration Thursday, February 28, 13

Slide 169

Slide 169 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Ftp Downloader Con guration Thursday, February 28, 13

Slide 170

Slide 170 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 171

Slide 171 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 172

Slide 172 text

@sandimetz Feb 2013 Red Green Refactor Is it DRY? Does it have a one responsibility? Does everything in it change at the same rate? Does it depend on more stable things? Thursday, February 28, 13

Slide 173

Slide 173 text

@sandimetz Feb 2013 Red Green Refactor Is it DRY? Does it have a one responsibility? Does everything in it change at the same rate? Does it depend on more stable things? Thursday, February 28, 13

Slide 174

Slide 174 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end Thursday, February 28, 13

Slide 175

Slide 175 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end Thursday, February 28, 13

Slide 176

Slide 176 text

@sandimetz Feb 2013 defaults:  &defaults    host:                              localhost    login:                            anon    password:                      anon    filename:                      patents.csv    path:                              Public/test    downloader_class:      FtpDownloader   test:    <<:  *defaults   development:    <<:  *defaults   production:    <<:  *defaults    path:                            Public/prod patent.yml Thursday, February 28, 13

Slide 177

Slide 177 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  FtpDownloader.new(config)    end   Thursday, February 28, 13

Slide 178

Slide 178 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader      def  initialize(opts={})        config              =  opts[:config]          ||=  \                                  Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                  config.connector_class.const_get.new(config)    end   Thursday, February 28, 13

Slide 179

Slide 179 text

@sandimetz Feb 2013 Remote Patents Patent Job Local Patents Ftp Downloader Con guration Thursday, February 28, 13

Slide 180

Slide 180 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 181

Slide 181 text

@sandimetz Feb 2013 Before Thursday, February 28, 13

Slide 182

Slide 182 text

@sandimetz Feb 2013 class  PatentJob    def  run        update_patents(parse(download_file))    end        def  download_file        temp  =  Tempfile.new('patents')        tempname  =  temp.path        temp.close        Net::FTP.open('localhost',  'anon',  'anon')  do  |ftp|            ftp.getbinaryfile('Public/prod/patents.csv',  tempname)        end        tempname    end      def  parse(temp)        CSV.read(temp,  :headers  =>  true).map(&:to_hash)    end      def  update_patents(rows)        Patent.overwrite(rows)    end   end Thursday, February 28, 13

Slide 183

Slide 183 text

@sandimetz Feb 2013 After Thursday, February 28, 13

Slide 184

Slide 184 text

@sandimetz Feb 2013 class  PatentJob    attr_reader  :downloader    def  initialize(opts={})        config            =  opts[:config]          ||=  \                                      Configuration.new({filename:  'patent.yml'})        @downloader  =  opts[:downloader]  ||=  \                                    config.connector_class.const_get.new(config)    end      def  run        update_patents(parse(downloader.download_file))    end      def  parse(temp)        CSV.read(temp,  :headers  =>  true).map(&:to_hash)    end      def  update_patents(rows)        Patent.overwrite(rows)    end   end Thursday, February 28, 13

Slide 185

Slide 185 text

@sandimetz Feb 2013 class  FTPDownloader    attr_reader  :config    def  initialize(config)        @config  =  config    end    def  download_file        temp  =  Tempfile.new(config.filename)        tempname  =  temp.path        temp.close        Net::FTP.open(config.host,                                      config.login,                                      config.password)  do  |ftp|            ftp.getbinaryfile(File.join(config.path,  config.filename),                                                                tempname)        end        tempname    end end Thursday, February 28, 13

Slide 186

Slide 186 text

@sandimetz Feb 2013 class  Configuration    attr_reader  :data,  :env    def  initialize(args={})        args    =  defaults.merge(args)        @env    =  args[:env]        @data  =  YAML::load_file(File.join(args[:path],                                                                            args[:filename]))        define_methods_for_environment    end    def  define_methods_for_environment        data[env].each  do  |name,  value|            instance_eval  <<-­‐EOS                def  #{name}                                  #  def  host                    "#{value}"                                #      data[env]['host']                end                                                  #  end            EOS        end    end    def  defaults        {env:            'production',          path:          File.join('config'),          filename:  'config.yml'}    end end Thursday, February 28, 13

Slide 187

Slide 187 text

@sandimetz Feb 2013 ActTraining RequiredUserNoti cation OESO RCC PayrollFunding ActiveKeyPerson Award Many New Jobs Thursday, February 28, 13

Slide 188

Slide 188 text

@sandimetz Feb 2013 SFTP Web Scraper via Mechanize Sql File Copy Many New Downloaders Thursday, February 28, 13

Slide 189

Slide 189 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 190

Slide 190 text

@sandimetz Feb 2013 The future is coming Thursday, February 28, 13

Slide 191

Slide 191 text

@sandimetz Feb 2013 Let’s enjoy it Thursday, February 28, 13

Slide 192

Slide 192 text

@sandimetz Feb 2013 Thursday, February 28, 13

Slide 193

Slide 193 text

Thanks Sandi Metz @sandimetz https://joind.in/7876 Thursday, February 28, 13

Slide 194

Slide 194 text

http://www. ickr.com/photos/freshwater2006/693945631/ http://www. ickr.com/photos/shainerin/3033898043/ http://www. ickr.com/photos/87255087@N00/4184665341/ http://www. ickr.com/photos/robwallace/154301240/ http://www. ickr.com/photos/jpstanley/239788572/ http://www. ickr.com/photos/nationalgalleries/3110282571/ http://www. ickr.com/photos/familymwr/5112367957/ http://www. ickr.com/photos/marine_corps/5132830788/ http://www. ickr.com/photos/atolsma/6945061383/ http://www. ickr.com/photos/whiteafrican/409072927/ http://www. ickr.com/photos/tabor-roeder/5652475824/in/photostream/ http://www. ickr.com/photos/76526962@N00/7982132871/in/photostream/ http://www. ickr.com/photos/johnspooner/2899977876/ http://www. ickr.com/photos/bike/7721413266/ http://www. ickr.com/photos/stevengrayphotography/6893448452/ http://www. ickr.com/photos/thelearningcurvedotca/5793648808/ http://www. ickr.com/photos/whateverthing/241245603/ Photo Credits Thursday, February 28, 13

Slide 195

Slide 195 text

@sandimetz Feb 2013 http://poodr.info Thursday, February 28, 13

Slide 196

Slide 196 text

@sandimetz Feb 2013 Hot off the press http://poodr.info Thursday, February 28, 13

Slide 197

Slide 197 text

Questions? https://joind.in/7876 Thursday, February 28, 13

Slide 198

Slide 198 text

@sandimetz Feb 2013 http://poodr.info Thursday, February 28, 13