Slide 1

Slide 1 text

@steffoz stefanoverna.com What Gems are Building Ruby Gems

Slide 2

Slide 2 text

@steffoz stefanoverna.com What gems are

Slide 3

Slide 3 text

@steffoz stefanoverna.com •A Gem is a software package with a name, version, and platform •Gems split out reusable functionality that others can use •Some gems also provide command line utilities Gems are build using the Rubygems tool (included with Ruby itself).

Slide 4

Slide 4 text

@steffoz stefanoverna.com Rubygems provides a gem executable that helps build, download and install gems.

Slide 5

Slide 5 text

@steffoz stefanoverna.com % tree freewill freewill/ ├── lib/ │ └── freewill.rb └── freewill.gemspec So, this is your first gem. Contains information about the gem’s files, test information, platform, version number, author’s email and name. Contains the code of the gem.

Slide 6

Slide 6 text

@steffoz stefanoverna.com % tree freewill freewill/ ├── bin/ │ └── freewill ├── lib/ │ └── freewill.rb ├── test/ │ └── test_freewill.rb ├── README ├── Rakefile └── freewill.gemspec This is a more usual gem. Contains the executables of the gem. Contains the tests of the gem. Contains the docs of the gem. Helps automating gem release, testing and generate docs.

Slide 7

Slide 7 text

@steffoz stefanoverna.com Gem::Specification.new do |s| s.name = 'freewill' s.version = '1.0.0' s.date = '2010-04-27' s.summary = "Freewill!" s.description = "I will choose Freewill!" s.authors = ["Nick Quaranto"] s.email = '[email protected]' s.homepage = 'http://example.com' s.files = ["lib/freewill.rb"] s.add_dependency 'activesupport' end

Slide 8

Slide 8 text

@steffoz stefanoverna.com › gem build freewill.gemspec Successfully built RubyGem Name: freewill Version: 1.0.0 File: redpomo-1.0.0.gem › ls redpomo-1.0.0.gem

Slide 9

Slide 9 text

@steffoz stefanoverna.com Gems are just tarballs.

Slide 10

Slide 10 text

@steffoz stefanoverna.com › tar zxvf freewill-1.0.0.gem x data.tar.gz x metadata.gz tar zxvf data.tar.gz x lib/freewill.rb [...] › gunzip metadata.gz › cat metadata --- !ruby/object:Gem::Specification name: redpomo version: !ruby/object:Gem::Version version: 0.0.7 prerelease: platform: ruby authors: - Stefano Verna autorequire: bindir: bin [...] All the gem files are placed in an inner data.tar.gz tarball A YAML serialized version of a Ruby class, containing all the gemspec infos.

Slide 11

Slide 11 text

@steffoz stefanoverna.com How to use gems

Slide 12

Slide 12 text

@steffoz stefanoverna.com require "foo/bar" Loads the given file, returning true if successful and false if the feature is already loaded. If the filename does not resolve to an absolute path, it will be searched for in the directories listed in $LOAD_PATH.

Slide 13

Slide 13 text

@steffoz stefanoverna.com › irb irb(main)> puts $LOAD_PATH ~/.rbenv/versions/1.9.3-p0/lib/ruby/1.9.1 [...]

Slide 14

Slide 14 text

@steffoz stefanoverna.com gem install "rails" The install command installs local or remote gems into the local gem repository.

Slide 15

Slide 15 text

@steffoz stefanoverna.com Rubygems patches Kernel#require with a pre-filter that adds /lib directories of gems to your $LOAD_PATH

Slide 16

Slide 16 text

@steffoz stefanoverna.com › irb irb(main)> puts $LOAD_PATH ~/.rbenv/versions/1.9.3-p0/lib/ruby/1.9.1 [...] irb(main)> require 'active_support' true irb(main)> puts $LOAD_PATH ~/.rbenv/versions/1.9.3-p0/lib/ruby/gems/1.9.1/gems/i18n-0.6.0/lib ~/.rbenv/versions/1.9.3-p0/lib/ruby/gems/1.9.1/gems/activesupport-3.2.3/lib ~/.rbenv/versions/1.9.3-p0/lib/ruby/1.9.1

Slide 17

Slide 17 text

@steffoz stefanoverna.com Lets build a gem!

Slide 18

Slide 18 text

@steffoz stefanoverna.com

Slide 19

Slide 19 text

@steffoz stefanoverna.com texticle comfortable-mexican-sofa lolita tranny cocaine smurf hoe whoa

Slide 20

Slide 20 text

@steffoz stefanoverna.com redditor

Slide 21

Slide 21 text

