divining_rod 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +40 -26
- data/VERSION +1 -1
- data/divining_rod.gemspec +2 -2
- data/lib/divining_rod.rb +42 -24
- data/spec/basic_spec.rb +89 -24
- metadata +2 -2
    
        data/README.markdown
    CHANGED
    
    | @@ -1,13 +1,6 @@ | |
| 1 1 | 
             
            # Divining Rod
         | 
| 2 2 |  | 
| 3 | 
            -
            A tool to help format your sites mobile pages. | 
| 4 | 
            -
            views like this
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                <%- if request.dv_profile.youtube_capable? %>
         | 
| 7 | 
            -
                  <%= link_to "YouTube Video", @link.youtube_url %>
         | 
| 8 | 
            -
                <%- else %>
         | 
| 9 | 
            -
                  Sorry, you have a shitty phone.
         | 
| 10 | 
            -
                <% end %>
         | 
| 3 | 
            +
            A tool to help format your sites mobile pages.
         | 
| 11 4 |  | 
| 12 5 | 
             
            ## Installation
         | 
| 13 6 |  | 
| @@ -15,35 +8,56 @@ views like this | |
| 15 8 |  | 
| 16 9 | 
             
            ## Usage
         | 
| 17 10 |  | 
| 18 | 
            -
             | 
| 11 | 
            +
            _initializers/divining\_rod.rb_
         | 
| 19 12 |  | 
| 20 13 | 
             
                DiviningRod::Matchers.define do |map|
         | 
| 14 | 
            +
                    # map.ua /user_agent_regex/, :format, :tags => []
         | 
| 21 15 | 
             
                    map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube_capable]
         | 
| 22 | 
            -
                    map.ua /Android/, :webkit, :tags => [:youtube_capable, :google_gears]
         | 
| 23 | 
            -
                    map. | 
| 16 | 
            +
                    map.ua /Android/, :webkit, :tags => [:android, :youtube_capable, :google_gears]
         | 
| 17 | 
            +
                    map.subdomain /wap/, :wap, :tags => [:crappy_old_phone]
         | 
| 18 | 
            +
                    
         | 
| 19 | 
            +
                    # Enable this to forces a default format if unmatched
         | 
| 20 | 
            +
                    # otherwise it will return the request.format
         | 
| 21 | 
            +
                    # map.default :html 
         | 
| 24 22 | 
             
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            _initializers/mime\_types.rb_
         | 
| 25 25 |  | 
| 26 | 
            -
                 | 
| 27 | 
            -
                 | 
| 28 | 
            -
             | 
| 26 | 
            +
                Mime::Type.register_alias "text/html", :webkit
         | 
| 27 | 
            +
                
         | 
| 28 | 
            +
            _app/controllers/mobile\_controller.rb_
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                class MobileController < ApplicationController
         | 
| 31 | 
            +
                  before_filter :detect_mobile_type
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  ....
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  private
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def detect_mobile_type
         | 
| 38 | 
            +
                    # If the profile isn't matched it defaults to request.format
         | 
| 39 | 
            +
                    @profile = DiviningRod::Profile.new(request).format
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
                
         | 
| 44 | 
            +
            _app/views/mobile/show.webkit.html_
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                <%- if @profile.iphone? %>
         | 
| 47 | 
            +
                  <%= link_to "Install our iPhone App in the AppStore", @iPhone_appstore_url %>
         | 
| 48 | 
            +
                <%- elsif @profile.android? %>
         | 
| 49 | 
            +
                  <%= link_to "Direct download", @android_app_url %>
         | 
| 50 | 
            +
                <% end %>
         | 
| 51 | 
            +
             | 
| 29 52 |  | 
| 30 53 | 
             
            ## Note on the development
         | 
| 31 54 |  | 
| 32 | 
            -
            This is still very much in beta, but we are using  | 
| 33 | 
            -
            to keep the API the same.
         | 
| 55 | 
            +
            This is still very much in beta, but we are using it in production. As such we plan
         | 
| 56 | 
            +
            to do our best to keep the API the same.
         | 
| 34 57 |  | 
| 35 58 | 
             
            The user agent definitions will be updated here later this week.
         | 
| 36 59 |  | 
| 37 | 
            -
             | 
| 38 | 
            -
            ### Note on Patches/Pull Requests
         | 
| 39 | 
            -
             
         | 
