Execute method in other context - ruby

How can I call Test#test method in DSL instance?
class DSL
def initialize(c)
x = # call c.test in self context
x == 'dsl_method_content_somethig' # => true
end
def dsl_method
'dsl_method_content'
end
end
class Test
def test
dsl_method + '_something'
end
end
DSL.new(Test.new)
Everything I tried gives me:
undefined local variable or method `dsl_method' for #<Test:0x007f8c0a934250> (NameError)

I think you might be trying to to include a module? If you just want dsl methods in one place, you can do this:
module DSL
def initialize
x = test
x == 'dsl_method_content_somethig' # => true
end
def dsl_method
'dsl_method_content'
end
end
Then in your class you can do:
class Test
include DSL
def test
dsl_method + '_something'
end
end
Although calling a method, that is defined in Test, from the DSL module is a little bit odd. As the module then needs to know implementation details, of every class it is included in.

Related

Check if a class method was called

I have the following module and classes:
module MyModule
def self.included base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
# this method MUST be called by every class which includes MyModule
def configure &block
#config = {}
block.call(#config) if block
end
end
end
class A
include MyModule
configure do |config|
# do sth with the config
end
end
class B
include MyModule
end
Is it possible to check, if the configure method from the module was called? This means A should be fine, but B should throw an error, because it never called configure.
I tried it within the self.included callback, but the configure method gets called afterwards.
Technically, #ndn is right, it could be called after the class has been evaluated. However, it sounds like what you want is to validate that the configure method has been called at some point within the class body definition (this will also allow any modules that have been included, to finish evaluating, so if a module include calls the configure method, it's all good as well).
The closest solution I've come up to address this situation can be found here:
https://github.com/jasonayre/trax_core/blob/master/lib/trax/core/abstract_methods.rb
The above code is an abstract methods implementation for ruby, which technically isn't what you're asking (you are talking about calling the method, abstract methods are about checking that a subclass defined it), but the same trick I used there could be applied.
Basically, I'm using ruby's trace point library to watch for the end of the class definition to hit, at which point it fires an event, I check whether the method was defined, and throw an error if not. So as long as you're calling configure from WITHIN your classes, a similar solution could work for you. Something like (not tested):
module MustConfigure
extend ::ActiveSupport::Concern
module ClassMethods
def inherited(subklass)
super(subklass)
subklass.class_attribute :_configured_was_called
subklass._configured_was_called = false
trace = ::TracePoint.new(:end) do |tracepoint|
if tracepoint.self == subklass #modules also trace end we only care about the class end
trace.disable
raise NotImplementedError.new("Must call configure") unless subklass._configured_was_called
end
end
trace.enable
subklass
end
def configure(&block)
self._configured_was_called = true
#do your thing
end
end
end
class A
include MustConfigure
end
class B < A
configure do
#dowhatever
end
end
class C < B
#will blow up here
end
Or, you could try using the InheritanceHooks module from my library and skip the manual tracepoint handling:
class BaseClass
include::Trax::Core::InheritanceHooks
after_inherited do
raise NotImplementedError unless self._configure_was_called
end
end
Note, although I am using this pattern in production at the moment, and everything works great on MRI, because tracepoint is a library built for debugging, there are some limitations when using jruby. (right now it breaks unless you pass the jruby debug flag) -- I opened an issue awhile back trying to get tracepoint added in without having to enable debug explicitly.
https://github.com/jruby/jruby/issues/3096
Here's an example based on your structure.
It checks at instantiation if configure has been called, and will work automatically with any class on which you prepended MyModule.
It checks at every instantiation if configure has been called, but it is just checking a boolean so it shouldn't have any performance impact.
I looked for a way to undefine a prepended method for a specific class but didn't find anything.
module MyModule
def self.prepended base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
def configured?
#configured
end
def configure &block
#configured = true
#config = {}
block.call(#config) if block
end
end
def initialize(*p)
klass = self.class
if klass.configured? then
super
else
raise "Please run #{klass}.configure before calling #{klass}.new"
end
end
end
class A
prepend MyModule
configure do |config|
config[:a] = true
puts "A has been configured with #{config}"
end
end
class B
prepend MyModule
end
A.new
puts "A has been instantiated"
puts
B.new
puts "B has been instantiated"
# =>
# A has been configured with {:a=>true}
# A has been instantiated
# check_module_class.rb:27:in `initialize': Please run B.configure before calling B.new (RuntimeError)
# from check_module_class.rb:50:in `new'
# from check_module_class.rb:50:in `<main>'

Getting the name of the calling class in Ruby

I'm trying to figure out how to get the name of the class that called a module function in a plugin-based application of mine.
caller seems to give me a file/line number, which is workable, but seems a bit hacky and not idiomatic.
Example code:
module AwesomeModule
def self.get_caller
puts #some unknown code here
end
end
class AwesomeClass
def initialize
AwesomeModule::get_caller
end
end
a = AwesomeClass.new # ideal return => "AwesomeClass"
You typically use ruby modules by including them. Try this:
module AwesomeModule
def get_caller
self.class
end
end
class AwesomeClass
include AwesomeModule
def initialize
get_caller
end
end
a = AwesomeClass.new # "AwesomeClass"
Also, note that in your question get_caller is being called on the AwesomeModule module itself, further complicating the issue.

