I've got some code to add methods to a module from simple definitions for talking to remote resources via a wrapper class around a REST client.
def service_function(function_name, method, uri, parameters)
class_eval <<-RUBY
def #{function_name}(params)
if !(#{function_name}_required_params - params.keys).empty? || \
!(params.keys - #{function_name}_params).empty?
raise Errors::InvalidParameters.new(service_name, __method__,
params.keys, #{function_name}_params)
end
WebServices::ServiceRequest.perform(self.domain, #{uri}, #{method}, params)
end
def #{function_name}_params
#{function_name}_required_params + #{function_name}_optional_params
end
def #{function_name}_required_params
#{parameters}.select { |param,req| req }.keys
end
def #{function_name}_optional_params
#{parameters}.select { |param,req| !req }.keys
end
RUBY
end
Before I can even run the code, just requiring the gem I'm building into IRB spits out this error:
1.9.2p320 :001 > require 'web-services'
SyntaxError: (eval):7: unknown regexp options - rt
The offending line is:
WebServices::ServiceRequest.perform(self.domain, #{uri}, #{method}, params)
Removing the "#{uri}" argument fixes it, even leaving in the "#{method}" argument. Does anyone out there have a clue as to why this might be? I'm about at my wit's end.
You have a url that looks something like /something/rt and that will look like a regex literal in here:
WebServices::ServiceRequest.perform(self.domain, #{uri}, #{method}, params)
You need to escape and quote #{uri} so that it looks like a string inside the heredoc, that way class_eval will see perform(..., '/something/rt', ...) and be happy. You might have similar issues with #{method}.
Related
It's fairly common in Ruby for methods that take blocks to look like this:
class File
def open(path, mode)
perform_some_setup
yield
ensure
do_some_teardown
end
end
It's also fairly idiomatic for a method to look like this:
def frobnicate
File.open('/path/to/something', 'r') do |f|
f.grep(/foo/).first
end
end
I want to write a spec for this that doesn't hit the filesystem, which ensures it pulls the right word out of the file, something like:
describe 'frobnicate' do
it 'returns the first line containing the substring foo' do
File.expects(:open).yields(StringIO.new(<<EOF))
not this line
foo bar baz
not this line either
EOF
expect(frobnicate).to match(/foo bar baz/)
end
end
The problem here is that, by mocking out the call to File.open, I've also removed its return value, which means that frobnicate will return nil. If I were to add something like File.returns('foo bar baz') to the chain, however, I'd end up with a test that doesn't actually hit any of the code I'm interested in; the contents of the block in frobnicate could do anything and the test would still pass.
How might I appropriately test my frobnicate method without hitting the filesystem? I'm not particularly attached to any particular testing framework, so if your answer is "use this awesome gem that'll do it for you" then I'm OK with that.
It seems like you just need to mock the call to File a little differently. I was getting syntax errors running your code as-is, so I'm not sure what version of RSpec you're on, but if you're on 3.x this will do the job:
frobnicate_spec.rb
gem 'rspec', '~> 3.4.0'
require 'rspec/autorun'
RSpec.configure do |config|
config.mock_with :rspec
end
def frobnicate
File.open('/path/to/something', 'r') do |f|
f.grep(/foo/).first
end
end
RSpec.describe 'frobnicate' do
it 'returns the first line containing the substring foo' do
allow(File).to receive(:open).and_call_original
allow(File).to receive(:open).and_yield StringIO.new <<-EOF
not this line
foo bar baz
not this line either
EOF
expect(frobnicate).to match(/foo bar baz/)
end
end
Invoke with ruby frobnicate_spec.rb so we can use a specified RSpec version.
Source: RSpec Mocks expecting messages and yielding responses
Using minitest it could be done like I post below. I have added the whole runnable file, so you can test it from the command line with ruby -Ilib:test test_file.rb:
def frobnicate
found_string = nil
File.open('/path/to/something', 'r') do |f|
found_string = f.grep(/foo/).first
end
found_string
end
class FrabnicateTest < Minitest::Test
def test_it_works
mock_file = StringIO.new(%(
not this line
foo bar baz
not hthis line either
))
search_result = nil
File.stub(:open, nil, mock_file) do
search_result = frobnicate
end
assert_match(/foo bar baz/, search_result)
end
end
I recently had cause to use the nokogiri gem to parse html but while i going through their documentation, i came across this ruby syntax that i hadn't seen before
html_doc = Nokogiri::HTML('<html><body><h1>Mr. Belvedere Fan Club</h1></body></html>')
xml_doc = Nokogiri::XML('<root><aliens><alien><name>Alf</name></alien></aliens></root>')
The part of interest for me is Nokogiri::HTML('...'). This looks very much like a method invocation but i know ruby method names cannot be in capital letters. So i looked through code files nokogiri gem and i came across the following definition
module Nokogiri
class << self
###
# Parse HTML. Convenience method for Nokogiri::HTML::Document.parse
def HTML thing, url = nil, encoding = nil, options = XML::ParseOptions::DEFAULT_HTML, &block
Nokogiri::HTML::Document.parse(thing, url, encoding, options, &block)
end
end
# more code
end
I tried reproducing the same code
module How
class << self
def DOESTHISWORK
puts "In How Method"
end
end
end
How::DOESTHISWORK
But it keeps coming back with the error "uninitialized constant How::DOESTHISWORK (NameError)". I know it has to do with the method name starting in capitals but i just haven't been able to figure out how it works in nokogiri.
The difference is in the Nokogiri example the method is being called with parentheses and a parameter value which identifies it as a method call. Your DOESTHISWORK method takes no parameters but can be called with empty parentheses e.g.
irb(main):028:0> How::DOESTHISWORK()
In How Method
=> nil
If you add a parameter to your method that can also serve to identify it as a method like so:
irb(main):036:0> How::DOESTHISWORK 'some param'
Starting method names with a lowercase letter is good practice but isn't enforced. Something that begins with a capital letter is assumed to be a constant and will be looked up as such, this is why the parentheses or parameter is needed to indicate a method is being referred to. Another example:
irb(main):051:0> def Example
irb(main):052:1> puts "An example!"
irb(main):053:1> end
=> nil
irb(main):054:0> Example
NameError: uninitialized constant Example
from (irb):54
from /Users/mike/.rbenv/versions/1.9.3-p194/bin/irb:12:in `<main>'
irb(main):055:0> Example()
An example!
=> nil
I also found this post to be very helpful
What are the restrictions for method names in Ruby?
It's good practice, while not mandatory, to start the method name with
a lower-case character, because names that start with capital letters
are constants in Ruby. It's still possible to use a constant name for
a method, but you won't be able to invoke it without parentheses,
because the interpeter will look-up for the name as a constant
I have the below code under test:
class MethodCache
##methods=Hash.new
def self.add_method(name, &block)
##methods[name]=block
end
def self.get_method(name)
##methods[name]
end
end
Now my spec looks like this:
describe MethodCache do
subject {MethodCache}
foo_block = ->{ puts "foo"}
it ".get_method" do
subject.add_method "foo", &foo_block
# does not work
# expect(subject.get_method("foo").to be &foo_block
# should syntax works
subject.get_method("foo").should be foo_block
end
end
I am trying to stay away from should syntax and use the expect syntax of RSpec. However it does not work in this case.
expect(subject.get_method("foo").to be &foo_block fails saying wrong number of arguments. I guess this is because the expectation block is treated as a block argument.
expect(subject.get_method("foo").to be foo_block (without the '&') does not work either. It says, the matcher expects a value and not argument.
What am I missing here?
If I run this simple Ruby code regularly, it works fine:
class String
def add_two
self + "2"
end
end
puts "hello".add_two
It prints "hello2" as it should. But this fails:
:ruby
class String
def add_two
self + "2"
end
end
puts "hello".add_two
This code produces an error:
NoMethodError at /
undefined method `add_two' for "hello":String
Any ideas what's wrong?
(Not sure if it matters, but I'm using HAML with Sinatra, which is running on Apache with the Passenger module.)
I would suggest that String is in another namespace and therefor another class.
What happens with that?
class ::String
I put your code as is into one of my Haml views in a Rails app and I got a different error to you:
SyntaxError at /
class definition in method body
So I wondered whether it was Haml's :ruby filter that was complaining, but since it "Parses the filtered text with the normal Ruby interpreter", it seemed unlikely. So, I searched for more info about the error and found references (see below) that led me to this, which works (but, really, should never be used):
:ruby
String.module_eval do
def add_two
self + "2"
end
end
puts "hello".add_two
References:
Class inside a Method Body
Class (Re)definition in Method Body
Some popular blog sites typically use square brackets in their URLs but ruby's built-in URI.parse() method chokes on them, raising a nasty exception, as per:
http://redmine.ruby-lang.org/issues/show/1466
I'm trying to write a simple monkey-patch that gracefully handles URLs with the square bracket. The following is what I have so far:
require 'uri'
module URI
def self.parse_with_safety(uri)
safe_uri = uri.replace('[', '%5B')
safe_uri = safe_uri.replace(']', '%5D')
URI.parse_without_safety(safe_uri)
end
alias_method_chain :parse, :safety
end
But when run, this generates an error:
/Library/Ruby/Gems/1.8/gems/activesupport-2.3.8/lib/active_support/core_ext/module/aliasing.rb:33:in alias_method: NameError: undefined method 'parse' for module 'URI'
How can I successfully monkey-patch URI.parse?
alias_method_chain is executed on the module level so it only affects instance methods.
What you have to do is execute it on the module's class level:
require 'uri'
module URI
class << self
def parse_with_safety(uri)
parse_without_safety uri.gsub('[', '%5B').gsub(']', '%5D')
end
alias parse_without_safety parse
alias parse parse_with_safety
end
end
#nil his comment is very helpful, we ended up with the following:
def parse_with_safety(uri)
begin
parse_without_safety uri.gsub(/([{}|\^\[\]\#`])/) {|s| URI.escape(s)}
rescue
parse_without_safety '/'
end
end