We are observing a strange behavior on our embedded ruby application. We have stripped down the code to bare minimal and were able to re-produce the issue. Following are the details.
1. The ruby code
#!/usr/bin/env ruby
#require "MyLibrary.so" *// Works fine*
module AA
class BC
def initialize
end
def loadFunction
require "MyLibrary.so" *//Gives error*
end
end
end
#Invoke the method
AA::BC.new().loadFunction
2. Source code for MyLibrary.so
#include "ruby.h"
const char loop[] =
"def loopFunc\n"
"puts \"HERE\"\n"
"end\n"
"begin\n"
"loopFunc()\n"
"rescue StandardError\n"
"puts $!\n"
"puts $!.class\n"
"end\n";
void initialize()
{
ruby_init();
ruby_init_loadpath();
rb_eval_string(loop);
}
extern "C" void Init_MyLibrary()
{
initialize();
}
When we require "MyLibrary.so" file inside the loadFunction in rb file, we are getting following error
undefined method `loopFunc' for main:Object
NoMethodError
However when we require at top of the rb file everything works fine.
Our first guess was that rb_eval_string() gets executed inside the module AA. So loopFunc is getting defined inside the module AA instead of being global. Therefore NoMethodError is being reported. When we invoked AA::BC.new().loopFunc() inside the cpp file, the method gets invoked successfully; which confirmed our guess.
Is this the expected behavior from an embedded ruby point of view because if we require an rb file(instead of .so) with the same code as passed to rb_eval_string, we are not getting any error.
rb_eval_string() does define the methods in the module from which it is invoked. We can either use rb_require/rb_load to get the correct behavior.
Related
Almost all of you used URI module to convert a url string to an object in order to make some validation or change.
Example:
require 'uri'
URI('https://google.com')
# => #<URI::HTTPS https://google.com>
As you can see, the result is the HTTPS object under the URI module.
So, there is a question what is run when you write module/class name with round braces like the line of code above.
I thought, it is implicit calling of call method, but I got NoMethodError.
Example:
class MyClass
def self.call
puts 'You were right!'
end
end
MyClass()
# => NoMethodError: undefined method `MyClass' for main:Object
Funny enough, with the code you've shown, MyClass.() works (which is an alias for .call).
However in the case of URI, this actually a method (methods can begin with capitals). You can see the source code here: https://apidock.com/ruby/Kernel/URI/instance
I would like to create a basic ruby script that renders Slim templates into html (This would eventually be part of a larger project). Ideally I would like to use the HTML produced within the script.
I understand this is possible using TILT (as shown in the SLIM README) where it says the following:
Slim uses Tilt to compile the generated code. If you want to use the Slim template directly, you can use the Tilt interface.
Tilt.new['template.slim'].render(scope)
Slim::Template.new('template.slim', optional_option_hash).render(scope)
Slim::Template.new(optional_option_hash) { source }.render(scope)
The optional option hash can have to options which were documented in the section above. The scope is the object in which the template code is executed.
However, I'm still unable to successfully run this. Therefore, I was wondering if someone could help me by producing a working example.
EDIT (this has recently been edited further ):
I have played about with the code quite a bit but I keep on getting the following error:
undefined local variable or method `source' for main:Object (NameError)
This is what i'm running:
require 'slim'
# I'm not sure about the next two lines...
optional_option_hash = {}
scope = Object.new
Tilt.new('template.slim').render(scope)
Slim::Template.new('template.slim', optional_option_hash).render(scope)
Slim::Template.new(optional_option_hash) { source }.render(scope)
Many Thanks for all your help.
See Specifying a layout and a template in a standalone (not rails) ruby app, using slim or haml
This is what I ended up using:
require 'slim'
# Simple class to represent an environment
class Env
attr_accessor :name
end
scope = Env.new
scope.name = "test this layout"
layout =<<EOS
h1 Hello
.content
= yield
EOS
contents =<<EOS
= name
EOS
layout = Slim::Template.new { layout }
content = Slim::Template.new { contents }.render(scope)
puts layout.render{ content }
For the scope, you can put in modules/classes or even self.
Quick essentials of
module SlimRender
def slim(template, variables = {})
template = template.to_s
template += '.slim' unless template.end_with? '.slim'
template = File.read("#{ROOT}/app/views/#{template}", encoding: 'UTF-8')
Slim::Template.new { template }.render OpenStruct.new(variables)
end
end
Include SlimRender to your class and:
def render_something
slim 'streams/scoreboard', scores: '1-2'
end
I am trying to write an application with Ruby and Qt. I have the following code:
list = Qt::ListView.new(window)
mod1 = MyModel.new #MyModel inherits from Qt::AbstractListModel
list.model = mod1 #<< Fails on this line
But it fails, telling me:
undefined method `model=' for #<Qt::ListView:0x0000000067e300>
Yet I see other posts on SO that use model=, and I see the method listed in IRB when I issue list.public_methods.
OK, the problem is that I wasn't calling super() in the initialize method of my model.
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
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.