What is the issue with the method acces in ancestors chain in Ruby

I have define a method that has few class inside of it and few modules. From one of the classes I am trying to call a method that is defined in a module(inside the common one) and I get an access error. Here is the full hierachy:
module Top
class NestedClass
#some code
NestedModule::method_name
end
module NestedModule
def method_name
#some code
end
end
end
And the error that I get: undefined method 'method_name' for Top::NestedModule:Module
Write it as :
module Top
module NestedModule
def self.method_name
#some code
end
end
class NestedClass
#some code
NestedModule::method_name
end
end
In your case you did NestedModule::method_name before defining the module NestedModule.
You cannot call undeclared methods as well as instance module methods directly.
Maybe this will clear things out for you:
module Top
module NestedModule
def self.module_method
1
end
def instance_method
2
end
end
class NestedClass
NestedModule.module_method # => 1
NestedModule.instance_method(:instance_method) # => #<UnboundMethod: Top::NestedModule#instance_method>
extend NestedModule
instance_method # => 2
include NestedModule
new.instance_method # => 2
end
end
And although "NestedModule::module_method" would also work here, the convention is to use dots when calling class/module methods, and double colons when accessing nested modules/classes.

Initializing instance variables in Mixins

Is there any clean way to initialize instance variables in a Module intended to be used as Mixin? For example, I have the following:
module Example
def on(...)
#handlers ||= {}
# do something with #handlers
end
def all(...)
#all_handlers ||= []
# do something with #all_handlers
end
def unhandled(...)
#unhandled ||= []
# do something with unhandled
end
def do_something(..)
#handlers ||= {}
#unhandled ||= []
#all_handlers ||= []
# potentially do something with any of the 3 above
end
end
Notice that I have to check again and again if each #member has been properly initialized in each function -- this is mildly irritating. I would much rather write:
module Example
def initialize
#handlers = {}
#unhandled = []
#all_handlers = []
end
# or
#handlers = {}
#unhandled = []
# ...
end
And not have to repeatedly make sure things are initialized correctly. However, from what I can tell this is not possible. Is there any way around this, besides adding a initialize_me method to Example and calling initialize_me from the extended Class? I did see this example, but there's no way I'm monkey-patching things into Class just to accomplish this.
module Example
def self.included(base)
base.instance_variable_set :#example_ivar, :foo
end
end
Edit: Note that this is setting a class instance variable. Instance variables on the instance can't be created when the module is mixed into the class, since those instances haven't been created yet. You can, though, create an initialize method in the mixin, e.g.:
module Example
def self.included(base)
base.class_exec do
def initialize
#example_ivar = :foo
end
end
end
end
There may be a way to do this while calling the including class's initialize method (anybody?). Not sure. But here's an alternative:
class Foo
include Example
def initialize
#foo = :bar
after_initialize
end
end
module Example
def after_initialize
#example_ivar = :foo
end
end
Perhaps this is a little hacky, but you can use prepend to get the desired behavior:
module Foo
def initialize(*args)
#instance_var = []
super
end
end
class A
prepend Foo
end
Here is the output from the console:
2.1.1 :011 > A.new
=> #<A:0x00000101131788 #instance_var=[]>
modules provides hooks, as Module#included. I suggest you check out ruby doc on the topic, or use ActiveSupport::Concern, which provides some helpers on modules.
I think there may be a simpler answer to this. The module should have an initializer that initialises the variables as you normally would do. In the initializer for the class that includes the module, invoke super() to invoke the initializer in the included module. This is simply following the method dispatch rules in Ruby.
On reflection, this will not work so well if the class including the module also has a superclass that needs to be initialised. The initializer in the module would need to accept a variable parameter list and pass this up to the superclass. It looks like a good avenue to explore though.

Extending ruby object; extend_object callback prevents instance methods from being extended

Having some trouble extending an object instance with a module, specifically when I define an extend_object callback in the Module class. My understanding is that when you do something like:
(s = String.new).extend SomeModule
The SomeModule extend_object callback is called. This seems to be the case, but when I include a callback, none of the instance methods defined in SomeModule are visible in the object. Some code should better explain this:
module M1
def self.extend_object(o)
end
def test_method
true
end
end
module M2
def test_method
true
end
end
(x = String.new).extend(M1)
(y = String.new).extend(M2)
Then,
x.methods.include?("test_method")
=> false
y.methods.include?("test_method")
=> true
More specifically,
x.singleton_methods
=> []
y.singleton_methods
=> ["test_method"]
Any ideas?
Reference:
http://www.ruby-doc.org/core/classes/Module.html#M001660
http://www.ruby-doc.org/core/classes/Object.html#M000337
You should use the extended callback rather than overriding extend_object. The first is called when an object is extended by your module. The latter is called to actually extend the object. It's like the difference between included and append_features.
Here's an example:
module M1
def self.extended(base)
puts "extended object #{base.inspect}"
end
def test_method
true
end
end
Then:
>> (x = String.new).extend(M1)
extended object ""
=> ""
>> x.methods.include?("test_method")
=> true
Got some help from a colleague, and realized I need to call super else it's a noop. Thanks.
Also you need to use the symbol, because method name is symbol
module M1
def self.extend_object(o)
super
end
def test_method
true
end
end
(x = String.new).extend(M1)
x.methods.include?(:test_method) #=> true

Resources