Specify class with forwardable - ruby

I'm trying to use ruby's forwardable module to make some variables in one class accessible to another class. However I am having some trouble doing this.
It seems that I'm able to 'forward' some variables within the self (first bit of code) but I'm unable to forward some variable within a class (second bit of code)
The following works:
require 'forwardable'
module ModuleName
#
class << self
attr_accessor :config
def run
#config = {hey: 'hi', jay: 'ji'}
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def_delegators ModuleName, :config
def run
puts "2) Config = #{config}"
end
end
end
ModuleName.run
(ModuleName::Start.new).run
#=> 1) Config = {:hey=>"hi", :jay=>"ji"}
#=> 2) Config = {:hey=>"hi", :jay=>"ji"}
BUT this doesn't
require 'forwardable'
module ModuleName
#
class Data
attr_accessor :config
def run
#config = {hey: 'hi', jay: 'ji'}
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def_delegators ModuleName::Data, :config
def run
puts "2) Config = #{config}"
end
end
end
(ModuleName::Data.new).run
(ModuleName::Start.new).run
#=> 1) Config = {:hey=>"hi", :jay=>"ji"}
#=> /Users/ismailm/Desktop/ex.rb:17:in `run': undefined method `config' for ModuleName::Data:Class (NoMethodError)
Can you help in fixing this part of the code...

Typically when you delegate between two classes, one object contains an instance of the other. The fact that you call (ModuleName::Data.new).run implies to me that is what you are trying to do, but are somehow missing the fact that you need an instance of the contained class to be stored somewhere in order to receive the call to :config
This variation of your second piece of code is closer to what I would expect to see in a delegation scenario:
require 'forwardable'
module ModuleName
#
class Data
attr_accessor :config
def initialize
#config = {hey: 'hi', jay: 'ji'}
end
def run
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def initialize data_obj = Data.new()
#data = data_obj
end
def_delegators :#data, :config
def run
puts "2) Config = #{config}"
end
end
end
(ModuleName::Data.new).run
(ModuleName::Start.new).run
I changed the constructor to ModuleName::Start in order to show a common pattern, not used here. Namely, you often pass in the wrapped object, or even more commonly the params that allow you to construct a new one and assign it to the instance variable that you wish to delegate to.
A minor related change: In your original code, the value of #config was only set via calling :run, so delegating direct to :config won't read the value you expect in the test. It worked in the first version because #config was set in the first call to run on the module, and then read globally as a singleton method on the second delegated call. I have worked around that here by setting it in Datas constructor, but of course anything that sets the value of #config on the instance you are delegating to would work.

Related

Is there to shorten `def self.method_name` and `def method_name` into one method?

So say I have this class:
class This
def a(that)
puts that
end
def self.b(that)
puts that
end
end
This.b("Hello!") # => passes
This.a("Hello!") # => fails
the_class = This.new()
the_class.b("Hello!") # => fails
the_class.a("Hello!") # => passes
Is there a way to shorten both of those methods into one method that is able to be called on a uninitialized object, AND is able to be called on an already initialized one, or will I always have to write a method like that twice?
You can extract the functionality into a module and both extend and include it.
module A
def a(that)
puts that
end
end
class This
include A # defines instance methods
extend A # defines class methods
end
This.a("foo") # => "foo"
This.new.a("foo") # => "foo"
Although I think it's more common to either include or extend and not both. The reason being that instance methods often depend on instance state, whereas class methods don't. If you have an instance This.new and want to call a class method, you can use .class i.e. This.new.class.a
The following bit of code uses some metaprogramming tricks to auto copy over any class methods to instances of that class.
module AutoAddMethods
def singleton_method_added(symbol)
define_method(symbol, method(symbol).to_proc)
end
end
class Foo
extend AutoAddMethods
#bar = 39
def initialize
#bar = 42
end
def test_one # Only added as an instance method.
puts "One #{#bar.inspect}"
end
def self.test_two # Added as both an instance and class method.
puts "Two #{#bar.inspect}"
end
end
i = Foo.new
i.test_one
i.test_two
Foo.test_two
And here is my test run output:
One 42
Two 39
Two 39
The test_two method is callable from both the class and its instances. It runs as if in the class.

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>'

Test module method with rspec-mocks

How to test config method in this module with Rspec 3 mocks?
module TestModule
class << self
attr_accessor :config
end
def self.config
#config ||= Config.new
end
class Config
attr_accessor :money_url
def initialize
#money_url = "https://example1.come"
end
end
end
I tried something like this:
describe "TestModule config" do
it "should have config instance" do
config = class_double("TestModule::Config")
obj = instance_double("TestModule")
allow(obj).to receive(:config).and_return(config)
obj.config
expect(obj.config).to eq(config)
end
end
It looks, that it doesn't works, why ?
Failures:
1) TestModule config should have config instance
Failure/Error: allow(obj).to receive(:config).and_return(config)
TestModule does not implement: config
# ./spec/config_spec.rb:41:in `block (2 levels) in '
I'd recommend testing the class directly with
describe "module config" do
it "has config" do
expect(TestModule.config).to be kind_of(TestModule::Config)
end
end
If you don't need the .config to be changed by an outside object, then there's no need to have the attr_accessor :config as the def self.config already defines a accessible .config method to TestModule. If you want to allow .config to be changed from the outside, then just having a attr_writer :config should suffice as the reader/getter is already defined as that method.
Moreover, if you already have your class open with class << self, then declaring the .config method inside that would make more sense as it would contain all class level definitions. Just drop the self. from the beginning of the declaration so it reads def config as you are already "inside" the class.
I believe you mixed up class_double and instance_double. Try switching them and see how it goes. (sent from mobile phone so forgive my brevity)
UPDATE: Now that I'm at a computer, I can dig into this a bit more. First, why are you stubbing the method you're testing? Aren't you trying to test that it returns an instance of the Config class? By stubbing the .config method, you're not really testing your method to prove that it does what you want it to do. I think you could really simplify this to something like:
RSpec.describe TestModule do
describe ".config" do
it "returns an Config instance" do
expect(TestModule.config).to be_a TestModule::Config
end
end
end

