What is the best way to achieve the following in Rails 4? Parent and child model have the same attribute. If child model hasn't set said attribute then it inherits from the parent, otherwise, it renders its own value.
I tried to create a method in the child method with the same name as the model attribute to do the logic, but that causes a stack level too deep error.
def age_of_seniority
age_of_seniority.present? ? age_of_seniority : borough.age_of_seniority
end
Update
I don't want to change the method name, I would like to be able to access it as a normal attribute
You can do this using read_attribute
def age_of_seniority
read_attribute(:age_of_seniority) || borough.age_of_seniority
end
Call super:
def seniority_age
super || borough.age_of_seniority
end
A simple example:
class Parent
attr_accessor :seniority_age
end
class Child < Parent
def seniority_age
super||'foo'
end
end
c = Child.new
puts c.seniority_age
c.seniority_age = "bar"
puts c.seniority_age
returns:
foo
bar
You are recursively calling the method from within the method, this of course will cause a stack overflow.
def age_of_seniority
age_of_seniority.present? ? age_of_seniority : borough.age_of_seniority
end
If the age_of_seniority is stored in the instance variable, you access it with:
#age_of_seniority
So in your code:
def age_of_seniority
#age_of_seniority.present? ? #age_of_seniority : borough.age_of_seniority
end
And to call the overrided method from parent, you can just use super.
not quite sure what's borough.age_of_seniority doing.
Related
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"
I am trying to trigger a method from inside the model where it is defined. But I am getting an "undefined method `completed_mission_names'" when I try to start my server. Can anybody help me find what I'm doing wrong ?
class MenteeProfile < ActiveRecord::Base
# Update trackable attributes with succeeded missions
MenteeProfile.completed_mission_names
protected
def last_completed_mission_action
end
def self.completed_mission_names
end
end
Simplified to the max, you are trying to do this:
class A
A.foo
def self.foo
puts 'Calling foo!'
end
end
This does not work because the method foo is not defined when you try to invoke it. You must define it first, then you can call it. Like so:
class B
def self.foo
puts 'Calling foo!'
end
B.foo
end
You could also call just foo instead of B.foo from within the class definition. You can add the protected keyword anywhere you like, it will not have any impact on class methods whatsoever.
I have a controller which calls out to another class.
class BlahController < ActionController
def index
OtherClass.get_stuff
end
end
In this class I want to be able to write controller style code.
for instance:
class OtherClass
def self.get_stuff
#foo = bar
end
end
However, I would also like #foo to exist when inside my view, but as it's a separate class those variables aren't making it back through into the controller assigns - so question is, how I can make this so?
(Ignore why I'm having to call out to a separate class, I'm trying to get this code fitting in with a legacy codebase without too much butchery)
class BlahController < ActionController
def index
OtherClass.get_stuff(self)
end
end
class OtherClass
def self.get_stuff(that)
that.instance_variable_set(:#foo, bar)
end
end
Please note that I don't agree with this method. I am just answering the question as you stated it.
I would prefer to accomplish this functionality through mixins and thereby decrease parameter coupling that is present within the code above.
Code structured like this will be difficult to read and maintain. Whenever you can, let the controller directly set all of the variables that the view needs:
class BlahController < ActionController
def index
#foo = OtherClass.get_stuff
end
end
class OtherClass
def self.get_stuff
# return the value that should be assigned to #foo
end
end
I'm have some difficulties here, I am unable to successfully call a method which belongs to a ProjectPage model in the ProjectPage controller.
I have in my ProjectPage controller:
def index
#searches = Project.published.financed
#project_pages = form_search(params)
end
And in my ProjectPage model:
def form_search(searches)
searches = searches.where('amount > ?', params[:price_min]) if check_params(params[:price_min])
#project_pages = ProjectPage.where(:project_id => searches.pluck(:'projects.id'))
end
However, I am unable to successfully call the form_search method.
To complete davidb's answer, two things you're doing wrong are:
1) you're calling a model's function from a controller, when the model function is only defined in the model itself. So you do need to call
Project.form_search
and define the function with
def self.form_search
2) you're calling params from the model. In the MVC architecture, the model doesn't know anything about the request, so params is not defined there. Instead, you'll need to pass the variable to your function like you're already doing...
Three thing:
1.) When you want to create a class wide method thats not limited to an object of the class you need to define it like
def self.method_name
..
end
and not
def method_name
...
end
2.) This can be done using a scope with lambda these are really nice features. Like This in the model add:
scope :form_search, lambda{|q| where("amount > ?", q) }
Will enable you to call
Project.form_search(params[:price_min])
The secound step would be to add a scope to the ProjectPage model so everything is at the place it belongs to!
3.) When you call a Class method in the Controller you need to specifiy the Model like this:
Class.class_method
Declare like this in model
def self.form_search(searches)
searches = searches.where('amount > ?', params[:price_min]) if check_params(params[:price_min])
#project_pages = ProjectPage.where(:project_id => searches.pluck(:'projects.id'))
end
and call from controller
#project_pages = ProjectPage.form_search(params)
Hey guys.
How do I know the methods that a child class overrided in my super class?
I have this:
class Test
def self.inherited(child)
# child.overrided_methods???
end
def self.foo
end
def self.bar
end
end
def Child < Test
def self.bar
puts "bar"
end
end
The method self.inherited is called when a subclass of Test is loaded. So I get the reference to this subclass in child, but I don't know how to get the methods that were overrided by this subclass.
Any ideas?
--
Arsen suggested the use of self.method_added(name) instead of self.inherited(child), but this method catches only instance methods and I want to catch class methods. Does anyone know another methods that does the same thing but with class methods?
In the last case I'll consider using a singleton and convert all this class methods to instance methods then the problem is solved.
For instance methods there is an Object::method_added(name) method you can override, similar to 'inherited' you have used:
class test
def self.method_added(name)
puts "method_added(#{name.inspect})"
super
end
end
irb(main):002:0> class Child < Test; def foo; end; end
method_added(:foo)
=> nil
You can then compare a received name to a list of your methods:
Test.instance_methods.include?(name.to_s)
With class methods this approach does not work (even if you do things like class << self magic), but a helpful fellow knew the answer: http://www.ruby-forum.com/topic/120416 :
class Test
def self.singleton_method_added(name)
puts "Class method added #{name.inspect}"
end
end
This is only the first part of the problem, because you need to know which class defined the method (it will be self) and whether the method is a new one, or overridden one. Experiment with this code:
class Test
def self.singleton_method_added(name)
if self == Test
puts "My own class method added: #{self.name}.#{name.inspect}"
elsif Test.methods(false).include?(name.to_s)
puts "Class method overriden: #{self.name}.#{name.inspect}"
elsif Test.methods(true).include?(name.to_s)
puts "My parent's class method overriden: #{self.name}.#{name.inspect}"
else
puts "New class method added: #{self.name}.#{name.inspect}"
end
end
end
Maybe a first step to the solution:
By calling child.instance_method(:bar) (if child refers to the class) or child.method(:bar) (if it refers to an instance of Child) you can get an UnboundMethod or Method object representing your method:
a = Test.instance_method(:foo)
b = Child.instance_method(:foo)
Unfortunately, a == b evaluates to false, although both refer to the same method.
def overridden_methods
klass = self.class
klass.instance_methods.select {|m| klass.instance_method(m).owner == klass}
end
Change according to your needs.