Use attribute of a derived class in parent - ruby-on-rails

I'm trying to use define_method to create additional methods for classes inheriting from a superclass:
class Child < Parent
ADDITIONAL_METHODS += ['xyz', 'qwe']
end
class Parent
ADDITIONAL_METHODS = ['common']
ADDITIONAL_METHODS.each do |key|
define_method key do
...
end
end
end
This doesn't work because ADDITIONAL_METHODS is always taken from the Parent class and the only method created is common. Is there a way to access the attribute from the derived class?

The example code would not work, because you use Parent as ancestor of Child before declaring Parent.
This would produce this error :
uninitialized constant Parent (NameError)
If it actually works for you, it means that Parent has indeed be declared before Child. In that case, the #each loop on ADDITIONAL_METHODS is performed before Child even exists, since instructions you give in a class outside method definition are executed right away :
class Foo
def initialize
puts "second"
end
puts "first"
end
Foo.new
puts "third"
Outputs :
first
second
third
Solution
You may want to implement a class method and call it right away, to perform that.
class Parent
private
def self.add_my_methods( *methods )
( methods.empty? ? [ 'common' ] : methods ).each do |key|
define_method key do
p key
end
end
end
add_my_methods # will implement "common"
end
class Child < Parent
add_my_methods 'xyz', 'qwe'
end
c = Child.new
c.common # outputs "common"
c.xyz # outputs "xyz"
c.qwe # outputs #qwe"
This is an usual pattern for metaprogramming on descendants, like you probably already encountered it with methods like #has_many, #before_filter, etc.

Related

No method error for class variable defined with attr_accessor

