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
Related
I'm currently working on an ERB View class for a gem. With this class I would like to have some helper methods for ERB templates.
It's okay about basic helpers like h(string). I found erbh gem who help me to understand more how context works.
But now I'm trying to create a content_for method like there is in Rails or Sinatra.
On first time I was using simple Proc to capture the view block and then just calling call method to print it. It was working enough at the beginning.
But after having completed views I saw wired thinks, some content are printed multiple times.
So I take a look on the Sinatra ContentFor helper to understand how they did it and I copy some methods of this helper. I have no errors, but the block return are always empty... and I don't really know why.
My knowledge about ERB are not good enough to know how ERB buffering works.
Code
Here a gist who explain the status of my code. I extracted the code from my library and simplified it a bit.
https://gist.github.com/nicolas-brousse/ac7f5454a1a45bae30c52dae826d587f/66cf76c97c35a02fc6bf4a3bc13d8d3b587356de
What I would like?
I just would like to have content_for methods works like they do with Rails and Sinatra.
Thanks!
After reading this blog article I finally found why it wasn't working. I don't know if I did it in the best way and cleaner way but it works.
So the bug was mainly from the ERB initilization. By using a property instead a local variable as eoutvar it now works.
erb = ERB.new(str, nil, "<>", "#_erbout")
I also change a bit the capture method who is used by content_for helper.
It looks like this now (gist)
def content_for(key, content = nil, &block)
block ||= proc { |*| content }
content_blocks[key.to_sym] << capture_later(&block)
end
def content_for?(key)
content_blocks[key.to_sym].any?
end
def yield_content(key, default = nil)
return default if content_blocks[key.to_sym].empty?
content_blocks[key.to_sym].map { |b| capture(&b) }.join
end
def capture(&block)
#capture = nil
#_erbout, _buf_was = '', #_erbout
result = yield
#_erbout = _buf_was
result.strip.empty? && #capture ? #capture : result
end
def capture_later(&block)
proc { |*| #capture = capture(&block) }
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'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}.
I have a situation in my Rails application where I need to include arbitrary modules depending on the current runtime state. The module provides custom application code that is only needed when certain conditions are true. Basically, I'm pulling the name of a company from the current context and using that as the filename for the module and its definition:
p = self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
include self.class.const_get(self.user.company.subdomain.capitalize.to_sym)
self.custom_add_url
end
My test module looks like this:
module Companyx
def custom_add_url
puts "Calling custom_add_url"
end
end
Now in the console, this actually works fine. I can pull a user and include the module like so:
[1] pry(main)> c = Card.find_by_personal_url("username")
[2] pry(main)> include c.class.const_get(c.user.company.subdomain.capitalize)=> Object
[3] pry(main)> c.custom_add_url
Calling custom_add_url
If I try to run the include line from my model, I get
NoMethodError: undefined method `include' for #<Card:0x007f91f9094fb0>
Can anyone suggest why the include statement would work on the console, but not in my model code?
I'm doing a similar thing. I found this answer useful:
How to convert a string to a constant in Ruby?
Turns out I was looking for the constantize method. This is the line I'm using in my code:
include "ModuleName::#{var.attr}".constantize
Edit:
So ack, I ran into various problems with actually using that line myself. Partially because I was trying to call it inside a method in a class. But since I'm only calling one method in the class (which calls/runs everything else) the final working version I have now is
"ModuleName::#{var.attr}".constantize.new.methodname
Obviously methodname is an instance method, so you could get rid of the new if yours is a class method.
Include is a method on a class.
If you want to call it inside a model, you need to execute the code in the context of its singleton class.
p = self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
myself = self
class_eval do
include self.const_get(myself.user.company.subdomain.capitalize.to_sym)
end
self.custom_add_url
EDIT:
class << self doesn't accept a block; class_eval does, hence it preserves the state of local variables. I've modified my solution to use it.
It's rather hard to find any documentation on Mocha, so I'm afraid I'm totally at sea here. I have found a problem with stubbing methods that pass arguments. So for instance if I set up a class like this:
class Red
def gets(*args)
#input.gets(*args)
end
def puts(*args)
#output.puts(*args)
end
def initialize
#input = $stdin
#output = $stdout
end
private
def first_method
input = gets.chomp
if input == "test"
second_method(input)
end
end
def second_method(value)
puts value
second_method(value)
end
end
Yes it's contrived, but it's a simplification of the idea that you may have a method that you don't want called in the test.
So I might write a test such as:
setup do
#project = Red.new
#project.instance_variable_set(:#input, StringIO.new("test\n"))
#project.stubs(:second_method)
end
should "pass input value to second_method" do
#project.expects(:second_method).with("test").once
#project.instance_eval {first_method}
end
Now I would expect this to pass. But instead I get this rather arcane error message:
Errno::ENOENT: No such file or directory - getcwd
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/backtrace_filter.rb:12:in `expand_path'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/backtrace_filter.rb:12:in `block in filtered'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/backtrace_filter.rb:12:in `reject'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/backtrace_filter.rb:12:in `filtered'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/expectation_error.rb:10:in `initialize'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/mockery.rb:53:in `new'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/mockery.rb:53:in `verify'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/api.rb:156:in `mocha_verify'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/integration/mini_test/version_131_and_above.rb:27:in `run'
This means absolutely nothing to me, other than something deep in Mochas bowels has just gone clang. If I write the same sort of test without an argument passing to the second method I get no problem. Am I missing something?
I think it must be something in shoulda causing the problem. I use test/unit, and everything appears to be OK.
require 'rubygems'
require "test/unit"
require 'mocha'
require File.dirname(__FILE__) + '/../src/red'
class RedTest < Test::Unit::TestCase
def setup
#project = Red.new
#project.instance_variable_set(:#input, StringIO.new("test\n"))
#project.stubs(:second_method)
end
def test_description_of_thing_being_tested
#project.expects(:second_method).with("test").once
#project.instance_eval {first_method}
end
end
gives the following output:
stephen#iolanta:~/tmp/red/test # ruby red_test.rb
Loaded suite red_test
Started
.
Finished in 0.000679 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
stephen#iolanta:~/tmp/red/test #
Sorry - I've only just seen this. It's better to submit bug reports to us in Lighthouse. What documentation have you found? Have you seen the RDoc on Rubyforge? What sort of documentation were you looking for that you did not find?
I've been unable to reproduce your bug. What version of Ruby, Rubygems, Shoulda & Mocha were you using?
You can see the results of me running your test in this Gist.