Define instance method of a class after class already defined in ruby - ruby

I have some problem with extending class with instance method after separate module is included into separate class
module ActsAsCommentable
def self.included(commentable)
Thread.class_eval do
def commentable
p "disqusable is #{commentable}"
p "disqusable class is #{commentable}"
end
end
end
end
class Thread
#some code...
end
class Asset
include ActsAsCommentable
end
And now I want to call this method somelike this:
thread = Thread.new
thread.commentable
The problem is, of course is that there is no binding with include method for class eval, and I could save variables that I want to pass into class eval in ActsAsCommentable module, but I dont want to. Is there a better way?
I tried to do instead
module ActsAsCommentable
def self.included(commentable)
class << Thread
define_method :commentable do
p "disqusable is #{commentable}"
p "disqusable class is #{commentable}"
end
end
end
end
But As I guessed this creates instance method for singletone object of class and therefore I can call it only through
Thread.commentable
And again, no binding...

If I understand you correctly, you need to be able to access the commentable variable inside your Thread extension, right?
If so, just change this:
Thread.class_eval do
To this:
Thread.class_exec(commentable) do |commentable|
And it should work.

Related

Call a method in a class in another class in Ruby

I was wondering how I could call a method in an instance of a class in another class.
This is what I came up with
class ClassA
def method
return "This is a method_from_class_A"
end
end
class ClassB
def initialize
#method_from_class_A=instance.method
end
def method_calls_method
#method_from_class_A
end
end
instance=ClassA.new
instance2=ClassB.new
puts instance2.method_calls_method
But I get this error:
Testing.rb:9:in initialize': undefined local variable or method
instance' for # (NameError) from
Testing.rb:19:in new' from Testing.rb:19:in'
How could I fix it?
Thank you for your response.
From your description this seems to be what you're going for:
class ClassB
def initialize
#instance_of_class_a = ClassA.new
end
def method_calls_method
#instance_of_class_a.method
end
end
Or to pass in the ClassA instance (this is called dependency injection):
class ClassB
def initialize(class_a_instance)
#instance_of_class_a = class_a_instance
end
def method_calls_method
#instance_of_class_a.method
end
end
instance_a = ClassA.new
instance_b = ClassB.new(instance_a)
puts instance_b.method_calls_method
Another Option would be to take a look at class methods: https://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/45-more-classes/lessons/113-class-variables
So in your code it would look similar to this:
class ClassA
def self.method
return "This is a method_from_class_A"
end
end
class ClassB
def method_calls_method
ClassA.method
end
end
instance=ClassB.new
puts instance.method_calls_method
*Notice the self. in ClassA to signify a class method. This is similar to a static method in other languages.
According to wikipedia: https://en.wikipedia.org/wiki/Method_(computer_programming)#Static_methods
Class(static) methods are meant to be relevant to all the instances of a class rather than to any specific instance.
You see class methods used a lot in the ruby Math class:
http://ruby-doc.org/core-2.2.2/Math.html
For example taking a square root of a number in is done by using the class method Math.sqrt. This is different from an instance method which would look like object.method instead Class.method. There are a lot of resources and tutorials out that explains this concept in more detail and probably clearer.

Have superclass methods call class in correct namespace

I have some namespaces that contain duck-type classes, and they all inherit from Base namespace, like below:
module Base
class Client
def self.greet
puts Wrapper
end
def do_stuff
puts Wrapper
end
end
class Wrapper
end
end
module Website
class Client < Base::Client
end
class Wrapper < Base::Wrapper
end
end
Website::Client.greet
Website::Client.new.do_stuff
---output---
Base::Wrapper
Base::Wrapper
I would like the above code to print (and reference) Website::Wrapper instead, is there a way to accomplish this by changing my inheritance structure?
If a constant named Wrapper is in the top-level namespace you can write ::Wrapper to refer to it, but usually just writing Wrapper is sufficient.
In Website::Client, if I attempt to call another class such as
Wrapper, it will call Base::Wrapper...
I'm not seeing that:
module Base
class Client
end
class Wrapper
end
end
module Website
class Wrapper < Base::Wrapper
end
class Client < Base::Client
p Wrapper #=>Website::Wrapper
def self.greet #Create class method
p Wrapper #=>Website::Wrapper
end
def do_stuff
p Wrapper #=>Website::Wrapper
end
end
end
Website::Client.greet
Website::Client.new.do_stuff
--output:--
Website::Wrapper
Website::Wrapper
Website::Wrapper
Can you modify that example to show the problem you are having?
Response to modified question:
Is this just bad practice?
Wouldn't it be surprising to call Base::Client.greet and get Website::Wrapper?
Is there an easy way to have methods called in the subclass default to
that class' namespace?
What do you mean by that? There is no class method named greet defined in Website::Client's singleton class. If you want to override Base::Client.greet you can do that.
Also, you are writing Website::Client when you call Website::Client.greet, so you already know from which class the method call originates...but inside Base::Client.greet, you can add the line puts self, and that will identify the object that called the method, which is the class Website::Client.

Create property as method on class in Ruby

