What does "with(&block)" mean in Ruby? - ruby

In a gem I'm working with I found the snippet:
#object.with(&block)
But the method with(&block) is not defined in the project. It looks like it's defined as a base method inside Ruby somwhere, but I'm not sure.
What does it mean? Can someone point to where that method is defined (like in Object or Class or some other Ruby class)?
Edit:
The code in question:
def self.redis(&block)
raise ArgumentError, "requires a block" if !block
#redis ||= Sidekiq::RedisConnection.create(#hash || {})
#redis.with(&block)
end
It's from the project Sidekiq (https://github.com/mperham/sidekiq). That project also includes the redis-rb gem (https://github.com/redis/redis-rb). I can't locate a with method defined in either.
Maybe I'm just missing something.

It's defined as part of the connection_pool gem which is used by sidekiq, and it's source is below. It looks like it's purpose is to obtain a connection from the pool, yield it to the provided block, and then release the connection back to the pool.
here's how I found that out:
pry> redis = Sidekiq::RedisConnection.create({})
pry> redis.method(:with).source
def with
conn = checkout
begin
yield conn
ensure
checkin
end
end
pry> redis.method(:with).source_location
["./ruby/gems/2.0.0/gems/connection_pool-1.1.0/lib/connection_pool.rb", 46]
And to identify the dependency:
~$ bundle exec gem dependency connection_pool --reverse-dependencies
Gem connection_pool-1.1.0
minitest (>= 5.0.0, development)
Used by
sidekiq-2.16.0 (connection_pool (>= 1.0.0))

Related

Monkey-patch using modules in a gem

I'm building a Ruby gem that includes a module that's meant to monkey-patch the Hash class to add a new method. I'm following this guide to try to do it neatly: http://www.justinweiss.com/articles/3-ways-to-monkey-patch-without-making-a-mess/
I've placed the module in lib/core_extensions/hash/prune.rb, and the module is declared as such:
module CoreExtensions
module Hash
module Prune
##
# Removes all pairs from the Hash for which the value is nil. Destructive!
def prune!
self.reject! { |_, v| v.nil? }
end
end
end
end
And in order to make the monkey patch take effect, I'm calling this within the main gem file:
Hash.include(CoreExtensions::Hash::Prune)
But after building the gem and trying to require it in an irb console, I get the following error: NameError: uninitialized constant Gem::CoreExtensions (Gem is a placeholder name).
I made sure to include the prune.rb file in my gemspec's files array: s.files = ['lib/gem.rb', 'lib/core_extensions/hash/prune.rb'], so I'm not sure why it can't detect the file and its modules. Can anyone help me figure this out?
Thank you!
EDIT: In case it will help anyone else - I tried to require the module file using require 'lib/core_extensions/hash/prune' but received 'cannot load such file' errors. Sticking ./ in front of the path fixed it.

Upgraded to minitest 5.4.0. Now must* and won't* doesn't work within Minitest::Test subclass, but assert* still works

