How do I refer to a submodule's "full path" in ruby? - ruby

In my app, I have
VeryUniqueModule::Foo
# and…
VeryUniqueModule::Bar
Foo and Bar are each for a different service. Part of my app has to dynamically figure out which module to refer to, which it capably does like so:
def service_api
# #relevant_object.service is a string that is either 'Foo' or 'Bar'
VeryUniqueModule.const_get(#relevant_object.service)
end
More on this later.
I just updated a library, and it now has its own top-level Foo class (which is bad design on its part). Now when I try to invoke #relevant_object.service_api::A_CONSTANT, my app complains that the library's Foo does not have A_CONSTANT.
Back to service_api above -- I thought that const_get was returning the class itself. In fact I know it is. If I fire it up in irb everything is as expected -- the return value is the class itself, and I can invoke things on the class. So…
How is it possible that there's a namespace conflict in the first place? I'm looking for A_CONSTANT on the Class object returned by service_api, not on a string that I'm evaling or something funky like that -- there shouldn't be any namespace issues, I'm referring directly to an object!
If this is indeed a problem, how can I fix service_api so that it will return the, erm, "full path"?

You might try this:
VeryUniqueModule.const_get('::VeryUniqueModule::' + #relevant_object.service)
And if that doesn't work, you could try bypassing service_api and doing this wherever you need A_CONSTANT:
Object.const_get('::VeryUniqueModule::' + #relevant_object.service + '::A_CONSTANT')
Note the :: before VeryUniqueModule. I don't think it's strictly necessary in this case, but it could be useful in that it guarantees Ruby will look for VeryUniqueModule in the global namespace and not inside some other module.

Related

How can I determine what objects a call to ruby require added to the global namespace?

Suppose I have a file example.rb like so:
# example.rb
class Example
def foo
5
end
end
that I load with require or require_relative. If I didn't know that example.rb defined Example, is there a list (other than ObjectSpace) that I could inspect to find any objects that had been defined? I've tried checking global_variables but that doesn't seem to work.
Thanks!
Although Ruby offers a lot of reflection methods, it doesn't really give you a top-level view that can identify what, if anything, has changed. It's only if you have a specific target you can dig deeper.
For example:
def tree(root, seen = { })
seen[root] = true
root.constants.map do |name|
root.const_get(name)
end.reject do |object|
seen[object] or !object.is_a?(Module)
end.map do |object|
seen[object] = true
puts object
[ object.to_s, tree(object, seen) ]
end.to_h
end
p tree(Object)
Now if anything changes in that tree structure you have new things. Writing a diff method for this is possible using seen as a trigger.
The problem is that evaluating Ruby code may not necessarily create all the classes that it will or could create. Ruby allows extensive modification to any and all classes, and it's common that at run-time it will create more, or replace and remove others. Only libraries that forcibly declare all of their modules and classes up front will work with this technique, and I'd argue that's a small portion of them.
It depends on what you mean by "the global namespace". Ruby doesn't really have a "global" namespace (except for global variables). It has a sort-of "root" namespace, namely the Object class. (Although note that Object may have a superclass and mixes in Kernel, and stuff can be inherited from there.)
"Global" constants are just constants of Object. "Global functions" are just private instance methods of Object.
So, you can get reasonably close by examining global_variables, Object.constants, and Object.instance_methods before and after the call to require/require_relative.
Note, however, that, depending on your definition of "global namespace" (private) singleton methods of main might also count, so you check for those as well.
Of course, any of the methods the script added could, when called at a later time, themselves add additional things to the global scope. For example, the following script adds nothing to the scope, but calling the method will:
class String
module MyNonGlobalModule
def self.my_non_global_method
Object.const_set(:MY_GLOBAL_CONSTANT, 'Haha, gotcha!')
end
end
end
Strictly speaking, however, you asked about adding "objects" to the global namespace, and neither constants nor methods nor variables are objects, soooooo … the answer is always "none"?

What if objects can print themselves in Ruby? [Object#print]

I was thinking wouldn't it be cool to have a print method defined in the Ruby Object class? Consider the following:
class Object
def print
puts self.to_s
end
end
23.times &:print
Is there any issue in having something like this? Seems like a good feature to have. It also appears easy to read.
There's already Object#inspect defined. Plus, there's already Kernel#print defined as private method in Object class and every class that inherits from it.
This method already exists in the Ruby standard library. However, it has a different name: display.
23.times &:display
# 012345678910111213141516171819202122
As you can see, it does not write a newline after the object's string representation; it is ill-suited for object inspection.
The main issue with adding methods to Object is that they become universal and may clash with similarly named methods in other libraries or in your project.
There are already multiple simple ways to output data or convert to string form in Ruby core, so the risk of a clash (on a very useful method name) likely outweighs any benefits from nicer syntax even in your own code.
If you have a smaller set of classes in your own project, where you feel this would be a useful feature to have, then this is an ideal use case for mix-ins.
Define a module:
module CanPrintSelf
def print
puts self.to_s
end
end
And include it in any class you want to have the feature:
class MyClass
include CanPrintSelf
end
my_object = MyClass.new
my_object.print
So you can have this feature if you like it, and you don't need to modify Object.

Ruby variable scope: access rack.env from inside an existing ruby class?

I have a simple class:
class Repository
class << self
def find(id)
...
end
end
end
It is called like this throughout our app:
thing = Repository.find("abc")
We are in a Sinatra/rack environment. During the request phase we do something like this:
env['org'] = 'acme'
What I'd like to do is be able to get access to 'acme' from inside the class Repository, without having to explicitly pass it in. There are so many calls to this class all over the place that it would be a pain to pass in the value each time through the find method e.g., find(id,org = nil). I thought maybe there's a way to include the rack gem in Repository, and get at it that way, but so far no luck. Global variables are out - has to be scoped to the request.
Is it possible to do something like this?
Personally, I think having a variable that changes like that inside a class method is asking for trouble, it breaks the Law of Demeter by reaching across boundaries. Instead, I'd wrap it in a Sinatra helper which then passes the second argument by default.
helpers do
def find( s )
Repository.find( s, env['org'] )
end
end
and modify the Repository's find method to take the second argument.

Ruby: instantiate objects from files

Overview:
main.rb
items/
one.rb
two.rb
three.rb
Every file in items/ should have a human readable description (serialization is out), like so (but maybe a DSL would be better?):
class One < BaseItem
name "Item one"
def meth
"something"
end
main.rb should be able to instantiate all objects from the items/ directory. How could this be accomplished? Not familiar with Ruby, I see the object model allows for some pretty cool things (those class hooks, etc), but I'm having trouble finding a way to solve this.
Any input way appreciated.
EDIT:
Shoot, I may have missed the gist of it - what I didn't mention was the stuff in the items/ dir would be dynamic — treat items as plugins, I'd want main.rb to autodetect everything in that dir at runtime (possibly force a reload during execution). main.rb has no prior knowledge of the objects in there, it just knows what methods to expect from them.
I've looked at building DSLs, considering defining (in main.rb) a spawn function that takes a block. A sample file in items/ would look something like:
spawn do
name "Item name"
def foo
"!"
end
end
And the innards of spawn would create a new object of the base type and pass the block to instance_eval. That meant I'd need to have a method name to set the value, but incidentally, I also wanted the value to be accessible under name, so I had to go around it renaming the attr.
I've also tried the inherit route: make every item file contain a class that inherits from a BaseItem of sorts, and hook into it via inherited ... but that didn't work (the hook never fired, I've lost the code now).
EDIT2:
You could look at what homebrew does with its formulas, that's very close to what I'd want - I just didn't have the ruby prowess to reverse engineer how it handles a formula.
It all boils down to requiring those files, and make sure that you implemented the functionality you want in them.
If you want a more specific response, you need to ask a more specific question.
I am no expert on object persistence, but answer to your specific question is, that you have 2 good choices: One is YAML, and the other is Ruby itself: a DSL written by you or someone else, and specific to your business logic.
But I think that more general answer would require reviewing object persistance in Ruby more systematically. For example, ActiveRecord::Base descendants persists as database tables. There are other ways, I found eg. this http://stone.rubyforge.org/ by googling. This is my problem as well, I'm facing the same question as you in my work.
What you are asking for looks and smells a lot like a normal Ruby script.
class One < BaseItem
name "Item one"
def meth
"something"
end
We'd close the class definition with another end statement. name "Item one" would probably be done inside the initialize method, by setting an instance variable:
attr_reader :name
def initialize(name)
#name = name
end
Typically we wouldn't call the folder "items", but instead it would be "lib", but otherwise what you are talking about is very normal and expected.
Instantiating all items in a folder is easily done by iterating over the folder's contents, requiring the files, and calling the new method for that item. You can figure out the name by mapping the filename to the class name, or by initializing an instance at the end of the file:
one = One.new("item one")
You could keep track of the items loaded in an array or hash, or just hardwire them in. It's up to you, since this is your code.
It sounds like you haven't tried writing any Ruby scripts, otherwise you would have found this out already. Normal Ruby programming books/documentation would have covered this. As is, the question is akin to premature optimization, and working with the language would have given you the answer.

Loading external files within a class/module

I have an external file: path_to_external_file.rb with some class definition:
class A
some_definitions
end
And I want to load that within module B so that the class A defined above can be referred to as B::A. I tried:
class B
load('path_to_external_file.rb')
end
but A is defined in the main environment, not in B:
A #=> A
B.constants # => []
How can I load external files within some class/module?
Edit
Should I read the external files as strings, and evaluate them within Class.new{...}, and include that class within B?
You cannot. At least using load or require, the Ruby files will always be evaluated in a top context.
You can work around that problem in two ways:
Define class B::A directly (but you are probably trying to avoid that)
Use eval(File.read("path_to_external_file.rb")) within your B class
Edit: Maybe, this library is interesting for you: https://github.com/dreamcat4/script/blob/master/intro.txt
Generally, it's a bad idea to define a class as "class A" but then "magically" make it contained by module B. If you want to refer to class A as B::A, you should define it using either:
module B
class A
# contents
end
end
or:
class B::A
# contents
end
Otherwise anyone who reads your code will be confused. In this case, you don't gain anything in clarity, brevity, or convenience by using "tricks", so straightforward code is better. There is a lesson here: the metaprogramming features of Ruby are great, but there is no need to use them gratuitously. Only use them when you really gain something from doing so. Otherwise you just make your code hard to understand.
BUT, having read your comment, it looks like there is really a good reason to do something like this in your case. I suggest that the following solution would be even better than what you are envisioning:
m = Module.new
m.module_eval("class C; end")
m.constants
=> [:C]
m.const_get(:C)
=> #<Module:0xfd0da0>::C
You see? If you want a "guaranteed unique" namespace, you can use an anonymous module. You could store these modules in a hash or other data structure, and pull the classes out of them as needed. This solves the problem you mentioned, that the users of your app are going to be adding their own classes, and you don't want the names to collide.

Resources