| 40 | 
            -
            * Fork the project.
         | 
| 41 | 
            -
            * Make your feature addition or bug fix.
         | 
| 42 | 
            -
            * Add tests for it. This is important so I don't break it in a
         | 
| 43 | 
            -
              future version unintentionally.
         | 
| 44 | 
            -
            * Commit, do not mess with rakefile, version, or history.
         | 
| 45 | 
            -
              (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
         | 
| 46 | 
            -
            * Send me a pull request. Bonus points for topic branches.
         | 
| 60 | 
            +
            ## Todo
         | 
| 47 61 |  | 
| 48 62 | 
             
            ### Copyright
         | 
| 49 63 |  | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.3.0
         | 
    
        data/divining_rod.gemspec
    CHANGED
    
    | @@ -5,11 +5,11 @@ | |
| 5 5 |  | 
| 6 6 | 
             
            Gem::Specification.new do |s|
         | 
| 7 7 | 
             
              s.name = %q{divining_rod}
         | 
| 8 | 
            -
              s.version = "0. | 
| 8 | 
            +
              s.version = "0.3.0"
         | 
| 9 9 |  | 
| 10 10 | 
             
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 11 11 | 
             
              s.authors = ["Mark Percival"]
         | 
| 12 | 
            -
              s.date = %q{2010-02- | 
| 12 | 
            +
              s.date = %q{2010-02-18}
         | 
| 13 13 | 
             
              s.description = %q{A mobile phone web request profiler using definitions that look like rails routes}
         | 
| 14 14 | 
             
              s.email = %q{mark@mpercival.com}
         | 
| 15 15 | 
             
              s.extra_rdoc_files = [
         | 
    
        data/lib/divining_rod.rb
    CHANGED
    
    | @@ -1,12 +1,13 @@ | |
| 1 1 | 
             
            require 'yaml'
         | 
| 2 2 |  | 
| 3 3 | 
             
            module DiviningRod
         | 
| 4 | 
            -
             | 
| 4 | 
            +
             | 
| 5 5 | 
             
              class Profile
         | 
| 6 | 
            -
             | 
| 6 | 
            +
             | 
| 7 7 | 
             
                attr_reader :match
         | 
| 8 | 
            -
             | 
| 8 | 
            +
             | 
| 9 9 | 
             
                def initialize(request)
         | 
| 10 | 
            +
                  @request = request
         | 
| 10 11 | 
             
                  matchers.each do |matcher|
         | 
| 11 12 | 
             
                    @match = matcher if matcher.matches?(request)
         | 
| 12 13 | 
             
                    break if @match
         | 
| @@ -15,43 +16,51 @@ module DiviningRod | |
| 15 16 | 
             
                end
         | 
| 16 17 |  | 
| 17 18 | 
             
                def group
         | 
| 18 | 
            -
                  @match | 
| 19 | 
            +
                  if @match
         | 
| 20 | 
            +
                    @match.group
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    @request.format
         | 
| 23 | 
            +
                  end
         | 
| 19 24 | 
             
                end
         | 
| 20 25 | 
             
                alias_method :format, :group
         | 
| 21 | 
            -
             | 
| 26 | 
            +
             | 
| 22 27 | 
             
                def recognized?
         | 
| 23 28 | 
             
                  !!@match
         | 
| 24 29 | 
             
                end
         | 
| 25 | 
            -
             | 
| 30 | 
            +
             | 
| 26 31 | 
             
                def method_missing(meth)
         | 
| 27 32 | 
             
                  if meth.to_s.match(/(.+)\?$/)
         | 
| 28 33 | 
             
                    tag = $1
         | 
| 29 | 
            -
                     | 
| 34 | 
            +
                    if @match
         | 
| 35 | 
            +
                      @match.tags.include?(tag.to_s) ||  @match.tags.include?(tag.to_sym) || @match.tags == tag
         | 
| 36 | 
            +
                    else
         | 
| 37 | 
            +
                      false
         | 
| 38 | 
            +
                    end
         | 
| 30 39 | 
             
                  else
         | 
| 31 40 | 
             
                    super
         | 
| 32 41 | 
             
                  end
         | 
| 33 42 | 
             
                end
         | 
| 34 | 
            -
             | 
| 43 | 
            +
             | 
| 35 44 | 
             
                def matchers
         | 
| 36 45 | 
             
                  DiviningRod::Matchers.definitions || []
         | 
| 37 46 | 
             
                end
         | 
| 38 | 
            -
             | 
| 47 | 
            +
             | 
| 39 48 | 
             
              end
         | 
| 40 | 
            -
             | 
| 49 | 
            +
             | 
| 41 50 | 
             
              class Matchers
         | 
| 42 | 
            -
             | 
| 51 | 
            +
             | 
| 43 52 | 
             
                class << self
         | 
| 44 | 
            -
             | 
| 53 | 
            +
             | 
| 45 54 | 
             
                  attr_accessor :definitions
         | 
| 46 | 
            -
             | 
| 55 | 
            +
             | 
| 47 56 | 
             
                  def define
         | 
| 48 57 | 
             
                    yield(self)
         | 
| 49 58 | 
             
                  end
         | 
| 50 | 
            -
             | 
| 59 | 
            +
             | 
| 51 60 | 
             
                  def clear_definitions
         | 
| 52 61 | 
             
                    @definitions = []
         | 
| 53 62 | 
             
                  end
         | 
| 54 | 
            -
             | 
| 63 | 
            +
             | 
| 55 64 | 
             
                  def ua(pattern, group, opts={})
         | 
| 56 65 | 
             
                    @definitions ||= []
         | 
| 57 66 | 
             
                    @definitions << Definition.new(group, opts) { |request|
         | 
| @@ -61,29 +70,38 @@ module DiviningRod | |
| 61 70 | 
             
                    }
         | 
| 62 71 | 
             
                  end
         | 
| 63 72 |  | 
| 73 | 
            +
                  def subdomain(pattern, group, opts={})
         | 
| 74 | 
            +
                    @definitions ||= []
         | 
| 75 | 
            +
                    @definitions << Definition.new(group, opts) { |request|
         | 
| 76 | 
            +
                      if pattern.match(request.subdomains[0])
         | 
| 77 | 
            +
                        true
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
                    }
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
             | 
| 64 82 | 
             
                  def default(group, opts = {})
         | 
| 65 83 | 
             
                    @definitions ||= []
         | 
| 66 84 | 
             
                    @definitions << Definition.new(group, opts) { |request| true }
         | 
| 67 85 | 
             
                  end
         | 
| 68 | 
            -
             | 
| 86 | 
            +
             | 
| 69 87 | 
             
                end
         | 
| 70 | 
            -
             | 
| 88 | 
            +
             | 
| 71 89 | 
             
              end
         | 
| 72 | 
            -
             | 
| 90 | 
            +
             | 
| 73 91 | 
             
              class Definition
         | 
| 74 | 
            -
             | 
| 92 | 
            +
             | 
| 75 93 | 
             
                attr_accessor :prc, :tags, :group
         | 
| 76 | 
            -
             | 
| 94 | 
            +
             | 
| 77 95 | 
             
                def initialize(group, opts={}, &blk)
         | 
| 78 96 | 
             
                  @group = group
         | 
| 79 97 | 
             
                  @tags = opts[:tags] || []
         | 
| 80 98 | 
             
                  @prc = blk
         | 
| 81 99 | 
             
                end
         | 
| 82 | 
            -
             | 
| 100 | 
            +
             | 
| 83 101 | 
             
                def matches?(request)
         | 
| 84 102 | 
             
                  !!@prc.call(request)
         | 
| 85 103 | 
             
                end
         | 
| 86 | 
            -
             | 
| 104 | 
            +
             | 
| 87 105 | 
             
              end
         | 
| 88 | 
            -
             | 
| 89 | 
            -
            end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            end
         | 
    
        data/spec/basic_spec.rb
    CHANGED
    
    | @@ -1,86 +1,151 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 3 | 
             
            describe DiviningRod do
         | 
| 4 | 
            -
             | 
| 4 | 
            +
             | 
| 5 5 | 
             
              before :each do
         | 
| 6 6 | 
             
                @request = mock("rails_request", :user_agent => 'My iPhone')
         | 
| 7 7 | 
             
                DiviningRod::Matchers.clear_definitions
         | 
| 8 8 | 
             
                DiviningRod::Matchers.define do |map|
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                    map.default(:unknown)
         | 
| 9 | 
            +
                  map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube, :geolocate]
         | 
| 11 10 | 
             
                end
         | 
| 12 11 | 
             
              end
         | 
| 13 | 
            -
             | 
| 12 | 
            +
             | 
| 14 13 | 
             
              it "should recognize an iPhone" do
         | 
| 15 14 | 
             
                profile = DiviningRod::Profile.new(@request)
         | 
| 16 15 | 
             
                profile.format.should eql(:webkit)
         | 
| 17 16 | 
             
              end
         | 
| 18 | 
            -
             | 
| 17 | 
            +
             | 
| 19 18 | 
             
              it "should know if it belongs to a category tag" do
         | 
| 20 19 | 
             
                profile = DiviningRod::Profile.new(@request)
         | 
| 21 20 | 
             
                profile.geolocate?.should be_true
         | 
| 22 21 | 
             
              end
         | 
| 23 | 
            -
             | 
| 22 | 
            +
             | 
| 24 23 | 
             
              it "should know if it does not belongs to a category" do
         | 
| 25 24 | 
             
                profile = DiviningRod::Profile.new(@request)
         | 
| 26 25 | 
             
                profile.wap?.should be_false
         | 
| 27 26 | 
             
              end
         | 
| 28 27 |  | 
| 29 | 
            -
               | 
| 30 | 
            -
                 | 
| 31 | 
            -
                 | 
| 32 | 
            -
             | 
| 28 | 
            +
              describe "without a default route" do
         | 
| 29 | 
            +
                
         | 
| 30 | 
            +
                before :each do
         | 
| 31 | 
            +
                  @request = mock("rails_request", :user_agent => 'My Foo Fone', :format => :html)
         | 
| 32 | 
            +
                  DiviningRod::Matchers.clear_definitions
         | 
| 33 | 
            +
                  DiviningRod::Matchers.define do |map|
         | 
| 34 | 
            +
                    map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube, :geolocate]
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it "should use the default group for unknown phones" do
         | 
| 39 | 
            +
                  profile = DiviningRod::Profile.new(@request)
         | 
| 40 | 
            +
                  profile.wap?.should be_false
         | 
| 41 | 
            +
                  profile.format.should eql(:html)
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 33 44 | 
             
              end
         | 
| 34 45 |  | 
| 35 | 
            -
             | 
| 46 | 
            +
             | 
| 47 | 
            +
              describe "with a default route" do
         | 
| 36 48 |  | 
| 49 | 
            +
                before :each do
         | 
| 50 | 
            +
                  @request = mock("rails_request", :user_agent => 'My Foo Fone')
         | 
| 51 | 
            +
                  DiviningRod::Matchers.clear_definitions
         | 
| 52 | 
            +
                  DiviningRod::Matchers.define do |map|
         | 
| 53 | 
            +
                    map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube, :geolocate]
         | 