@steffoz stefanoverna.com > redditor > redditor hot|top|new|controversial > redditor hot Programming > redditor top fffffffuuuuuuuuuuuu The Goal (we'll miss)

Slide 22

Slide 22 text

@steffoz stefanoverna.com What we need AKA The "Ruby Toolbox" phase

Slide 23

Slide 23 text

@steffoz stefanoverna.com

Slide 24

Slide 24 text

@steffoz stefanoverna.com

Slide 25

Slide 25 text

@steffoz stefanoverna.com 1 HTTP Client

Slide 26

Slide 26 text

@steffoz stefanoverna.com 1 HTTP Client RestClient Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions.

Slide 27

Slide 27 text

@steffoz stefanoverna.com

Slide 28

Slide 28 text

@steffoz stefanoverna.com 1 require 'rest_client' response = RestClient.get 'http://example.com/resource' response.code # 200 response.cookies # {"Foo"=>"BAR", "QUUX"=>"QUUUUX"} response.headers # {:content_type=>"text/html; charset=utf-8", :cache_control=>"private" ... response.to_s # \n\n\n

Slide 29

Slide 29 text

@steffoz stefanoverna.com 2 JSON Parser

Slide 30

Slide 30 text

@steffoz stefanoverna.com 2 JSON Parser JSON This is a implementation of the JSON specification according to RFC 4627

Slide 31

Slide 31 text

@steffoz stefanoverna.com 2 require 'json' JSON.parse '{ "foo": "bar" }' # {"foo"=>"bar"} { foo: "bar" }.to_json # {"foo":"bar"}

Slide 32

Slide 32 text

@steffoz stefanoverna.com 3 URL Shortener

Slide 33

Slide 33 text

@steffoz stefanoverna.com 3 URL Shortener googl Google URL Shortener API in Ruby

Slide 34

Slide 34 text

@steffoz stefanoverna.com 3 require 'googl' url = Googl.shorten('http://www.zigotto.com') url.short_url # "http://goo.gl/ump4S" url.long_url # "http://www.zigotto.com/" url.qr_code # "http://goo.gl/ump4S.qr" url.info # "http://goo.gl/ump4S.info"

Slide 35

Slide 35 text

@steffoz stefanoverna.com 4 CLI Table Drawer

Slide 36

Slide 36 text

@steffoz stefanoverna.com 4 CLI Table Drawer command_line_reporter This gem makes it easy to provide a report while your ruby script is executing

Slide 37

Slide 37 text

@steffoz stefanoverna.com 4 require 'command_line_reporter' include CommandLineReporter table :border => true do row :color => 'red' do column 'Name', :width => 20, :color => 'blue' column 'Address', :width => 20 column 'City', :width => 15 end row do column 'Caeser' column '1 Appian Way' column 'Rome' end row do column 'Richard Feynman' column '1 Golden Gate' column 'Quantum Field' end end

Slide 38

Slide 38 text

@steffoz stefanoverna.com 5 CLI

Slide 39

Slide 39 text

@steffoz stefanoverna.com 5 CLI thor A simple and efficient tool for building self- documenting command line utilities

Slide 40

Slide 40 text

@steffoz stefanoverna.com 5 class App < Thor map "-L" => :list desc "install APP_NAME", "install an app" method_options :version def install(name) version = options[:version] # do something end desc "list [SEARCH]", "list all the apps" def list(search = "") # list everything end end

Slide 41

Slide 41 text

@steffoz stefanoverna.com Exploration phase https://gist.github.com/2878853

Slide 42

Slide 42 text

@steffoz stefanoverna.com Ok, now let's make the world a better place. Let's release redditor to the public!

Slide 43

Slide 43 text

@steffoz stefanoverna.com gem install ore

Slide 44

Slide 44 text

@steffoz stefanoverna.com mine redditor \ -D 'Browse Reddit from CLI' \ -a 'Stefano Verna' \ -e '[email protected]' \ --markdown \ --yard \ --bin \ --bundler

Slide 45

Slide 45 text

@steffoz stefanoverna.com # redditor.gemspec gem.add_dependency 'activesupport' gem.add_dependency 'thor' gem.add_dependency 'command_line_reporter' gem.add_dependency 'json' gem.add_dependency 'googl' gem.add_dependency 'rest-client' gem.add_dependency 'launchy'

Slide 46

Slide 46 text

@steffoz stefanoverna.com # lib/redditor/cli.rb require 'thor' module Redditor class CLI < Thor desc "hot", "Shows the hottest stories on Reddit" def hot # COPY AND PASTE? HELL YEAH! end end end

Slide 47

Slide 47 text

@steffoz stefanoverna.com # bin/redditor #!/usr/bin/env ruby require 'redditor/cli' Redditor::CLI.start

Slide 48

Slide 48 text

@steffoz stefanoverna.com Done! rake build and go celebrate! https://gist.github.com/2878875

Slide 49

Slide 49 text

@steffoz stefanoverna.com Nope. You need to test it, bitch.

Slide 50

Slide 50 text

@steffoz stefanoverna.com Test Driven Development

Slide 51

Slide 51 text

@steffoz stefanoverna.com Behaviour Driven Development

Slide 52

Slide 52 text

@steffoz stefanoverna.com Behaviour Driven Development

Slide 53

Slide 53 text

@steffoz stefanoverna.com spec/fixtures/output/hot.txt

Slide 54

Slide 54 text

@steffoz stefanoverna.com # spec/acceptance/cli_usage_spec.rb require 'spec_helper' describe "CLI usage" do describe "redditor hot" do it "returns the hottest news on Reddit" do redditor "hot" output.should == read_fixture("output/hot.txt") end end end

Slide 55

Slide 55 text

@steffoz stefanoverna.com

Slide 56

Slide 56 text

@steffoz stefanoverna.com

Slide 57

Slide 57 text

@steffoz stefanoverna.com def hot stories = Story.find(type: :hot) StoriesPrinter.output(stories) end One class, one responsibility (MVC also)

Slide 58

Slide 58 text

@steffoz stefanoverna.com # spec/cli_spec.rb require 'spec_helper' require 'redditor/cli' describe Redditor::CLI do describe ".hot" do it "fetches /hot.json stories, and prints them out" do stories = double("stories array") Redditor::Story.stub(:find).with(type: :hot).and_return(stories) Redditor::StoriesPrinter.should_receive(:output).with(stories) subject.hot end end end

Slide 59

Slide 59 text

@steffoz stefanoverna.com # spec/cli_spec.rb require 'spec_helper' require 'redditor/cli' describe Redditor::CLI do describe ".hot" do it "fetches /hot.json stories, and prints them out" do stories = double("stories array") Redditor::Story.stub(:find).with(type: :hot).and_return(stories) Redditor::StoriesPrinter.should_receive(:output).with(stories) subject.hot end end end double  generic  term  for  any  kind  of  pretend  object  used  in   place  of  a  real  object  for  testing  purposes.  The  name  comes   from  the  notion  of  a  Stunt  Double  in  movies.

Slide 60

Slide 60 text

@steffoz stefanoverna.com # spec/cli_spec.rb require 'spec_helper' require 'redditor/cli' describe Redditor::CLI do describe ".hot" do it "fetches /hot.json stories, and prints them out" do stories = double("stories array") Redditor::Story.stub(:find).with(type: :hot).and_return(stories) Redditor::StoriesPrinter.should_receive(:output).with(stories) subject.hot end end end

Slide 61

Slide 61 text

@steffoz stefanoverna.com

Slide 62

Slide 62 text

@steffoz stefanoverna.com

Slide 63

Slide 63 text

@steffoz stefanoverna.com The acceptance test is still failing. Acceptance tests are reality checks.

Slide 64

Slide 64 text

@steffoz stefanoverna.com It's time to start a new inner loop to think about what Story#find should do.

Slide 65

Slide 65 text

@steffoz stefanoverna.com It's time to start a new inner loop to think about what Story#find should do.

Slide 66

Slide 66 text

@steffoz stefanoverna.com # spec/story_spec.rb require 'spec_helper' require 'redditor/story' describe Redditor::Story do describe "#find" do it "fetches stories for the specified filter, mapping them into Stories" do url = double("Reddit URL") raw_story = double("story hash") story = double("Story") Redditor::Story.stub(:url_for).with(foo: :bar).and_return(url) Redditor::Story.stub(:fetch).with(url).and_return([ raw_story ]) Redditor::Story.stub(:new).with(raw_story).and_return(story) result = Redditor::Story.find(foo: :bar) result.should have(1).story result.first.should == story end end end

Slide 67

Slide 67 text

@steffoz stefanoverna.com # lib/redditor/story.rb module Redditor class Story def self.find(filters) fetch(url_for(filters)).map do |raw_story| Story.new(raw_story) end end end end

Slide 68

Slide 68 text

@steffoz stefanoverna.com

Slide 69

Slide 69 text

@steffoz stefanoverna.com

Slide 70

Slide 70 text

@steffoz stefanoverna.com The acceptance test is still failing. Now let's define Story#url_for

Slide 71

Slide 71 text

@steffoz stefanoverna.com Maybe next time :) Until then, https://gist.github.com/2878916

Slide 72

Slide 72 text

Thanks! @steffoz U stefanoverna.com Y