Can rails controllers be inherited to a third level deep? - ruby-on-rails

Can I have controllers in Rails that are 3 levels deep inheritance? One would think such a trivial thing is possible, but the concrete controller at the "third" level gives the generic/useless error of "uninitialized constant Ns2::SecondController"
This is basically with this code (I haven't tried this exact code)
module Ns3
class ThirdController < Ns2::SecondController
end
end
module Ns2
class SecondController< Ns1::FirstController
end
end
module Ns1
class FirstController< ApplicationController
end
end
NOTE: The use of namespaces, within the routes and all such directories should be set up properly.
I'm sure I could rearrange the logic and get something working with mixins or helpers. However, I'd like the immediate question answered for my own benefit. Either Y/N or a way passed the error. Not interested in a refactoring work-around solution ATM. Though I guess it couldn't hurt.
Thanks

This can be done.
However it appears RoR is weird, and that you have to implicitly specify the namespace for base classes. If you let it default to the current namespace it acts weird.

Its most likely a typo in either the class name or filename.
You need to put the classes in the correct file/directory structure for Rails autoloading to work, e.g:
#/controllers/ns3/third_controller.rb
module Ns3
class ThirdController < Ns2::SecondController
end
end
#/controllers/ns2/second_controller.rb
module Ns2
class SecondController < Ns1::FirstController
end
end
#/controllers/ns1/first_controller.rb
module Ns1
class FirstController < ApplicationController
end
end
Another thing to try is scoping from the root namespace so with a :: prefix, like so:
module Ns1
class SecondController < ::Ns1::FirstController
end
end
You could also try this:
#/controllers/ns3/third_controller.rb
class Ns3::ThirdController < ::Ns2::SecondController
end

Related

Keep namespacing of a controller when including a model in Rails

I am trying to keep the namespace of a class when including a module.
Lets say I have these Models:
class Shop < ApplicationRecord
self.abstract_class = true
end
class A::Shop < ::Shop
end
class B::Shop < ::Shop
end
And this controller:
module A
class ShopController < AuthenticatedController
include Basic::Features
def test
p Shop.new #YES! its a A::Shop
end
end
end
And this Module:
module Basic
module Features
def test
p Shop.new #Shop (abstract)
end
end
end
In the above example, the namespace is overwritten when including the module.
As I want to use the Basic::Features module at multiple places in my codebase, I would like to automatically switch between A::Shop and B::Shop when including it in the controller.
Anybody any idea if this is possible, and how.
Here is one option:
module Basic
module Features
def test
p Object.const_get('::' + self.class.to_s.split('::').first + '::Shop')
end
end
end
It will not work if you have deeper namespaces, e.g. A::B::Shop, but it could be made to work. Also in rails you could use deconstantize instead of split.
I think the reason you code does not work is because it is looking in A::ShopController namespace and since not found it then tries the root namespace, ::, and finds Shop.

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

Defining/nesting modules and classes in a Rails app

I'm trying to define a variety of modules/classes in a Rails app. My directory structure looks something like this:
lib/
fruit/ # just a module, with no associated file
fruit_operator.rb
apple.rb # abstract class, defines behavior for inheritance
orange.rb # abstract class, defines behavior for inheritance
apple/
granny_smith.rb # should inherit from apple.rb
red_delicious.rb
orange/
valencia.rb
seville.rb
I want two things:
The sub-classes should inherit from their parent classes (Apple and Orange).
I should be able to access these classes from a top-level (within /fruit file -- ie fruit_operator.rb
All the attempts I've tried to get this working are throwing an error of some sort or another.
Attempt # 1:
apple.rb
module Fruit
class Apple
def juicy
true
end
end
end
apple/granny_smith.rb
module Fruit
class GrannySmith::Apple
end
end
When I try to access GrannySmith from fruit_operator.rb I run into errors. Accessing as simply GrannySmith generates
uninitialized constant Fruit::FruitOperator::GrannySmith
If I try Fruit::GrannySmith, I get
uninitialized constant Fruit::GrannySmith
If I try Apple::GrannySmith or Fruit::Apple::GrannySmith, I hit the error
Unable to autoload constant Fruit::Apple::GrannySmith, expected /lib/fruit/apple/granny_smith.rb to define it
Attempt #2:
apple.rb
class Fruit::Apple
def juicy
true
end
end
apple/granny_smith.rb
class GrannySmith < Fruit::Apple
end
Attempting to access from fruit_operator.rb, I run into identical errors as the above.
Attempt #3:
apple.rb
class Fruit::Apple
def juicy
true
end
end
apple/granny_smith.rb
class Fruit::Apple::GrannySmith
end
This last version allows me to access the class directly from fruit_operator.rb (as Apple::GrannySmith), but it doesn't inherit from Apple!
Any idea how to structure/access these classes and modules? I've looked around quite a bit (on SO and elsewhere), and can't find a great guide for how to do this, particularly in a Rails app.
You must import the definition of the fruit files into the fruit operator file. For example,
require_relative './apple/granny_smith'
I think you're best solution is to implement Fruit as a class, and have Apple and Orange both inherit from Fruit, and GrannySmith inherit from Apple, like so:
Class Fruit
def seeds?
true
end
end
Class Apple < Fruit
def juicy
true
end
end
class GrannySmith < Apple
def color
"green"
end
end
Depending on what your need for the fruit_operator is, you may choose to include those methods/actions via a mixin Module.

Controller Inheritance how to implement an abstract controller

Given I have a class A which is kind of abstract and encapsulates logic which is needed in decendants B and C.
class A
end
class B < A
end
class C < A
end
Furthermore if have resourceful routing which provides routes for B and C and are therefore handled by the respective controllers.
To dry up things I moved shared code of both conntrollers into an "abstract" controller (never to be instantiated and no routes to its actions):
class AController < ApplicationController
def new(additional_opts)
render locals: {base: "stuff"}.merge(additional_opts)
end
end
class BController < AController
def new
super(foo: 1)
end
end
class CController < AController
def new
super(bar: 1)
end
end
A controller action usally has no parameters. But since the AController is intended to be abstract this approach may be valid, or is it better to rely on instance variables and simply call super and then pulling the needed information from the variables instead?
Any insights welcome.
Edit 1:
Thankfully Lateralu42 suggested Concerns which gets me thinking about; ok what is my real question here i want to have an anwser for? (Like in hitch hikers guide). So it is also
about then to use which method of code reuse?
Found a nice blog post here.
Actually, I think your problem could be solved using the concerns pattern (module shared between controllers or models) : How to use concerns in Rails 4

Rails - Best practice for abstract class definition and file naming

I want to define 3 classes:
a MotherClass (abstract, can not be inferred)
a SubClassA (inherits from MotherClass)
a SubClassB (inherits from MotherClass)
What is the best solution to declare it in Rails ?
1. Put everything in app/models/
MotherClass < AR::Base in app/models/mother_class.rb
SubClassA < MotherClass in app_models/sub_class_a.rb
SubClassB < MotherClass in app/models/sub_class_b.rb
Advantage: not very complicated to implement
Inconvenient: a big mess in models folder
2. Create a module for the two subclasses
MotherClass < AR::Base in app/models/mother_class.rb
MotherModule::SubClassA < MotherClass in app/models/mother_module/sub_class_a.rb
MotherModule::SubClassB < MotherClass in app/models/mother_module/sub_class_b.rb
Advantage: same than Solution 1
Inconvenient: naming MotherModule and MotherClass with different names, but they mean almost the same thing
3. Create a module for the 3 classes
MotherModule::Base < AR::Base in app/models/mother_module/base.rb
MotherModule::SubClassA < MotherModule::Base in app/models/mother_module/sub_class_a.rb
MotherModule::SubClassB < MotherModule::Base in app/models/mother_module/sub_class_b.rb
Advantage: very clean
Inconvenient: need some functions in Base to override (table_name for example)
So my question is: What is the best practice in Rails and
- how to name my classes?
- what are their directories?
First of all, I think you must already realize that ruby does not have true abstract classes. But we can approximate the behavior. And while doing so, it sounds like you have a preference toward organizational structure which I will attempt to address.
I must start by saying, however, that I'm surprised that you're coming at the problem so strongly from the organizational angle. First on my mind would be whether I really wanted to implement single table inheritance or not and then let that drive the organizational problem. Usually the answer here is that Single Table Inheritance is not what you actually want. But... let's dive in!
Using Single Table Inheritance
Here's the standard way to utilize and organize models using Single Table Inheritance:
# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
# An "abstract" method
def method1
raise NotImplementedError, "Subclasses must define `method1`."
end
def method2
puts method1 # raises NotImplementedError if `method1` is not redefined by a subclass
end
end
# app/models/sub_class_a.rb
class SubClassA < MotherClass
def method1
# do something
end
end
# app/models/sub_class_b.rb
class SubClassB < MotherClass
def method1
# do something
end
end
Given the above, we would get an exception when calling MotherClass.new.method2 but not when calling SubClassA.new.method2 or SubClassB.new.method2. So we've satisfied the "abstract" requirements. Organizationally, you called this a big mess in the models folder... which I can understand if you've got tons of these subclasses or something. But, remember that in single table inheritance even then parent class is a model and is / should be usable as such! So, that said, if you'd really like to organize your models file system better then you are free to do so. For example, you could do:
app/models/<some_organizational_name>/mother_class.rb
app/models/<some_organizational_name>/sub_class_a.rb
app/models/<some_organizational_name>/sub_class_b.rb
In this, we are keeping all other things (i.e. the Code for each of these models) the same. We're not namespacing these models in any way, we're just organizing them. To make this work it's just a matter of helping Rails to find the models now that we've placed them in a subfolder of the models folder without any other clues (i.e. without namespacing them). Please refer to this other Stack Overflow post for this. But, in short, you simply need to add the following to your config/application.rb file:
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**/}')]
Using Mixins
If you decide that Single Table Inheritance is not what you want (and they often aren't, really) then mixins can give you the same quasi-abstract functionality. And you can, again, be flexible on file organization. The common, organizational pattern for mixins is this:
# app/models/concerns/mother_module.rb
module MotherModule
extend ActiveSupport::Concern
# An "abstract" method
def method1
raise NotImplementedError, "Subclasses must define `method1`."
end
def method2
puts method1 # raises NotImplementedError if `method1` is not redefined
end
end
# app/models/sub_class_a.rb
class SubClassA
include MotherModule
def method1
# do something
end
end
# app/models/sub_class_b.rb
class SubClassB
include MotherModule
def method1
# do something
end
end
With this approach, we continue to not get an exception when calling SubClassA.new.method2 or SubClassB.new.method2 because we've overridden these methods in the "subclasses". And since we can't really call MotherModule#method1 directly it is certainly an abstract method.
In the above organization, we've tucked MotherModule away into the models/concerns folder. This is the common location for mixins in Rails these days. You didn't mention what rails version you're on, so if you don't already have a models/concerns folder you'll want to make one and then make rails autoload models from there. This would, again, be done in config/application.rb with the following line:
config.autoload_paths += Dir[Rails.root.join('app', 'concerns', '{**/}')]
The organization with the mixins approach is, in my opinion, simple and clear in that SubclassA and SubClassB are (obviously) models and, since they include the MotherModule concern they get the behaviors of MotherModule. If you wanted to group the subclass models, organizationally, into a folder then you could still do this of course. Just use the same approach outlined at the end of the Single Table Inheritance section, above. But I'd probably keep MotherModule located in the models/concerns folder still.
Even though ruby doesn't really have abstract classes, it's powerful enough to let you implement it yourself by implementing self.included on a mixin module. Hopefully this generic example gives you enough to go on for your particular implementation.
module MotherInterface
def self.included base
required_class_methods = [:method1, :method2]
required_instance_methods = [:fizzle, :fazzle]
required_associations = [:traits, :whatevers]
required_class_methods.each do |cm|
raise "MotherInterface: please define .#{cm} class method on host class #{base.name}" unless base.respond_to?(cm)
end
required_instance_methods.each do |im|
raise "MotherInterface: please define ##{im} instance method on host class #{base.name}" unless base.instance_methods.include?(im)
end
required_associations.each do |ass|
raise "MotherInterface: please be sure #{base.name} has a :#{ass} association" unless base.reflections.key?(ass)
end
base.send :include, InstanceMethods
base.extend ClassMethods
end
# inherited instance methods
module InstanceMethods
def foo
end
def bar
end
end
# inherited class methods
module ClassMethods
def baz
end
def bat
end
end
end
class SubClassA < ActiveRecord::Base
include MotherInterface
# ... define required methods here ...
end
class SubClassB < ActiveRecord::Base
include MotherInterface
end
Some advantages to this approach are:
Yes, you can still technically instantiate the mixin, but it's not actually tied to active record, so it tastes more like an abstract class.
The sub classes get to define their own connection information. You have two databases? Differing columns? Cool, no problem. Just implement your instance methods and stuff appropriately.
The dividing line between parent and child is very obvious.
But, there are disadvantages too:
All the meta programming is a bit more complex. You'll have to think a little abstractly (HA!) about how to organize your code.
There are probably other advantages and disadvantages I haven't considered, kind of in a hurry here.
Now, as far as file locations, I would suggest that the mixin itself, presumably mother_interface.rb, go someplace other than your models folder.
In config/application.rb, throw in a line like this:
config.autoload_paths << File.join(Rails.root, 'app', 'lib')
...and then you can create (rails)/app/lib/mother_interface.rb. Really, you should do it however makes sense to you. I dislike the word "concerns" for this, and other people dislike the word "lib." So, use whatever word you like, or make up your own.
Using Single Table Inheritance with little bit meta programming
# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
def self.inherited(subclass)
subclass.include(OnlyChildMethods)
end
module OnlyChildMethods
extend ActiveSupport::Concern
included do
def child_method_one
puts 'hi one'
end
def child_method_two
puts 'hi two'
end
end
end
end
# app/models/sub_class_a.rb
class SubClassA < MotherClass
def some_specific_method
#some code
end
end
# app/models/sub_class_b.rb
class SubClassB < MotherClass
def some_specific_method
#some code
end
end
mother_class_instance.child_method_one
=> NoMethodError: undefined method 'child_method_one'
sub_class_a_instance.child_method_one
hi one
=> nil

Resources