Search code examples
rubyblockwebmock

How to pass data along with a ruby block?


I'm trying to pass some data as a block to some external API. It would be a hassle to accommodate it to accepting additional parameters. If it were javascript, I might make it like so:

var callback = function() {
    // do something
}
callback['__someData'] = options;
someExternalAPI(callback);

Is this possible with Ruby? Or how should I go about associating some data with a block?

Not sure if the edits to the question were correct. First, I'd like to specifically pass some data along with a block if that is possible. Not sure if it is though. And probably the only way to do it in ruby is to pass some data as a block.

Additionally, here might be some useful info.

Okay, it probably makes sense to show the whole picture. I'm trying to adapt webmock to my needs. I have a function, which checks if request's params (be them of POST, or of GET) match specified criteria:

def check_params params, options
  options.all? do |k,v|
    return true unless k.is_a? String
    case v
    when Hash
      return false unless params[k]
      int_methods = ['<', '<=', '>', '>=']
      v1 = int_methods.include?(v.first[0]) ? params[k].to_i : params[k]
      v2 = int_methods.include?(v.first[0]) \
        ? v.first[1].to_i : v.first[1].to_s
      v1.send(v.first[0], v2)
    when TrueClass, FalseClass
      v ^ ! params.key?(k)
    else
      params[k] == v.to_s
    end
  end
end

It's not perfect, but it suffices for my particular needs, for now. I'm calling it like this:

stub_request(:post, 'http://example.com/')
  .with { |request|
    check_params Rack::Utils.parse_query(request.body), options
  }

And the thing is generally I see no sensible way to output with block conditions. But in my particular case one can just output options hash. And instead of this:

registered request stubs:

stub_request(:post, "http://example.com")

to have this:

stub_request(:post, "http://example.com").
  with(block: {"year"=>2015})

Which is what I'm trying to do.


Solution

  • Okay, I ended up doing this:

    p = Proc.new {}
    p.class.module_eval { attr_accessor :__options }
    p.__options = {a: 1}                                                                                
    # ...
    pp p.__options
    

    Or to be more specific:

    def mk_block_cond options, &block_cond
      options = options.select { |k,v| ! k.is_a?(Symbol) }
      return block_cond if options.empty?
      block_cond.class.module_eval { attr_accessor :__options }
      block_cond.__options = options
      block_cond
    end
    
    module WebMock
      class RequestPattern
        attr_reader :with_block
      end
    end
    
    module StubRequestSnippetExtensions
      def to_s(with_response = true)
        request_pattern = @request_stub.request_pattern
        string = "stub_request(:#{request_pattern.method_pattern.to_s},"
        string << " \"#{request_pattern.uri_pattern.to_s}\")"
    
        with = ""
    
        if (request_pattern.body_pattern)
          with << ":body => #{request_pattern.body_pattern.to_s}"
        end
    
        if (request_pattern.headers_pattern)
          with << ",\n       " unless with.empty?
    
          with << ":headers => #{request_pattern.headers_pattern.to_s}"
        end
    
        if request_pattern.with_block \
        && request_pattern.with_block.respond_to?('__options') \
        && request_pattern.with_block.__options
          with << ",\n       " unless with.empty?
    
          with << "block: #{request_pattern.with_block.__options}"
        end
    
        string << ".\n  with(#{with})" unless with.empty?
        if with_response
          string << ".\n  to_return(:status => 200, :body => \"\", :headers => {})"
        end
        string
      end
    end
    
    module WebMock
      class StubRequestSnippet
        prepend StubRequestSnippetExtensions
      end
    end
    
    module RequestPatternExtensions
      def to_s
        string = "#{@method_pattern.to_s.upcase}"
        string << " #{@uri_pattern.to_s}"
        string << " with body #{@body_pattern.to_s}" if @body_pattern
        string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
        if @with_block
          if @with_block.respond_to?('__options') \
          && @with_block.__options
            string << " with block: %s" % @with_block.__options.inspect
          else
            string << " with given block"
          end
        end
        string
      end
    end
    
    module WebMock
      class RequestPattern
        prepend RequestPatternExtensions
      end
    end
    

    And now I stub requests this way:

    stub_request(:post, 'http://example.com/')
      .with &mk_block_cond(options) { |request|
        check_params Rack::Utils.parse_query(request.body), options
      }
    

    P.S. github issue