| 54 | 
            +
                    map.default :html
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                it "should use the default group for unknown phones" do
         | 
| 59 | 
            +
                  profile = DiviningRod::Profile.new(@request)
         | 
| 60 | 
            +
                  profile.wap?.should be_false
         | 
| 61 | 
            +
                  profile.format.should eql(:html)
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              describe "without a default definition" do
         | 
| 67 | 
            +
             | 
| 37 68 | 
             
                before :each do
         | 
| 38 69 | 
             
                  @request = mock("rails_request", :user_agent => 'Foo Fone')
         | 
| 39 70 | 
             
                  DiviningRod::Matchers.clear_definitions
         | 
| 40 71 | 
             
                  DiviningRod::Matchers.define do |map|
         | 
| 41 | 
            -
             | 
| 72 | 
            +
                    map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube, :geolocate]
         | 
| 42 73 | 
             
                  end
         | 
| 43 74 | 
             
                end
         | 
| 44 | 
            -
             | 
| 75 | 
            +
             | 
| 45 76 | 
             
                it "should not find a match" do
         | 
| 46 77 | 
             
                  DiviningRod::Profile.new(@request).recognized?.should be_false
         | 
| 47 78 | 
             
                end
         | 
| 48 | 
            -
             | 
| 79 | 
            +
             | 
