Can a ruby class method inherit from another class? - ruby-on-rails

I read here that a ruby class can only inherit from one class, and can include modules.
However, the devise module defines controllers like this:
class Users::PasswordsController < Devise::PasswordsController
...
end
Now, given that Users is probably a class, with PasswordsController being a method:
>> Devise::PasswordsController.class
=> Class
How is it that a method in a class inherits from another class?

class Users::PasswordsController < Devise::PasswordsController
...
end
In the above code, Users is the module and PasswordsController is the class inside Users module. Similarly Devise is the module and PasswordsController is the class inside Devise module.
so when you run
Users::PasswordsController.class
#=> Class
Users.class
#=>Module

What confuses you here is that you have wrong assumptions, namely:
Users is probably a class
Not necessarily. Here we have namespace with nesting, therefore Users can be either a class or a module. In fact classes are modules.
PasswordsController being a method
PasswordsController here is a class nested in the Users namespace. :: simply lets you go one level into the nesting tree.
Consider:
module Foo
class Bar
end
end
Foo::Bar.class # => class

From Rails naming convention, Users is most probably a module, and Users::PasswordsController is a class.
Note that :: is not for calling class methods (although it can be used this way). It's for accessing constants inside a module/class. For example
module Foo
BAR = 'bar'
end
Foo::BAR
#=> "bar"
In Ruby, a module/class name is a constant, and the value stored in it is the module/class. So :: also is used for accessing a module/class inside another module/class. For example
module Foo
class Bar
end
end
Foo::Bar
#=> Foo::Bar

Both Users and Device are modules, just used for scoping the real classes that are PasswordsController and PasswordsController.

Related

Loading module into User Model in Rails

