In node.js you can write:
var lib = require('lib');
but in Ruby the require function simply runs the code in the file and true is returned.
Currently, I'm using a very dirty solution:
main.rb:
$stuff = []
require './file1.rb'
require './file2.rb'
# and so on
file1.rb:
$stuff << something
and so on.
How can I eliminate the use of a global variable?
eg:
main.rb:
$stuff = []
$stuff << cool_require './file1.rb'
# etc
file1.rb:
exports.what = something
One of the biggest errors when working with a language, is trying to make the language working like a different one.
Ruby is not NodeJs, there are features built-in into each language that are unique to the language and cannot be reproduced easily.
In other words, there is no way to implement the NodeJS require behavior in Ruby because in Ruby there is no notion of export. When you require a file, every method/class included in the required file are made available to the scope.
In Ruby there are objects and method visibility. The way you have to make a method visible or not is to declare it as public or private/protected.
Well, first consider that Ruby is not Node.js. As Simone Carletti said, there are some features that are unique to each language. Sometimes it's good to take from other language but sometimes it's bad.
There are few things that you must keep in mind:
meth is method invocation, to pass method you use method method: method(:meth) or package it into module/class
you can use class/module by assigning it to some 2nd variable:
class A;
def self.aa; puts 'aa'; end;
end;
New_a = A;
New_a.aa # aa;
eval is dangerous method(you can evaluate unknown code)
Method:
Here is one way you can do. It is not idiot-proof tough. It is not 100% safe(eval). :
file1.rb:
Module.new do
def self.meth1
42
end
def self.meth2
'meth2'
end
end
This file contain module with 2 methods.
I am using Module.new because it returns object that you want. You can assign it later into variable/constant.
I am using self.meth* so you don't have to include but run instantly it like this: module_name.meth1()
req.rb:
def cool_require name
eval(File.read name)
end
Some_variable = cool_require('req.rb')
puts Some_variable.meth1 # 42
puts Some_variable.meth2 # meth2
cool_require reads filename(argument name) and evaluate it(it is just like you would type it in irb/pry)
Some_variable is constant. It won't disappear that easily.
2 last line is how it works. As fair I remember, that's how node.js' require works.
Related
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
Is there a better way to get class/module name viz, C from A::B::C, B from A::B::C, and A From A::B::C. The following code uses string and split to get "Stegosaurus" from Cowsay::Character::Stegosaurus, How to do away with string and split?
require 'cowsay'
x = Cowsay.random_character().class
x.name.split("::")[2]
require 'cowsay'
true
x = Cowsay.random_character().class
Cowsay::Character::Stegosaurus
x.name.split("::")[2]
"Stegosaurus"
I don't think there's anything for handling this in core/standard library.
As an alternative to custom written methods there is always activesupport:
require 'active_support/core_ext/string/inflections'
Cowsay::Character::Stegosaurus.name.demodulize
#=> "Stegosaurus"
Cowsay::Character::Stegosaurus.name.deconstantize
#=> "Cowsay::Character"
These methods are implemented as follows:
def demodulize(path)
path = path.to_s
if i = path.rindex('::')
path[(i+2)..-1]
else
path
end
end
def deconstantize(path)
path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
end
Take a look into docs if interested in more methods.
As to
A From A::B::C
If you'd require the whole activesupport, you'd get bunch of Module methods, including parent:
require 'active_support'
Cowsay::Character::Stegosaurus.parent
#=> Cowsay
If you're not going to use it extensively, I recommend to just grab the needed methods from activesupport and put it into some helper, because loading it whole might be an overkill.
The need here is to read a json file and to make the variables which is done from one class and use them with in another class. What I have so far is
helper.rb
class MAGEINSTALLER_Helper
#note nonrelated items removed
require 'fileutils'
#REFACTOR THIS LATER
def load_settings()
require 'json'
file = File.open("scripts/installer_settings.json", "rb")
contents = file.read
file.close
#note this should be changed for a better content check.. ie:valid json
#so it's a hack for now
if contents.length > 5
begin
parsed = JSON.parse(contents)
rescue SystemCallError
puts "must redo the settings file"
else
puts parsed['bs_mode']
parsed.each do |key, value|
puts "#{key}=>#{value}"
instance_variable_set("#" + key, value) #better way?
end
end
else
puts "must redo the settings file"
end
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
Which is called in a file of Pre_start.rb
class Pre_start
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
mi_h = MAGEINSTALLER_Helper.new
mi_h.load_settings()
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
file="www/depo/newfile-#{bs_MAGEversion}.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{bs_MAGEversion}/file-#{bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
the josn file is valid json and is a simple object (note there is more just showing the relevant)
{
"bs_mode":"lite",
"bs_MAGEversion":"1.8.0.0"
}
The reason I need to have a json settings file is that I will need to pull settings from a bash script and later a php script. This file is the common thread that is used to pass settings each share and need to match.
Right now I end up with an empty string for the value.
The instance_variable_setis creating the variable inside MAGEINSTALLER_Helper class. That's the reason why you can't access these variables.
You can refactor it into a module, like this:
require 'fileutils'
require 'json'
module MAGEINSTALLER_Helper
#note nonrelated items removed
#REFACTOR THIS LATER
def load_settings()
content = begin
JSON.load_file('scripts/installer_settings.json')
rescue
puts 'must redo the settings file'
{} # return an empty Hash object
end
parsed.each {|key, value| instance_variable_set("##{key}", value)}
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
class PreStart
include MAGEINSTALLER_Helper
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
load_settings # The method is available inside the class
file="www/depo/newfile-#{#bs_MAGEversion}.tar.gz"
if !File.exist?(file)
download("http://www.dom.com/#{#bs_MAGEversion}/file-#{#bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
I refactored a little bit to more Rubish style.
On this line:
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
instance_variable_get isn't retrieving from the mi_h Object, which is where your value is stored. The way you've used it, that line is equivalent to:
bs_MAGEversion=#bs_MAGEversion
Changing it to mi_h.instance_variable_get would work. It would also be painfully ugly ruby. But I sense that's not quite what you're after. If I read you correctly, you want this line:
mi_h.load_settings()
to populate #bs_MAGEversion and #bs_mode in your Pre_start object. Ruby doesn't quite work that way. The closest thing to what you're looking for here would probably be a mixin, as described here:
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
We do something similar to this all the time in code at work. The problem, and solution, is proper use of variables and scoping in the main level of your code. We use YAML, you're using JSON, but the idea is the same.
Typically we define a constant, like CONFIG, which we load the YAML into, in our main code, and which is then available in all the code we require. For you, using JSON instead:
require 'json'
require_relative 'helper'
CONFIG = JSON.load_file('path/to/json')
At this point CONFIG would be available to the top-level code and in "helper.rb" code.
As an alternate way of doing it, just load your JSON in either file. The load-time is negligible and it'll still be the same data.
Since the JSON data should be static for the run-time of the program, it's OK to use it in a CONSTANT. Storing it in an instance variable only makes sense if the data would vary from instance to instance of the code, which makes no sense when you're loading data from a JSON or YAML-type file.
Also, notice that I'm using a method from the JSON class. Don't go through the rigamarole you're using to try to copy the JSON into the instance variable.
Stripping your code down as an example:
require 'fileutils'
require 'json'
CONTENTS = JSON.load_file('scripts/installer_settings.json')
class MAGEINSTALLER_Helper
def download(from,to)
puts "completed download for #{from}\n"
end
end
class Pre_start
def initialize(params=nil)
file = "www/depo/newfile-#{ CONFIG['bs_MAGEversion'] }.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{ CONFIG['bs_MAGEversion'] }/file-#{ CONFIG['bs_MAGEversion'] }.tar.gz", file)
else
puts "mage package exists"
end
end
end
CONFIG can be initialized/loaded in either file, just do it from the top-level before you need to access the contents.
Remember, Ruby starts executing it at the top of the first file and reads downward. Code that is outside of def, class and module blocks gets executed as it's encountered, so the CONFIG initialization will happen as soon as Ruby sees that code. If that happens before you start calling your methods and creating instances of classes then your code will be happy.
I have just written my first terminal application in ruby. I use OptionParser to parse the options and their arguments. However I want to create commands. For example:
git add .
In the above line, add is the command which cannot occur anywhere else than immediately after the application. How do I create these.
I will appreciate if anyone could point me in the right direction. However, please do not reference any gems such as Commander. I already know about these. I want to understand how it is done.
The OptionParser's parse! takes an array of arguments. By default, it will take ARGV, but you can override this behaviour like so:
Basic Approach
def build_option_parser(command)
# depending on `command`, build your parser
OptionParser.new do |opt|
# ...
end
end
args = ARGV
command = args.shift # pick and remove the first option, do some validation...
#options = build_option_parser(command).parse!(args) # parse the rest of it
Advanced Approach
Instead of a build_option_parser method with a huge case-statement, consider an OO approach:
class AddCommand
attr_reader :options
def initialize(args)
#options = {}
#parser = OptionParser.new #...
#parser.parse(args)
end
end
class MyOptionParser
def initialize(command, args)
#parser = {
'add' => AddCommand,
'...' => DotsCommand
}[command.to_s].new(args)
end
def options
#parser.options
end
end
Alternatives
For sure, there exist tons of Rubygems (well, 20 in that list), which will take care of your problem. I'd like to mention Thor which powers, e.g. the rails command line tool.
You can retrieve the command with Array#shift prior invoking OptionParser.
As we know, there are several way of Proc calling in Ruby 1.9
f =->n {[:hello, n]}
p f[:ruby] # => [:hello, :ruby]
p f.call(:ruby) # => [:hello, :ruby]
p f.(:ruby) # => [:hello, :ruby]
p f === :ruby # => [:hello, :ruby]
I am curious, what is more 'natural' way of calling Proc? 'Natural', probably, means more Computer Science - like way.
The second option is by far the most used.
p f.call(:ruby) # => [:hello, :ruby]
It makes it more similar to a standard method. Also, some libraries actually rely on duck typing when validating arguments checking the availability of the #call method. In this case, using #call ensures you can provide a lambda or any other object (including a Class) that responds to #call.
Rack middlewares are a great example of this convention. The basic middleware can be a lambda, or you can supply more complex logic by using classes.
I always use option 3. Considering the syntactic ambiguities of being able to call methods without parentheses, this is the closest you can get to actual method call syntax.
I saw the first way used in Rack source code. It confused me in a long time. It's picked from lib/rack/builder.rb (version: 1.6.0.alpha)
module Rack
class Builder
...
def to_app
app = #map ? generate_map(#run, #map) : #run
fail "missing run or map statement" unless app
# This is the first option calling a proc
# #use is a array of procs (rack middleware)
#use.reverse.inject(app) { |a,e| e[a] }
end
...
end
end