| 49 80 | 
             
              end
         | 
| 50 81 |  | 
| 82 | 
            +
              describe "matching a subdomain" do
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                before :each do
         | 
| 85 | 
            +
                  @request = mock("rails_request", :user_agent => 'Foo Fone', :subdomains => ['wap'])
         | 
| 86 | 
            +
                  DiviningRod::Matchers.clear_definitions
         | 
| 87 | 
            +
                  DiviningRod::Matchers.define do |map|
         | 
| 88 | 
            +
                    map.subdomain /wap/, :wap, :tags => [:shitty]
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                it "should not find a match" do
         | 
| 93 | 
            +
                  DiviningRod::Profile.new(@request).recognized?.should be_true
         | 
| 94 | 
            +
                  profile = DiviningRod::Profile.new(@request)
         | 
| 95 | 
            +
                  profile.wap?
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
              
         | 
| 100 | 
            +
              describe "matching the weird requests(no user_agent passed)" do
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                before :each do
         | 
| 103 | 
            +
                  @request = mock("rails_request", :user_agent => nil, :subdomains => [])
         | 
| 104 | 
            +
                  DiviningRod::Matchers.clear_definitions
         | 
| 105 | 
            +
                  DiviningRod::Matchers.define do |map|
         | 
| 106 | 
            +
                    map.ua /iPhone/, :wap, :tags => [:shitty]
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                it "should not find a match" do
         | 