Trying to make available the methods I have stored in a Module which is located in app/models (side note: not sure if this is the correct place for modules?).
Module:
module MyModule
class MyClass
def some_method
# do something
end
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
I am getting a NoMethodError:
NoMethodError (undefined method 'some_method' for #<User:0x00007f6a3ce452c0>
You seem to have missunderstood what what modules and classes do in Ruby. In Ruby a module is simply an object that wraps a set of methods and constants.
A module can extend other modules, classes and objects and can be included in classes thus implementing multiple inheritance. Modules in Ruby fill the role that traits, namespaces and singletons do in other languages.
Classes are actually modules (Module is part of the ancestors chain of Class) with the key difference that you can make instances of a class and that class can inherit from a single other class and cannot extend other objects or be included.
The code example here actually doesn't make sense. If you want to declare a method that will be available to classes that include a module you want to declare it in the module itself:
module MyModule
def some_method
# do something
end
end
When you then call User#another_method it will look in the ancestors chain of the User class until it finds the method which is defined in MyModule.
module MyModule
class MyClass
def some_method
# do something
end
end
end
Will actually definte the class MyClass with an instance method that is only available to instances of MyClass. The only thing that the module does here is change the module nesting so that the class is defined in MyModule instead of the global namespace.
If you want to mix in a method from a method into your class then just put the methods directly in the module (without an intermediate class).
Module:
module MyModule
def some_method
# do something
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
Have a look at this answer, you need to instantiate your Class first. Or if you want to
class User < ApplicationRecord
include MyModule
def another_method
my_instance = MyClass.new
my_instance.some_method
end
end
As for a place to store your Module, have a look at this guide about service objects, it gave me some inspiration when it comes to different modules.

Rails Model needs to validate includes? class_name

I have a model called ToolFilter with a column of 'tool_type'. The string here refers to a class for a tool. I put a method in my application_controller called tools_list that gets the descendants of Tool.This works nicely in my frontend, but ToolFilter is complaining about the method tools_list.
class ToolFilter < ActiveRecord::Base
validate :existence_of_tool
def existence_of_tool
unless tools_list.include? tool_type
errors.add(:tool_type, "Invalid tool_type {{tool_type}}, use 'tools_list' to see a list of valid tool_object_types")
end
end
class ApplicationController < ActionController::Base
helper_method :tools_list
def tools_list
Rails.application.eager_load!
Tool.descendants
end
It's a bit strange to tell a model about other classes in the file system, but I need to validate that it is one of these. Should I put tools_list is a module and include it in ToolFilter? Any suggestions?
Write this to include helper in your model
ApplicationController.helpers.tool_list
Though I will not recommend calling helper in model.
And checking tools with classes is damm bad idea.
I ended up creating a module called ToolExtention which has these helper methods in them. I then included this module in my controllers wherever it was needed and moved my logic from the views into the controller which I believe is better practice.
module ToolExtension
def self.tools_list
Rails.application.eager_load!
Tool.descendants
end
...
class ProjectsController < ApplicationController
include ToolExtension
...
ToolExtension.tools_list

How to mock a Rails subclass contant in Rspec?

I have a BaseController that I want to superclass other controllers with. I also superclass some controllers with the standard ApplicationController.
For example, I may have:
class AController < ApplicationController
end
as well as...
class BController < BaseController
end
When testing, I sometimes need to create an arbitrary "mock" or "fake" class to test before_filters, module includes, etc. I don't do this often, but on occasion it's helpful.
I created one in my specs like so:
class FakeController < ApplicationController
end
That's fine.
But now, I need to create another one of these FakeController classes, but this time as a subclass of my BaseController class.
Unfortunately the FakeController constant is already registered and I'm getting superclass mismatch errors.
I don't want to do something like FakeController2 -- as I think this opens a bad can of worms.
Any suggestions?
I don't see the issue with using FakeController2 or any other name, but the only public alternative I can think of is to introduce a module so that your second FakeController exists in a different namespace, as in:
module Foo
class FakeController < ApplicationController
end
end
There is a private method remove_const defined on Kernel which can be used to unregister a constant from an object. So, if FakeController is defined on Object, you can unregister it with the call:
Object.send(:remove_const, :FakeController)
At that point, you can define the constant again as you would if it had never never been defined in the first place. (Remember: Ruby is an interpreted language.)
An alternative to subclassing your controllers is to use the anonymous controller mechanism provided by the rspec-rails gem.

ruby modules and classes same name in structure

I have a folder structure like the following in one of my projects:
lib
bar.rb
bar
other_bar.rb
another_bar.rb
next_bar.rb
...
bar.rb
require File.expand_path(File.dirname(__FILE__) + "/bar/other_bar.rb")
class Bar
puts "running BarBase"
end
bar/other_bar.rb
module Bar
class OtherBar
puts "running module Bar with class OtherBar"
end
end
If I now run ruby bar.rb I get this:
running module Bar with class OtherBar
bar.rb:3:in `': Bar is not a class (TypeError)
I'd like to have a similar structure to a rails model inheritance structure. How can I fix this? So far as I know ruby does not support this out of the box. Is there a workaround for such a situation?
Bar can't be a module and a class, they are different things.
Change bar.rb to module Bar or change other_bar.rb to class Bar.
Whichever it is, it has to be consistent. You can't change one to the other. The question is which should it be? If Bar is a container for other classes and only has a few global singleton methods? Then it's a module. But if it can be instantiated, then it's a class.
And yes, you can nest classes. This is totally acceptable:
class Bar
class OtherBar
puts "running module Bar with class OtherBar"
end
end
Bar::OtherBar.new # yay!
Modules and Classes can be nested inside either other in any way you see fit.
Edit with some commented examples to help clear this all up:
module Foo
# Foo::A
class A
# simple namespaced class
end
# Foo::B, inherits from Foo::A
class B < A
# inherting from a class in the same namespace
end
# modify Foo::B
class B
# When modifying an existing class you don't need to define the superclass
# again. It will raise an error if you reopen a class and define a different
# superclass. But leaving it off is fine.
end
# nested module Foo::Inner
module Inner
# Foo::Inner::C
class C
# simple more deeply namespaced class
end
# Foo::Inner::D, inherits from Foo::A
class D < A
# inherits from a class in a parent namespace
# works because ruby looks upward in the nesting chain to find missing constants.
end
# Foo::Inner::Foo
class Foo
# simple nested class with the same name as something in a parent namespace
# This is a totally different Foo, because it's in a different namespace
end
# Foo::Inner::E, inherits from Foo::Inner::Foo
class E < Foo
# class inhereting from another class in the same namespace
# Foo::Inner::Foo is "closer" than the global Foo, so that gets found as the superclass
end
# Foo::Inner::F, which mixes in the gloabl module Foo
class F
# the :: constant prefix says to start looking in the global namespace
# so here we include the top level module Foo, and not the "closer" in namespace Foo::Inner::Foo
include ::Foo
# This is an error. This attempts to include the class Foo::Inner::Foo since thats the closest by namespace
# thing that matches the constant Foo. (you can't include classes, only modules)
# You need the :: prefix to grab the global Foo module
include Foo
end
end
end
# Z decalred in the global namespace, which inherits from the deeply nested class Foo::Inner::C
class Z < Foo::Inner::C
# Any class anywhere can inherit from any other class in any namespace.
# Just drill in!
end
# the following 2 declarations at this point would be identical
# This defines a class deep with in a namespace
class Foo::Inner::Foo::Bar < Foo::A
end
# same as above, but reopens each namespace
module Foo
module Inner
class Foo
class Bar < ::Foo::A
end
end
end
end
Just use class Bar instead of module Bar. In Ruby, classes can be reopened and added to.
It is recommended not to use a class as a namespace, especially if you use Rails.
If you want to keep the Bar class in lib/bar.rb, an option is to move the other classes in the Bars namespace in lib/bars.rb.
Read more as to why here: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/
Update: this behavior is fixed in ruby 2.5
See: https://blog.bigbinary.com/2017/10/18/ruby-2.5-has-removed-top-level-constant-lookup.html

Inheritance and Polymorphism conflicting in ruby on rails

I have an issue with Ruby on Rails.
I have several model classes that inherit from the same class in order to have some generic behaviour.
The parent class is called CachedElement.
One of the child is called Outcome.
I want an other model, called Flow to belong to any child of CachedElement.
Hence Flow has a polymorphic attributes called element, to which it belongs_to
When I create a new flow, that belongs to an Outcome, the element_type is set to "CachedElement" which is the parent class, instead of "Outcome".
This is confusing because since I have several type of CachedElement which are stored in different tables, the element_id refers to several different element.
In short I would like the element_type field to refer to the child class name and not the parent class name.
How can I do that ?
The field element_type is set to the parent class because ActiveRecord expects you to use single-table inheritance when deriving from other models. The field will reference the base class because it refers to the table that each instance is stored in.
If the children of CachedElement are stored in their own tables, it may be more helpful to replace the use of inheritance with the use of Ruby modules. The standard approach for sharing logic between classes is to use mix-ins instead of inheritance. For example:
module Cacheable
# methods that should be available for all cached models
# ...
end
class Outcome < ActiveRecord::Base
include Cacheable
# ...
end
You can now easily use polymorphic associations as you have been doing already, and element_type will be set to the proper class.
the file should go on you lib folder. but...
you could do the inheritance thing as well.
all you need to do is to tell you parent class to act as an abstract class.
# put this in your parent class then try to save a polymorphic item again.
# and dont forget to reload, (I prefer restart) if your gonna try this in
# your console.
def self.abstract_class?
true
end
and thats pretty much it, this was kinda unespected for me and actually really
hard to find in the documentation and anywhere else.
Kazuyoshi Tlacaelel.
Thanks, that's what I did, but it was kind of tricky to be able to inherits both instance and class methods from the module
Class methods can be done by:
module Cachable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def a_class_method
"I'm a class method!"
end
end
def an_instance_method
"I'm an instance method!"
end
end
class Outcome < ActiveRecord::Base
include Cacheable
end
if you want to add class methods and instance methods through a mixin (Module)
then I recommend you to abstract these in different modules.
module FakeInheritance
def self.included(klass)
klass.extend ClassMethods
klass.send(:include, InstanceMethods)
end
module ClassMethods
def some_static_method
# you dont need to add self's anywhere since they will be merged into the right scope
# and that is very cool because your code is more clean!
end
end
module InstanceMethods
# methods in here will be accessable only when you create an instance
end
end
# fake inheritance with static and instance methods
class CachedElement
include FakeInheritance
end

Resources