How rails is handling the inheritance concept, parent accessing child - ruby-on-rails

This raises an error "undefined method hi":
class A
def bla
hi
end
end
class B < A
def hi
puts "Hii"
end
end
a = A.new
a.bla
I have a Rails application that does this:
class BlaParentController < ApplicationController
before_filter :setting
def create
#random_obj = ParentRandom.create(permitted_params)
end
end
class BlaController < BlaParentController
private
def setting
end
def permitted_params
end
end
A method call BlaController#create goes to BlaParentController#create. But BlaParentController is accessing methods permitted_params and setting from its child BlaController. How this is possible?

For ordinary methods, what matters is whether it is defined for the receiver. Other kinds of contexts (such as, from which method it is called) does not matter. (The few exception to this are methods like Kernel#__dir__, Module#nesting, Exception#backtrace.)
In the first example, the receiver is an instance of A, but not B. Hence it can access methods in A, but not those in B.
In the second example, the receiver is an instance of BlaController, which is a subclass of BlaParentController. Hence it can access methods in BlaController as well as those in BlaParentController.

Related

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"

Calling a method inside model

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.

Why is the strong_parameters method private?

The rails scaffolds give you the resource_params method as private by default:
private
def person_params
params.require(:person).permit(:name, :age)
end
I understand why strong_parameters is a good thing. I also understand that this prevents the method from being accessed outside the controller, but are there any real dangers to making this method public, or what is the reasoning behind having this as a private method? It would be nice to be able to send that method to the controller from a gem that extends ActionController.
In other words, why not access the method outside of the controller? For example, if I have a separate controller that handles authorization and I want to pass an instance variable back to the original controller that contains the initialized object.
Because this method is not called from any external objects.
Mass assignment protection is not connected with 'person params' method visibility, it is just best practice for application design
Controllers using to handless only one request by app design. You should not call methods from one controller in another. If you want to share methods for several controllers, you can use inheritance, mixins, or service objects
inheritance
class BaseController < ApplicationController
private
def shared_method
end
end
class UsersController < BaseController
def index
shared_method
end
end
mixin
module SomeMixin
extend ActiveSupport::Concern
included do
def shared_method
end
end
end
class UsersController < ApplicationController
include SomeMixin
def index
shared_method
end
end
service object
class SomeService
def shared_method(params)
# process params
end
end
class UsersController < ApplicationController
include SomeMixin
def index
SomeService.new.shared_method(params)
end
end
It wasn't always this way and it's there to protect you. Check out this great blog post on the subject.
Here are some relevant snippets:
Problem with Mass-Assignment: Security vulnerability
Mass-assignment saves us the need to assign values to each attribute
of the model, but it can create problems. Since we aren’t restricting
which attributes can be set nor are we checking the valus of these
attributes, a malicious hacker could assign any value to any
attribute. In our example, he could set the value of admin true,
making himself a super user.
Here is what the url might look like
http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1
Users are able to exploit this if they know even a little bit about your models and cause issues.
In most languages there are classifications in a class for methods, attributes or whatever else it may contain.
Depending on language or inheritence, default behaviour might be public, private...
In ruby, classes by default contain public methods and attributes. You need to specify if a method is private.
The public method or attribute:
This is accessible from out of the class or the instance of the class. So, if you have class:
class Foo
def my_id
10
end
private
def my_class
"Bar"
end
public
def my_friend
"Zonk"
end
end
Then:
2.0.0p247 :001 > #foo = Foo.new
2.0.0p247 :002 > #foo.my_id
=> 10
2.0.0p247 :003 > #foo.my_class
NoMethodError: undefined method `my_class' for #<Foo:0x000000045a53f8>
2.0.0p247 :004 > #foo.my_friend
=> "Zonk"
You see than you can change from private to public as you wish, though maybe not a very good idea.

Loss of context in multiple blocks

I'm trying to create a super class that will be extended. The super class will call a method that has to be implemented by the child class. The thing is, that method is called sometimes 3 blocks deep. In those blocks, I also refer to attributes of the class.
But, I get an error saying that there is no variable or method, and it's because the methods and variables are assumed to be from the block class.
This is how it looks like:
class SuperClass
attr_accessor :model
def initialize(model)
#model = model
end
def resources
s = Tire.search(get_index) do
query do
boolean do
must { term :model_id, model.id } #attr_accessor fails
must { all }
search_scope(self) #search_scope fails
end
end
sort do
sort_scope(self) #sort_scope fails
end
end
s.results
end
end
class SubClass < SuperClass
attr_accessor :params
def initialize(model, params)
#params = params
super(model)
end
def search_scope(boolean_query)
boolean_query.must { term field: params[:feild] }
#...
end
def sort_scope(sort_query)
sort_query.by :field, params[:sort_dir]
#...
end
end
search = SubClass.new(model, {})
results = search.resources # undefined method error as explained below
What I'm trying to achieve is calling the method search_scope and sort_scope (Implemented in child classes) that will set also set a few search and sort parameters. But I get undefined method 'search_scope' for #<Tire::Search::BooleanQuery:0x00000004fc9820>. As you can see, it's trying to call search_scope on the class of the block context. Same with the attr_accessor :model.
I know I can remedy this by doing
def resources
instance = self
# ...
end
And then calling instance.model and instance.search_scope, but this means my child classes have to define the instance in their own search_scope and sort_scope methods too.
I was wondering whether there is a better way to solving this?

How to discover the overrided methods in Ruby/Rails?

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.

Resources