| 111 | 
            +
                  DiviningRod::Profile.new(@request).recognized?.should be_false
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
             | 
| 51 116 | 
             
            end
         | 
| 52 117 |  | 
| 53 118 |  | 
| 54 119 | 
             
            describe DiviningRod::Matchers do
         | 
| 55 | 
            -
             | 
| 120 | 
            +
             | 
| 56 121 | 
             
              before :each do
         | 
| 57 122 | 
             
                @request = mock("rails_request", :user_agent => 'iPhone Foo')
         | 
| 58 123 | 
             
                DiviningRod::Matchers.clear_definitions
         | 
| 59 124 | 
             
                DiviningRod::Matchers.define do |map|
         | 
| 60 | 
            -
             | 
| 125 | 
            +
                  map.ua /iPhone/, :iphone, :tags => [:iphone, :youtube]
         | 
| 61 126 | 
             
                end
         | 
| 62 127 | 
             
              end
         | 
| 63 | 
            -
             | 
| 128 | 
            +
             | 
| 64 129 | 
             
              it "should recognize an iPhone" do
         | 
| 65 130 | 
             
                DiviningRod::Matchers.definitions.first.matches?(@request).should be_true
         | 
| 66 131 | 
             
                DiviningRod::Matchers.definitions.first.group.should eql(:iphone)
         | 
| 67 132 | 
             
              end
         | 
| 68 | 
            -
             | 
| 133 | 
            +
             | 
| 69 134 | 
             
              describe "defining a default definition" do
         | 
| 70 | 
            -
             | 
| 135 | 
            +
             | 
| 71 136 | 
             
                before :each do
         | 
| 72 137 | 
             
                  @request = mock("rails_request", :user_agent => 'Foo Fone')
         | 
| 73 138 | 
             
                  DiviningRod::Matchers.clear_definitions
         | 
| 74 139 | 
             
                  DiviningRod::Matchers.define do |map|
         | 
| 75 | 
            -
             | 
| 140 | 
            +
                    map.default :unknown, :tags => [:html]
         | 
| 76 141 | 
             
                  end
         | 
| 77 142 | 
             
                end
         | 
| 78 | 
            -
             | 
| 143 | 
            +
             | 
| 79 144 | 
             
                it "should use the default route if no other match is found" do
         | 
| 80 145 | 
             
                  DiviningRod::Matchers.definitions.first.matches?(@request).should be_true
         | 
| 81 146 | 
             
                  DiviningRod::Matchers.definitions.first.group.should eql(:unknown)
         | 
| 82 147 | 
             
                end
         | 
| 83 | 
            -
             | 
| 148 | 
            +
             | 
| 84 149 | 
             
              end
         | 
| 85 | 
            -
             | 
| 86 | 
            -
            end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: divining_rod
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors: 
         | 
| 7 7 | 
             
            - Mark Percival
         | 
| @@ -9,7 +9,7 @@ autorequire: | |
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 11 |  | 
| 12 | 
            -
            date: 2010-02- | 
| 12 | 
            +
            date: 2010-02-18 00:00:00 -05:00
         | 
| 13 13 | 
             
            default_executable: 
         | 
| 14 14 | 
             
            dependencies: 
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency 
         |