Calling a module inside another module - ruby

I am calling a method in a module from another module and am getting a weird error.
require 'nmap'
...
module Enumeration::Hostnames
def reverse_dns ip_addrs
...
ip_addrs.each do |ip_addr|
list = ListScan.test ip_addr #this is the problem
...
end
...
ListScan is in the nmap file.
module ListScan
def ListScan.test target
target = '-sL ' + target
ListScan::parse_results Nmap::Parser.parsescan('nmap',target)
end
...
end
The error is `const_missing': uninitialized constant Enumeration::Hostnames::ListScan (NameError) on the line ListScan.test ip_addr.
Why is it assuming that ListScan is in the Enumeration::Hostnames module? Mixing in ListScan to Hostnames didn't work.

Ruby searches for constants starting from the current context, which in this case is Enumeration::Hostnames.
Try using
::ListScan.test ip_address

Related

Ruby encapsulate/package existing code into a namespace

I've been looking all around and didn't found any kind of answer to the problem i'm facing in Ruby. I'm writing an app that use core modules sets that are available in different versions. If I'm sourcing a core set version after an another one both code version will be sourced at the same and will clash with each other. That is quite normal and I'm ok with this.
One approach could be to unload the previous version to load the new one but I'd like to keep all the ones loaded into specific namespaces (to avoid time consuming to unload/reload code all the time). 2 possible solutions to me (or maybe other)
Either source the code then move it into a version namespace (some clues to do it see below but doesn't work yet)
Or source the code directly into a version namespace (don't know how to do it exactly, maybe with module_eval but need to recode the require process with dependencies). Does any solution seems possible ?
Here is a very simple poc of what I'm trying to achieve
file : coreset_1.0.0.rb
module CoreA
def self.who_am_i?; self.to_s; end
def self.get_coreb; CoreB end
end
module CoreB
def self.who_am_i?; self.to_s; end
end
file : coreset_2.0.0.rb (got some changes)
module CoreA
def self.my_name; self.to_s; end
def self.get_coreb; CoreB end
end
module CoreB
def self.my_name; self.to_s; end
end
file : coreManager.rb
module CoreManager
def self.load_version(arg_version)
#Create a module set for the selected version
core_set_name = CoreSet + '_' + arg_version.gsub('.', '_')
core_set = eval("Module #{core_set_name}; end; #{core_set_name}"
#Load the requested code
require "coreset_#{arg_version}.rb"
#Move loaded code into it core set module
core_set.const_set(:CoreA, Object.send(:remove_const, :CoreA))
core_set.const_set(:CoreB, Object.send(:remove_const,:CoreB))
#Return the created core set
core_set
end
end
If running the code :
require 'coreManager.rb'
core_set = CoreManager.load_version("1.0.0")
puts core_set::CoreA.who_am_i?
puts core_set::CoreA.get_coreB
it returns :
CoreA #not CoreSet_1_0_0::CoreA
uninitialized constant CoreA::CoreB (NameError)
If running something statically defined, it works
module CoreSet
module CoreA
def self.who_am_i?; self.to_s; end
def self.get_coreb; CoreB end
end
module CoreB
def self.who_am_i?; self.to_s; end
end
end
CoreSet::CoreA.get_coreb
It returns as expected :
CoreSet::CoreB
Depite of what is usually said :"a module is a constant", it seems to be more than that. What are the differencies and how to make the dynamic version working ?
Any other ideas ?
Thanks for your help folks :)
There are several things broken in your code (which is ok for POC, I guess), but the main one is that require loads constants and globals into the global namespace.
So, your Core<X> modules are not namespaced this way, as you might expect. There is Kernel#load method that allows "wrapped" execution of the loaded file, but it's wrapped into an anonymous module, so you can prevent the global namespace from being polluted, but you cannot "target" the constants to be defined into a particular namespace this way.
What you could try is to load the code as text and then eval it within the dynamically created module, matching your version. For example, look at this quick and very dirty sketch (coreset_...rb files are expected to sit into coresets directory):
module CoreSet; end
class CoreManager
class << self
def load_version(ver)
raise "Vesion #{ver} unknown" unless exists?(ver)
file = filename(ver)
code = File.read(file)
versioned_wrapper = Module.new do
class_eval code
end
CoreSet.const_set("V_#{ver.gsub('.', '_')}", versioned_wrapper)
end
private
def exists?(ver)
File.exists? filename(ver)
end
def filename(ver)
"coresets/coreset_#{ver.gsub('.', '_')}.rb"
end
end
end
CoreManager.load_version("1.0.0")
CoreManager.load_version("2.0.0")
p CoreSet::V_1_0_0::CoreA.who_am_i? # => "CoreSet::V_1_0_0::CoreA"
p CoreSet::V_1_0_0::CoreA.get_coreb # => CoreSet::V_1_0_0::CoreB
p CoreSet::V_2_0_0::CoreA.my_name # => "CoreSet::V_2_0_0::CoreA"
p CoreSet::V_2_0_0::CoreA.get_coreb # => CoreSet::V_2_0_0::CoreB
But, DON'T do this at home, please :) At least, I would think twice.
If all you need is to have all the versions loaded at once (you need namespaces exactly for this, right?) what stops you from defining them statically, like CoreSet::V1::Core<X> etc and use the idiomatic and safe ways to (auto)load them? :) Playing with dynamic nested constants definition (and, especially, their removing) is one of the easiest ways to shoot your own foot...
Ok I finally came to a solution that may help others or that can be discussed.
Getting the error uninitialized constant CoreA::CoreB (NameError) leads me to take the problem under a new angle. If I'm not able to access to CoreB module from CoreA (because the module nesting has been broken when redefining module constants into the CoreSet module) then why not referencing in each core module the other ones in the set ? And finaly it works without any dirty hack, I'm just creating pointers and the Ruby Core find it natively ;)
module CoreManager
def self.load_version(arg_version)
#Create a module set for the selected version
core_set_name = CoreSet + '_' + arg_version.gsub('.', '_')
core_set = eval("Module #{core_set_name}; end; #{core_set_name}"
#Load the requested code
toplevel_consts = Object.constants
require "coreset_#{arg_version}.rb"
core_modules = Object.constants - toplevel_consts
#Move the core modules to the set namespace
core_modules.collect! do |core_module|
core_module_sym = core_module.to_s.to_sym
core_set.const_set(core_module_sym, Object.send(:remove_const, core_module_sym))
eval("#{core_set}::#{core_module}")
end
#Create connexion between set cores to skirt broken module nesting
core_modules.each do |current_core|
core_modules.each do |other_core|
current_core.const_set(other_core.to_s.to_sym, other_core) unless current_core == other_core
end
end
#Return the created core set
core_set
end
end

Call EXCEL method from Module

Creating module:
module CF
def work_sheet(excel_doc_name, workbook_name)
dir_to_excel = Dir.pwd + '/lib/config/data/'
read_workbook = RubyXL::Parser.parse("#{dir_to_excel}#{excel_doc_name}")
worksheet = read_workbook["#{workbook_name}"]
end
end
Call this method from module:
Given(/^Excel read$/) do
include CF
work_sheet("Login.xlsx", "Login_info")
end
Error message:
NoMethodError: undefined method `work_sheet' for #<Object:0x442f158>
Please check
To include the methods of a module in a step definition, you need to add the module to the World.
In your env.rb (or a file required by it) add the following line (after you have required the module file):
World(CF)
You should also remove the include CF line from the step definition.