Rails has these cool properties that seem to be actually methods. For example:
class SomeController < ApplicationController
before_filter :authenticate!
end
What are these actually called and how would you create your own? For example, in one of my models I want to be able to have a dynamic property that selects an internal method for processing some results:
class MyModel < ActiveRecord::Base
active_method :some_class_method
end
How would I set this up so I can set active_method like that and be able to access the active_method symbol as an instance var?
Edit for elaboration:
So give this starter below, I need to figure out how to define "selected_method" so that it defines a accessor or instance variable so "called_selected_method" calls "method_b".
class MyClass
selected_method :method_b
def call_selected_method
end
private
def method_a
puts 'method_a'
end
def method_b
puts 'method_b'
end
end
c = MyClass.new
c.call_selected_method # should put 'method_b'
It's actually just a method call to a method defined on the class. before_filter is provided by a ruby Module, which is mixed in to ActionController.
Creating your own methods similar to before_filter is as easy as:
Define a class method on your Class
Call that method in any concrete implementations of your class.
Some example code:
class MyClass
class << self
def some_function(*args)
# your code here
end
end
some_function "foo"
end
If you wanted to abstract it further, you can put the class method in to a Module, and then include that module in to your class(es).
UPDATE:
In relation to your asking of how to get a call of some_function to set an instance variable on your class, you can't, as class methods cannot affect specific instances of that class.
I have to wonder, though... you're writing a method that will just act as a proxy to your other method, and would be hard-coded in to the class definition. That offers no benefit to you, and would just make your code redundantly complicated.

Why can't I call include from a class method in ruby?

You can call include to mixin a module with a class in ruby, but it must be done at the beginning of the class definition. Why can't it be done inside a class function? Is there an alternate syntax?
EX:
module UsefulThings
def a() puts "a" end
end
class IncludeTester
include UsefulThings
def initialize
end
end
n = IncludeTester.new
n.a()
^^ This works, but if I change IncludeTester to the following, I get the error "undefined method `include'"
class IncludeTester
def initialize
include UsefulThings
end
end
It can be done in a class method.
This works:
module UsefulThings
def a
puts "a"
end
end
class IncludeTester
def self.mix_in_useful_things
include UsefulThings
end
end
x = IncludeTester.new
IncludeTester.mix_in_useful_things
x.a # => a
But "initialize" is not a class method, it's an instance method.
"new" is a class method. You can think of new as allocating a new object and then calling initialize on it, passing initialize whatever arguments were passed to new.
You can't call include directly in initialize because include is a private method of Class (inherited from Module), not of the newly created IncludeTester instance.
If you want to include a module into a class from an instance method, you have to do something like this:
class IncludeTester
def initialize
self.class.send(:include, UsefulThings)
end
end
It's necessary to use "send" here because include is private method, which means it can only be directly invoked with an implicit receiver (of self).
When you call initialize normally in a class definition, you're actually calling it with an implicit receiver of "self", referring to the class being defined.
This is what is actually happening when you do this:
class IncludeTester
include UsefulThings
end
include is a method from Module, Module is the superclass of Class and so include is a method on Class and that makes it a class method in your IncludeTester. When you do this:
class IncludeTester
def initialize
include UsefulThings
end
end
you're trying to call a class method inside an instance method and Ruby says
`initialize': undefined method `include'
because there is no instance method called include. If you want to call a class method inside an instance method (such as initialize), you'd do this:
def initialize
self.class.include UsefulThings
end
But that won't work because include is a private method; you can get around that with class_eval though:
def initialize
self.class.class_eval {
include UsefulThings
}
end
You would be doing include UsefulThings every single time you instantiated an IncludeTester, aside from not making much sense, it could cause problems if UsefulThings had an included method.
It's actually fully possible to include a module from a class method, like so:
module Stuff
def say_hello
puts "hello"
end
end
class Foo
def self.i_am_a_class_method
include Stuff
end
def i_am_an_instance_method
end
end
You cannot however do that from an instance method, because the include method is only available as a private class method, and therefore not accessible from a Foo.new instance.
You want the extend method:
class IncludeTester
def initialize
extend UsefulThings
end
end
This need not be done within the a method either:
IncludeTester.new.tap { |newTester| newTester.extend(UsefulThings) }

Ruby metaprogramming question

When I call self.class.instance_variable_set("#var", ...) from inside a class method, where is that variable actually stored? Is it on the class itself? On the instance of that class? I can't seem to find it with any of the following:
e = Example.new
e.instance_variables
e.class.class_variables
I even tried using the (class << self; self; end) trick but I can't find anything (http://ruby-metaprogramming.rubylearning.com/html/seeingMetaclassesClearly.html).
Here is the code snippet (which works as I need) but I'm not sure why it works :)
module HandyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def use_template(template_name)
self.class.instance_variable_set("#_template", template_name)
end
def build(attributes = {})
template_name = self.class.instance_variable_get("#_template")
# do stuff with the template
end
end
end
class Example
include HandyModule
use_template :contact_form
end
Essentially I can include this handy module, and then I have a class method called use_template with which I can specify which template to be used by my build method.
When you call use_template inside the class definition, the self is the class Example. When you call self.class, it is Example.class, or Class. You define the instance variable to the class of the classes.
class Class
p #_template
end
# prints :contact_form
You probably should use just self instead of self.class.

Resources