I want to define methods dynamically using an array of strings.
Here is a simple piece of code that should achieve that.
class SomeClass
attr_accessor :my_array
def initialize(user, record)
#my_array=[]
end
my_array.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
When I instantiate this class in the console, I get the following error
NoMethodError (undefined method `each' for nil:NilClass)
What is wrong with this code and how to make it work. any help highly appreciated :)
Edit 1:
What I ultimately want to achieve is to inherit from SomeClass and override my_array in the child class to dynamically define methods with its attributes like so
class OtherClass < SomeClass
my_array = %w[method1 method2 method3]
# Some mechanism to over write my_array.
end
And then use self.inherited to dynamically define methods in child class.
Is there a good way to achieve this?
In your code, you use an instance variable (#my_array) and an attr_accessor over it, and then try to access my_array from class level (that is, from the body of the class definition, outside of any methods). But instance variables only exist at instance level, so it is not available in the class scope.
One solution (the natural one, and the one which you would probably use in other languages) is to use a class variable: ##my_array. But class variables in ruby are a little problematic, so the best solution would be to make use of class instance variables, like that:
class SomeClass
class << self
attr_accessor :my_array
end
#my_array=[]
def initialize(user, record)
end
#my_array.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
The syntax is a little tricky, so, if you look that up and it still doesn't makes sense, try just reading about scopes and using a regular class variable with ##.
Edit:
Ok, so, after your edit, it became more clear what you are trying to accomplish. A full working example is like follows:
class SomeClass
class << self
attr_accessor :my_array
end
#my_array=[]
def awesome_method
puts 'awesome'
end
def self.build!
#my_array.each do |element|
self.define_method("#{element}?".to_sym){ awesome_method }
end
end
end
class ChildClass < SomeClass
#my_array = %w[test little_test]
self.build!
end
child_instance = ChildClass.new
child_instance.test?
>> awesome
child_instance.little_test?
>> awesome
So, I've made some tweaks on SomeClass:
It does not need an initialize method
I tried to use the inherited hook for this problem. It won't ever work, because this hook is called as soon as "ChildClass < SomeClass" is written, and this must be before you can define something like #my_array = %w[test little_test]. So, I have added a self.build! method that must be called in the child instances so that they build their methods from my_array. This is inevitable, but I think it is also good, because it makes more explicit in the subclasses that you are doing something interesting there.
I think you want "define_method", not "alias_method".
awesome_method in passed in a block, which is ruby's way of doing functional programming.
With that done, ChildClass inherits from SomeClass, and it's instances have the dynamically created methods 'test?' and 'little_test?'.
You need to change my_array to class level accessible, in my case class constant.
class SomeClass
DYNAMIC_METHOD_NAMES = %w(method_a method_b method_C).freeze
def initialize(user, record)
end
DYNAMIC_METHOD_NAMES.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end

Call derived class instance method from parent class in ruby

I have a parent class Parent and its child class Child. Child class contains a method child_method which I have to call from Parent class. I have tried a couple of approaches, one of them is below:
class Child < Parent
def child_method(params)
# ...
end
def some_other_method(params)
Parent.call_child_method(params, &method(:child_method))
end
end
class Parent
def self.call_child_method(params, &callback)
# Some common code which it's Child classes share
callback.call(params)
end
end
Below is the error that I get:
NameError Exception: undefined local variable or method `params'
for <Child:0x00000000154f53e8>
Did you mean? params
And in case you are wondering why I'm not directly calling child_method from Child class itself. Well, the reason beging that 2 different child classes duplicate that code which then call different methods with different params and the constraints are such that I can't return after calling the call_child_method from Child class and then make a call to child_method. I must call those methods(other child class has another method with different number of params) while I am inside 'call_child_method' only. Moreover, the old code was not written by me and due to time constraints I don't want to refactor the whole Design logic. So, what options do I have here ?
Your code already works, so I don't know what the question is.
However, one thing I will say is that there's a standard way to handle control flow like this, without resorting to method meta-programming: yield. You can do something like this:
class Parent
def common_logic(params)
# Some common code which it's Child classes share
yield
end
end
class Child < Parent
def child_method(params)
# ...
end
def some_other_method(params)
common_logic(params) { child_method(params) }
end
end
Your code almost works, but you forgot the def keyword when defining the call_child_method method.
The following works on my system:
class Parent
def self.call_child_method(params, &callback)
# Some common code which it's Child classes share
callback.call(params)
end
end
class Child < Parent
def child_method(params)
p "The params are", params
end
def some_other_method(params)
Parent.call_child_method(params, &method(:child_method))
end
end
Child.new.some_other_method("hello")
I get output:
"The params are"
"hello"

Rails concern method override another concern method doesn't work like normal modules

Let's say I have the following structure in ruby (no rails)
module Parent
def f
puts "in parent"
end
end
module Child
def f
super
puts "in child"
end
end
class A
include Parent
include Child
end
A.new.f # prints =>
#in parent
#in child
Now when using rails concerns
module Parent
extend ActiveSupport::Concern
included do
def f
puts "In Parent"
end
end
end
module Child
extend ActiveSupport::Concern
included do
def f
super
puts "In Child"
end
end
end
class A < ActiveRecord::Base
include Parent
include Child
end
A.new.f #exception
NoMethodError: super: no superclass method `f' for #<A:0x00000002244490>
So what am I missing here? I need to use super in concerns like in normal modules. I searched but I could not find help on this topic
The reason for this is that included method block is actually evaluated in the context of the class. That mean, that method defined in it is defined on a class when module is included, and as such takes precedence over included modules.
module Child1
extend ActiveSupport::Concern
included do
def foo
end
end
end
module Child2
def bar
end
end
class A
include Child1
include Child2
end
A.new.method(:foo).owner #=> A
A.new.method(:bar).owner #=> Child2
Method lookup
In ruby, every time you want to call a method, ruby has to find it first (not knowing whether it is method or a variable). It is done with so called method lookup. When no receiver is specified (pure call like puts) it firstly searches the current scope for any variables. When not found it searches for that method on current self. When receiver is specified (foo.bar) it naturally search for the method on given receiver.
Now the lookup - in ruby all the methods always belongs to some module/class. The first in the order is receiver's eigenclass, if it exists. If not, regular receiver's class is first.
If the method is not found on the class, it then searches all the included modules in given class in the reversed order. If nothing is found there, superclass of given class is next. The whole process goes recursively until something is found. When lookup reaches BasicObject and fails to find the method it quit and triggers search for method_missing, with default implementation defined on BasicObject.
Important thing to notice is that methods which belongs to the class always take precedence over module methods:
module M
def foo
:m_foo
end
end
class MyClass
def foo
:class_foo
end
include M
end
MyClass.new.foo #=> :class_foo
About super
Search for a super method is very similar - it is simply trying to find a method with the same name which is further in the method lookup:
module M1
def foo
"M1-" + super
end
end
module M2
def foo
'M2-' + super
end
end
module M3
def foo
'M3-' + super
end
end
class Object
def foo
'Object'
end
end
class A
include M2
include M3
end
class B < A
def foo
'B-' + super
end
include M1
end
B.new.foo #=> 'B-M1-M3-M2-Object'
ActiveSupport::Concern#included
included is a very simple method that takes a block and creates a self.included method on the current module. The block is executed using instance_eval, which means that any code in there is actually executed in the context of the class given module is being included in. Hence, when you define a method in it, this method will be owned by the class including the module, not by the module itself.
Every module can hold only one method with given name, once you tries to define second one with the same name, the previous definition is completely erased and there is no way it can be find using ruby method lookup. Since in your example you included two modules with same method definition in included block, the second definition completely overrides the first one and there is no other definition higher in method lookup, so super is bound to fail.

How can I call methods within controller (RoR)?

I am very new to RoR and I have played around the source code. But I have a problem that I already built a 'def A' for creating first CSV file, and 'def B' for creating second CSV file. Each 'def' has its own button, but I have the third button to create all CSVs (to produce output from first and second CSV files.)
What is the possible way to do it?
def first_csv
...
end
def second_csv
..
end
def all_csv
<< how to call get first and second csv >>
end
Thanks in advance,
It should be as simple as you imagine:
def all_csv
first_csv
second_csv
end
Muntasim's answer is correct, but I have to add some additional information.
Ruby provides two types of methods..class methods and instance methods.
class MyClass < AvtiveRecord::Base
# class method
def self.foo
# do something
# within this scope the keyword "self" belongs to the class
end
# another class method which calls internally the first one
def self.bar
something = foo # self.foo could also be written
# do something with something
# within this scope the keyword "self" belongs to the class
end
# instance method
def foo
# do something
# if you use the keyword "self" within an instance method, it belongs to the instance
end
# another instance method which calls class and the first instance method
def bar
mystuff = Myclass.bar # if you want to call the class method you cannot use "self", you must directly call the class
mystuff2 = foo # self.foo is the same, because of the instance scope
return [mystuff, mystuff2]
end
end
You can call the last instance method like following
instance = MyClass.first
instance.bar

Ruby call child method from parent class

I'm writing a program in rails where one class has the same behavior as another class. The only difference is that there is a class variable, #secret_num, that is calculated differently between the two classes. I would like to call a particular super class method, but use the class variable from the child class. What is tricky is that the class variable is not a constant so I am setting it within its own method. Is there any way to do what I'm attempting to do below?
Thanks
Class Foo
def secret
return [1,2,3].sample
end
def b
#secret_num = secret
... # lots of lines of code that use #secret_num
end
end
Class Bar < Foo
def secret
return [4, 5, 6].sample
end
def b
super # use #secret_num from class Bar.
end
end
This doesn't work because the call to super also called the parent class's secret method, i.e. Foo#secret, but I need to use the secret number from the child class, i.e. Bar#secret.
class Foo
def secret
[1,2,3].sample
end
def b(secret_num = secret)
<lots of lines of code that use secret_num>
end
end
class Bar < Foo
def secret
[4, 5, 6].sample
end
end
Note, you don't need to pass secret as an argument to b. As long as you don't redefine b in the subclass, inheritance will take care of calling the correct implementation of secret.
My preference is to have it as an argument so I can pass in various values in testing.

Resources