I have been using the MiniTest that comes with Ruby 2.1 without a problem. I would subclass MiniTest::Unit:TestCase create a couple methods like 'test_simple', and everything just worked. I would use Expectations and Asserts without a problem.
I have upgraded Minitest to 5.4.0 using a gem. Everywhere I use Expectations (musts and wonts) I get a strangle error. Example of the test class.
gem 'minitest'
require "minitest/autorun"
require "rest-client"
require "json"
require "pp"
# require './testcase_addins'
class TestUserKey < Minitest::Test
def test_simple
data = 0
assert( data >= 0 )
data.must_be :>=,0
end
end
When I run this, the assert line passes without a problem, but must_be line throws this error:
1) Error:
TestUserKey#test_simple:
NoMethodError: undefined method `assert_operator' for nil:NilClass
(eval):4:in `must_be'
user_key_testcase.rb:14:in `test_simple'
The strange part is what is the nil:NilClass in the error can't be nil; it's 0. Even I change the Fixnum to a String, I still get the same error.
If I change the test to a spec test, everything works again. So I can't use Expectations with in Unit Tests? IF that is the case, could someone explain why?
The short answer is that with 5.4.0 your test class must inherit from MiniTest::Spec in order to use expectations.
I tested this on a new ubuntu machine with ruby 2.1.2 installed via RVM:
rvm install ruby-2.1.2
This code works with the stock ruby 2.1.2 (no 5.4.0 minitest gem installed, slightly cleaned up from your example code above):
#!/usr/bin/env ruby
require 'minitest/unit'
require "minitest/autorun"
class TestUserKey < MiniTest::Unit::TestCase
def test_simple
data = 0
assert( data >= 0 )
data.must_be :>=,0
end
end
Running this code works fine. To reproduce the error listed above, install minitest 5.4.0:
gem install minitest -v 5.4.0
Now the code fails with "NoMethodError: undefined method `assert_operator' for nil:NilClass". You now have both versions of minitest installed:
~/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/minitest/ # stock ruby version of minitest
~/.rvm/gems/ruby-2.1.2/gems/minitest-5.4.0/ # minitest v5.4.0 installed via rubygems
Now that everything is set up, we can dig into what exactly is happening. The expectations are defined with a call to infect_an_assertion, like this:
infect_an_assertion :assert_operator, :must_be, :reverse
For 5.4.0 that call happens in ~/.rvm/gems/ruby-2.1.2/gems/minitest-5.4.0/lib/minitest/expectations.rb. It's roughly the same in both versions, it just happens in a different place.
infect_an_assertion is also roughly the same for both versions. For :must_be it winds up making this call, which is identical between the two versions of minitest:
MiniTest::Spec.current.#{meth}(args.first, self, *args[1..-1])
They're doing some metaprogramming here, the call at runtime will look more like this since meth is set to assert_operator:
MiniTest::Spec.current.assert_operator(...)
The important part is MiniTest::Spec.current. In 5.4.0 this method returns nil, which results in a NoMethodError exception when it tries to call assert_operator on nil.
In the stock minitest from ruby 2.1.2:
Here MiniTest::Spec inherits from MiniTest::Unit::TestCase. TestCase defines the method current and returns a value that was set in the initialize method. You can see this all happening around line 1303 of ~/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/minitest/unit.rb:
def initialize name # :nodoc:
#__name__ = name
#__io__ = nil
#passed = nil
##current = self # FIX: make thread local
end
def self.current # :nodoc:
##current # FIX: make thread local
end
Therefore, when you inherit from MiniTest::Unit::TestCase in your test class with the stock minitest, current is defined as a method and is guaranteed to return a value when the above call to MiniTest::Spec.current is made. This is why it works in the stock ruby 2.1.2 minitest.
In minitest 5.4.0
In 5.4.0, Minitest::Spec inherits from Minitest::Test, which does not define current (nor do any of its parents). The current method is defined directly on Minitest::Spec. It simply returns Thread.current[:current_spec]:
# line 83 of ~/.rvm/gems/ruby-2.1.2/gems/minitest-5.4.0/lib/minitest/spec.rb
def self.current # :nodoc:
Thread.current[:current_spec]
end
The value of Thread.current[:current_spec] is set in the Minitest::Spec constructor on line 87 of the same file:
def initialize name # :nodoc:
super
Thread.current[:current_spec] = self
end
The problem is that when your test class inherits from Minitest::Test, the Minitest::Spec constructor never gets called and Thread.current[:current_spec] is never initialized. That means the call in infect_an_assertion to Minitest::Spec.current returns nil, which results in the NoMethodError you're seeing when it tries to call assert_operator on nil. The solution is to make your test class inherit from Minitest::Spec so that the constructor is called and Thread.current[:current_spec] gets a value.
Here is a slightly modified version of the original code that works with minitest 5.4.0:
#!/usr/bin/env ruby
gem 'minitest'
require "minitest/autorun"
class TestUserKey < Minitest::Spec
def test_simple
data = 0
assert( data >= 0 )
data.must_be :>=,0
end
end
Hope this helps!
From the minitest readme, it seems like the must style syntax is a part of the spec syntax, which would require you to use methods like describe and it instead of defining your own unit test methods.

