Is there a way to do relative path references from a superclass? - ruby

I'm developing two Ruby gems, one is a framework, the other is an application.
My main app's class inherits from a framework's class.
The class defined in the framework have some file loading methods that rely on _____FILE_____ so subclasses can load resources relative to their path.
What I want is for the subclasses to be able to use those methods defined in the parent without (basically) touching them. Is it possible?
The problem is with _____FILE_____, it doesn't change if the code is called from a subclass, so the loading resource methods are "stuck" in the frameworks directory, instead of trying to load from the app dirs.
I know I can redefine those methods in the subclass, but I wanted to take advantage of them being already defined in the parent.
I want the subclasses to refer to their own directory (where the subclass file is) using a method defined in the parent, that's the problem.
Or do I have to set the app directory by hand?
I'll try to make that clearer:
Is it possible to write a method like this:
# /home/usr/framework/parent.rb
class Parent
def show_my_path
puts File.dirname(__FILE__)
end
end
# /home/usr/app/app.rb
# require Parent
class App < Parent
end
App.new.show_my_path
# Can we have here /home/usr/app
# instead of /home/usr/framework ?
# What's the right way to do this?

As We know that __FILE__ will have current file name whatever it is. so you can try this -
# /home/usr/framework/parent.rb
class Parent
def show_my_path(filename)
puts File.dirname(filename)
end
end
# /home/usr/app/app.rb
# require Parent
class App < Parent
end
App.new.show_my_path(__FILE__)
What say?

__FILE__ is transform during the code read. So it can't be change. Even in a proc.
You can try to extract information with caller method maybe.

Related

Find directory of subclass before initialization to configure dry-view paths

I'm using dry-views in multiple Rails engines and I have to duplicate the configuration in every subclass.
class BaseView < Dry::View::Controller
configure do |c|
c.paths = [File.join(__dir__, 'templates')]
end
end
class SubView < BaseView
configure do |c|
c.paths = [File.join(__dir__, 'templates')] # todo: remove me
end
end
The reason is, that my views can be deeply nested in a sub folder of app ie.:
app/
app/foo/index.rb
app/foo/templates/index.html.erb
app/foo/bar/show.rb
app/foo/bar/templates/show.html.erb
Additionally the BaseView class does not live in the same gem in most of the cases.
If I delete the configure block from the SubView class, the template is not found anymore. The __dir__ variable contains the directory path of the BaseView class.
I've tried to implement a after initialization method in the base class that has access to the directory of the subclass. But at that point the configuration is not possible anymore due to restrictions in dry-rb configuration. The configuration must happen before initialization.
The only solution I could come up with is to duplicate the configure block in each class, or have a gem/engine specific parent class that configures all possible template paths.
The usual approach of looking for the directory of a certain method that is implemented in each subclass does also not work in this case, since most views don't even define methods.
Is there better ways to access the directory of a given class during the load phase of this class in a method of the parent class?
class BaseView < Dry::View::Controller
def self.inherited(child)
child.class_eval do
configure do |c|
c.paths = [File.join(__dir__, 'templates')]
end
end
end
end
Callback Class#inherited.

Why must I write 'require' to access module-wide methods in Ruby?

I have a Ruby module in a file called my_module.rb:
module My_module
def my_module_method
puts 'inside my method'
end
end
In a file my_class.rb in the same folder, I have a class contained within the module.
module My_module
class My_class
def my_object_method
My_module.my_module_method
end
end
end
My_module::My_class.new.my_object_method => 'undefined method 'my_module_method''
I was not expecting this error. I assumed that Ruby would run into the line 'My_module.my_module_method' and search for a module called 'My_module' and a method within it called 'my_module_method.' This is what Java does, for example. However, Ruby does not do this. In order to get my_object_method to work, I have to write in my_class.rb:
require 'my_module.rb'
Why doesn't Ruby search for My_module when I call my_object_method? It seems obvious what it should search for and therefore redundant to require the programmer to explicitly write 'yes, Ruby, please allow me to make calls to module-wide methods.' What am I missing?
Ruby doesn't automatically load files. If you need a code from some file, you have to load it (by calling require) explicitly.
Thus, when you run "ruby my_class.rb" it loads only this file and you have to define dependencies between files by yourself.
You seem to have a misunderstanding of how to define a class method. In order to make your method call work, you could define it as def self.my_method_name.
In both classes and modules, methods work the same when you define them as class methods using self. or alternatively the class << self syntax. However instance methods (methods without the self.) work differently in these 2 cases. In classes, as you seem to understand, they're accessible once you instantiate the class using .new. In modules, they're only accessible if you include or extend.
See also:
difference between class method , instance method , instance variable , class variable?
http://www.rortuts.com/ruby/ruby-include-vs-extend/
Oh any by the way. Ruby doesn't enforce any convention where you have 1 file per class (named identically). You need to manually require files wherever you need them. Although there are some frameworks such as Rails which auto-require files, and enforce naming conventions.