Dynamically extend existing method or override send method in ruby

Let say we have classes A,B,C.
A
def self.inherited(sub)
# meta programming goes here
# take class that has just inherited class A
# and for foo classes inject prepare_foo() as
# first line of method then run rest of the code
end
def prepare_foo
# => prepare_foo() needed here
# some code
end
end
B < A
def foo
# some code
end
end
C < A
def foo
# => prepare_foo() needed here
# some code
end
end
As you can see I am trying to inject foo_prepare() call to each one of foo() methods.
How can that be done?
Also I have been thinking about overriding send class in class A that way I would run foo_prepare and than just let send (super) to do rest of the method.
What do you guys think, what is the best way to approach this problem?
Here's a solution for you. Although it's based on module inclusion and not inheriting from a class, I hope you will still find it useful.
module Parent
def self.included(child)
child.class_eval do
def prepare_for_work
puts "preparing to do some work"
end
# back up method's name
alias_method :old_work, :work
# replace the old method with a new version, which has 'prepare' injected
def work
prepare_for_work
old_work
end
end
end
end
class FirstChild
def work
puts "doing some work"
end
include Parent # include in the end of class, so that work method is already defined.
end
fc = FirstChild.new
fc.work
# >> preparing to do some work
# >> doing some work
I recommend Sergio's solution (as accepted). Here is what I did which fit my needs.
class A
def send(symbol,*args)
# use array in case you want to extend method covrage
prepare_foo() if [:foo].include? symbol
__send__(symbol,*args)
end
end
or
class A
alias_method :super_send, :send
def send(symbol,*args)
prepare_foo() if [:foo].include? symbol
super_send(symbol,*args)
end
end
As of Ruby 2.0 you can use 'prepend' to simplify Sergio's solution:
module Parent
def work
puts "preparing to do some work"
super
end
end
class FirstChild
prepend Parent
def work
puts "doing some work"
end
end
fc = FirstChild.new
fc.work
This allows a module to override a class's method without the need for alias_method.

Sharing variables across submodules and classes

I am trying to build a simple little template parser for self-learning purposes.
How do I build something "modular" and share data across it? The data doesn't need to be accessible from outside, it's just internal data. Here's what I have:
# template_parser.rb
module TemplateParser
attr_accessor :html
attr_accessor :test_value
class Base
def initialize(html)
#html = html
#test_value = "foo"
end
def parse!
#html.css('a').each do |node|
::TemplateParser::Tag:ATag.substitute! node
end
end
end
end
# template_parser/tag/a_tag.rb
module TemplateParser
module Tag
class ATag
def self.substitute!(node)
# I want to access +test_value+ from +TemplateParser+
node = #test_value # => nil
end
end
end
end
Edit based on Phrogz' comment
I am currently thinking about something like:
p = TemplateParser.new(html, *args) # or TemplateParser::Base.new(html, *args)
p.append_css(file_or_string)
parsed_html = p.parse!
There shouldn't be much exposed methods because the parser should solve a non-general problem and is not portable. At least not at this early stage. What I've tried is to peek a bit from Nokogiri about the structure.
With the example code you've given, I'd recommend using composition to pass in an instance of TemplateParser::Base to the parse! method like so:
# in TemplateParser::Base#parse!
::TemplateParser::Tag::ATag.substitute! node, self
# TemplateParser::Tag::ATag
def self.substitute!(node, obj)
node = obj.test_value
end
You will also need to move the attr_accessor calls into the Base class for this to work.
module TemplateParser
class Base
attr_accessor :html
attr_accessor :test_value
# ...
end
end
Any other way I can think of right now of accessing test_value will be fairly convoluted considering the fact that parse! is a class method trying to access a different class instance's attribute.
The above assumes #test_value needs to be unique per TemplateParser::Base instance. If that's not the case, you could simplify the process by using a class or module instance variable.
module TemplateParser
class Base
#test_value = "foo"
class << self
attr_accessor :test_value
end
# ...
end
end
# OR
module TemplateParser
#test_value = "foo"
class << self
attr_accessor :test_value
end
class Base
# ...
end
end
Then set or retrieve the value with TemplateParser::Base.test_value OR TemplateParser.test_value depending on implementation.
Also, to perhaps state the obvious, I'm assuming your pseudo-code you've included here doesn't accurately reflect your real application code. If it does, then the substitute! method is a very round about way to achieve simple assignment. Just use node = test_value inside TemplateParser::Base#parse! and skip the round trip. I'm sure you know this, but it seemed worth mentioning at least...

Resources