Load two Ruby Modules/Gems with the same name

I'm trying to use two Gems to access Amazon Web Services (AWS). One is the Amazon 'aws-sdk', the other is 'amazon-ec2'. I'm using the second as the aws-sdk does not cover the cloudwatch section of the amazon services.
The issue is that both load into the same namespace.
require 'aws-sdk' # aws-sdk gem
require 'AWS' # amazon-ec2 gem
config = {:access_key_id => 'abc', :secret_key => 'xyz'}
# start using the API with aws-sdk
ec2 = AWS::EC2.new(config)
# start using the API for anazon-ec2
cw = AWS::Cloudwatch::Base.new(config)
Now this understandably throws an error on the last line as the AWS module is pointing at the first required library, in this case aws-sdk.
NameError: uninitialized constant AWS::Cloudwatch
So, is it possible for me to load one of those into another namespace? Something like
require 'aws-sdk', 'AWS_SDK'
require 'AWS', 'AWS_EC2'
ec2 = AWS_SDK::EC2.new(config)
cw = AWS_EC2::Cloudwatch::Base.new(config)
Or is there another trick I could use here?
Thanks
In Ruby, modules with the same name from different gems don't replace each other. If one gem implements
module AWS
class Foo
end
end
and another implements
module AWS
class Bar
end
end
and you require them both, you will end up with an AWS module that contains both a class Foo and a class Bar (unless the second does something really tricky like explicitly undefining anything already present in the module, before defining its own stuff, which is very unlikely). As long as the second gem doesn't redefine any methods in the first gem (or attempts to use a module as a class or vice versa), they should both work fine. I think you may be looking for the wrong solution.
Edit:
And in fact, what happens for me (in an environment with only these gems present (aws-sdk 1.2.3 and amazon-ec2 0.9.17) and the exact code you listed above) is exactly that:
.rvm/gems/ree-1.8.7-2011.03#ec2/gems/amazon-ec2-0.9.17/lib/AWS/EC2.rb:2: EC2 is not a module (TypeError)
Could it be that an error gets swallowed somewhere and that the module AWS::Cloudwatch hasn't been defined, simply because the initialization of the gem goes awry?
I think I've found a solution that works, let me illustrate it with an example. Suppose we have to files a.rb and b.rb that define the same module with actual name clashes:
#file a.rb
module A
def self.greet
puts 'A'
end
end
#file b.rb
module A
def self.greet
puts 'other A'
end
end
If you need to require both of them, the following seems to do the trick:
require_relative 'a'
TMP_A = A.dup
A.greet # => A
TMP_A.greet # => A
require_relative 'b'
TMP_A2 = A
A.greet # => other A
TMP_A2.greet # => other A
TMP_A.greet # => A
Without the dup, TMP_A will also point to the A defined in b.rb after the require_relative, but the dup will ensure that a real copy is produced instead of simply holding a reference to the module.