Refer to method in module with same name as class

I have a class and a module which have the same names:
module Pushover
def configure
..
end
end
module MyModule
class Pushover
def blah
Pushover.configure
end
end
end
This doesn't work because the Pushover.configure call directs to the containing class. Now, an obvious fix would be to rename the class. However, the Module is from a gem and the class conforms to a naming convention required in a DSL. So ideally they should both stay the same. I could also create a second helper class and call via that, but that all seems a little hacky. My preferred solution would be to directly reference the module method.
All the existing questions around this area seem to be disambiguating in the opposite direction - i.e. they want to get the class reference not the module.
Is there any way for me to inform Ruby that I mean the module rather than the class when I specify Pushover?
If you don't want to look up the constant relative to the current scope, just use an absolute path:
::Pushover.configure

Avoiding globals with dynamically loaded ruby files

I am in a position at the moment where I have a plugins folder where there there could be 1 or 100 plugins to be loaded.
Now the problem is, each plugin requires an instance of a class defined within the startup ruby file.
A really simplified example would be:
#startup.rb
def load_plugins
#... get each plugin file
require each_plugin
end
class MuchUsedClass
def do_something
#...
end
end
muchUsedInstance = MuchUsedClass.new
load_plugins
#some_plugin.rb
class SomePluginClass
def initialize(muchUsedInstance)
#muchUsedInstance = muchUsedInstance
end
def do_something_with_instance
#muchUsedInstance.do_something
end
end
somePluginInstance = SomePluginClass.new(muchUsedInstance)
somePluginInstance.do_something_with_instance
The main problem is that when you call require, it doesn't have any clue about what has happened before it is being required. So I find it nasty making a global variable within the startup file just to satisfy all other required files, but it seems like one of the only ways to be able to pass some data down to an included file, I could also make a singleton class to expose some of this, but that also seems a bit nasty.
As I am still new to ruby and am still looking through the statically typed glasses, I will probably be missing a decent pattern to solve this, in C# I would opt for dependency injection and just hook everything up that way...
Your example code does not have a global variable. Global variables have names that start with $. The code as you wrote it won't work, because muchUsedInstance is just a local variable and will not be shared between different Ruby files.
If you are not going to change the instance ever, you could easily store it as a constant:
MuchUsedInstance = MuchUsedClass.new
You could store it as a nested constant inside the class:
MuchUsedClass::Instance = MuchUsedClass.new
You could store it as an instance variable inside the class object, with a getter method that automatically creates it if it isn't there already:
def MuchUsedClass.instance
#instance ||= MuchUsedClass.new
end

How to load a unknown class in a module?

I have some rb files, all with the same structure:
class RandomName < FooBar
end
The randomname is a random class name which changes in each rb file but all inherits from Foobar.
how i can load all randomclass from there rb files?
I think there are 2 parts to the solution:
How to dynamically instantiate a class
a. Using String#constantize from ActiveSupport
klass = "SomeNamespace::SomeClassName".constantize
klass.new
b. Use Module#const_get (which doesn't handle namespaces)
klass = const_get(:SomeClassName)
klass.new
How to detect a class name
A convention followed widely in ruby is to name the file after the class that it contains, so random_name.rb would contain the RandomName class. If you follow this convention, then you could do something like:
Dir["/path/to/directory/*.rb"].each do |file|
require file
file_name = File.basename(file.path, '.rb')
# using ActiveSupport for camelcase and constantize
file_name.camelcase.constantize.new
end
I think you should explain what you are trying to accomplish. The approach you are taking seems unconventional and there may be a much more effective way of reaching your goal without doing all this loading of files and dynamic instantiation of classes with random names.
Remember, just because ruby lets you do something, it doesn't mean it's a good idea to actually do it!
you can define a method called inherited in the FooBar class. look here
class FooBar
def self.inherited(subclass)
puts "New subclass: #{subclass}"
end
end
Every time a subclass is created, you will get it in the callback. Then you can do whatever you want with all those subclasses.
I have a similar requirement, passing a class name in as a string. One trick with require is that it doesn't have to be at the start, so I prefer to only load the class I need.
I used eval because it doesn't have any Rails dependencies (I'm writing pure Ruby code here).
The following relies on convention (that the Class is in a file of the same name), but if you do know the class and file, this approach has the advantage of not requiring every file in a directory and only dynamically loading the one you need at the time you need it.
klass = "classname"
begin
# Load the file containing the class from same directory I'm executing in
require_relative klass # Or pass a local directory like "lib/#{klass}"
# Use eval to convert that string to a Constant (also capitalize it first)
k = eval(klass.capitalize).new
rescue
# Do something if the convention fails and class cannot be instantiated.
end
k.foo # Go ahead and start doing things with your new class.

Resources