undefined constant ParseConfig

Actually i'm experimenting with ruby-lint and ruboto to improve my code. ruby-lint says:
"get.rb: error: line 89, column 14: undefined constant ParseConfig"
On that place i have the marked code:
require 'parseconfig'
module PublicanCreatorsGet
def self.config
home = Dir.home
config = ParseConfig.new("#{home}/.publicancreators.cfg") <-------
end
end
But what makes this to a constant? I thought they are UPPERCASED.
A variable whose name begins with an uppercase letter (A-Z) is a constant.
Ruby treated class name as constant, if you really want to solve the error you can try following in your module
require 'parseconfig'
module PublicanCreatorsGet
include ParseConfig
def self.config
home = Dir.home
config = ParseConfig.new("#{home}/.publicancreators.cfg")
end
end

How do I extend a Ruby class inside a module?

I'm trying to extend the File class inside a module. Here's my (abbreviated) code:
module Wireshark
# Extend the file class to write a header comment
class File
def write_header
self.puts '/* ' + Wireshark::AUTOGEN_LABEL + ' */'
self.puts '/* ' + Wireshark::timestamp + ' */'
end
end
# Read a file
def read
begin
file = File.read 'file'
rescue IOError
STDERR.puts 'Error reading file.'
return
end
end
end
When I run my code, I'm getting
undefined method `read' for Wireshark::File:Class (NoMethodError)
when I try to run file.read. I tried getting rid of the module encapsulation, but I'd like to only extend the File class inside my module, not in the rest of my program.
You're close.
module Wireshark
module File
def write_header
self.puts '/* ' + Wireshark::AUTOGEN_LABEL + ' */'
self.puts '/* ' + Wireshark::timestamp + ' */'
end
end
# Extend File with the methods in the Wireshark::File module
::File.send :include, Wireshark::File
# Read a file
def read
begin
file = ::File.read 'file'
rescue IOError
STDERR.puts 'Error reading file.'
return
end
end
end
The general idea here is that we define a Wireshark::File module that holds the methods you want to include on the File class, then you can just include them on File directly.
You'll also notice that in the read method, I changed File to ::File. Ruby will walk up the tree to try to find the nearest matching value for a given constant, so since you are in the Wireshark module, and there is a constant named File in that scope, using just File gets you Wireshark::File. Specifying ::File means "The File constant at the top-level namespace".
In the current version of Ruby it's not possible to do this, but it is a popular proposed extension called "refinements".
If you need to patch the core File class, you'll need to do that as a monkey patch, and this affects all instances of File anywhere in your Ruby process.
Normally you can do it this way:
# Define the instance methods you want to overide
module MyFileHacksInstanceMethods
def read
# ... (reimplementation) ...
end
end
# Force load these in the File class
class File
include MyFileHacksInstanceMethods
end
What you've declared in your example here is an independent class called Wireshark::File because it's within that module's namespace. ::File is the main class (:: being a prefix to force absolute name, sort of like / for filesystems).

DRY within a Chef recipe

What's the best way to do a little DRY within a chef recipe? I.e. just break out little bits of the Ruby code, so I'm not copying pasting it over and over again.
The following fails of course, with:
NoMethodError: undefined method `connect_root' for Chef::Resource::RubyBlock
I may have multiple ruby_blocks in one recipe, as they do different things and need to have different not_if blocks to be truley idempotent.
def connect_root(root_password)
m = Mysql.new("localhost", "root", root_password)
begin
yield m
ensure
m.close
end
end
ruby_block "set readonly" do
block do
connect_root node[:mysql][:server_root_password] do |connection|
command = 'SET GLOBAL read_only = ON'
Chef::Log.info "#{command}"
connection.query(command)
end
end
not_if do
ro = nil
connect_root node[:mysql][:server_root_password] do |connection|
connection.query("SELECT ##read_only as ro") {|r| r.each_hash {|h|
ro = h['ro']
} }
end
ro
end
end
As you already figured out, you cannot define functions in recipes. For that libraries are provided. You should create a file (e.g. mysql_helper.rb ) inside libraries folder in your cookbook with the following:
module MysqlHelper
def self.connect_root( root_password )
m = Mysql.new("localhost", "root", root_password)
begin
yield m
ensure
m.close
end
end
end
It must be a module, not a class. Notice also we define it as static (using self.method_name). Then you will be able to use functions defined in this module in your recipes using module name with method name:
MysqlHelper.connect_root node[:mysql][:server_root_password] do |connection|
[...]
end
For the record, I just created a library with the following. But that seems overkill for DRY within one file. I also couldn't figure out how to get any other namespace for the module to use, to work.
class Chef
class Resource
def connect_root(root_password)
...

Resources