How can I get rid of the following warning: Problem while setting context on example startundefined local variable or method `selenium_driver'

Still making my first steps in Ruby (while dealing with some written code). I am getting the following warning each time I run spec (listed as is):
Problem while setting context on example startundefined local variable or method `selenium_driver' for #<Spec::Example::ExampleGroup::Subclass_1::Subclass_1:0x7f2d2cd840e0>
(Edit: Split into two lines, it says)
Problem while setting context on example start
undefined local variable or method `selenium_driver' for #<Spec::Example::ExampleGroup::Subclass_1::Subclass_1:0x7f2d2cd840e0>
While grep-ing through Ruby code - could find the following:
/home/user/.rvm/gems/ruby-1.8.7-p334#frontend/gems/selenium-client-1.2.18/lib/selenium/rspec/spec_helper.rb: STDERR.puts "Problem while setting context on example start" + e
So here is the excerpt from the source code of spec_helper.rb:
config.append_before(:each) do
begin
if selenium_driver && selenium_driver.session_started?
selenium_driver.set_context "Starting example '#{self.description}'"
end
rescue Exception => e
STDERR.puts "Problem while setting context on example start" + e
end
end
Kindly advise how can I solve the (potential) problem.
Update: This grep might be helpful as well:
user#vm-ubuntu:~/dev/branch/tests$
grep selenium_driver *
my_module.rb: #selenium_driver = driver
my_module.rb: ['TERM', 'INT'].each {|s| Signal.trap(s) { #selenium_driver.stop && Process.exit(1) } }
my_module.rb: return #selenium_driver
Update N2:
My Gemfile:
source "http://rubygems.org" # Default source
gem "hpricot", "~>0.8.4"
gem "json", "~>1.5.1"
gem "rspec", "~>1.3.2"
gem "selenium-client", "~>1.2.18"
My selenium_helper.rb file:
require 'selenium/client'
require "selenium/rspec/spec_helper"
...
The problem is that selenium-client gem expects that you name your driver object 'selenium_driver' and make it visible from the spec.
For example if you initialize selenium like this:
before(:all) do
#driver = create_driver($hub_url, $hub_port, $browser)
#driver.start_new_browser_session
end
You need to change it to look like this:
attr_reader :selenium_driver
before(:all) do
#selenium_driver = create_driver($hub_url, $hub_port, $browser)
#selenium_driver.start_new_browser_session
end
Basically it's same code just different variable name. The selenium-client uses that convention to apply context information to the tests.
It's saying it can't find the variable selenium_driver.
Problem while setting context on example startundefined local variable or method `selenium_driver' for #<Spec::Example::ExampleGroup::Subclass_1::Subclass_1:0x7f2d2cd840e0>
is made up of the string "Problem while setting context on example start" plus the exception error message (what's produced by e) of
"undefined local variable or method `selenium_driver' for #<Spec::Example::ExampleGroup::Subclass_1::Subclass_1:0x7f2d2cd840e0>"`.
Add
gem "selenium-client"
to your Gemfile (don't forget to run $ bundle install)
And add following to spec/spec_helper.rb
require "selenium/client"
require "selenium/rspec/spec_helper"

ruby-openid: #socket not set while performing discovery

I'm having a bit of truble with omniauth/openid.
When trying to authenticate, I found this in my logs:
OpenID::FetchingError: Error fetching https://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username: undefined method `io' for nil:NilClass
The important thing there is undefined method io' for nil:NilClass which comes from openid/fetchers.rb, in the following snippet:
module Net
class HTTP
def post_connection_check(hostname)
check_common_name = true
cert = #socket.io.peer_cert
cert.extensions.each { |ext|
next if ext.oid != "subjectAltName"
ext.value.split(/,\s+/).each{ |general_name|
if /\ADNS:(.*)/ =~ general_name
check_common_name = false
...
That error is generated by #socket.io.peer_cert, #socket is not defined.
Have any of you encountered this before? Not quite sure what the cause is.
Versions I'm running:
ruby 1.9.3dev (2010-08-17 trunk 29020) [x86_64-darwin10.4.0]
ruby-openid (2.1.8)
ruby-openid-apps-discovery (1.2.0)
omniauth 0.2.0
We had this same problem and it was a direct result of Net::HTTP#connect never being invoked. Turns out we had the fakeweb gem scoped into the environment that was throwing the error (development, in our case).
Narrowing fakeweb's scope allows for normal processing of #connect and #socket is once again happy.
group :test do
gem 'fakeweb'
end
We came across the same / very similar problem with both fakeweb and webmock (when using the VCR gem). Switching from fakeweb to typhoeus seemed to have solved this problem for us.

Resources