Controller Inheritance how to implement an abstract controller - ruby-on-rails

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

Related

Can rails controllers be inherited to a third level deep?

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

how to render a view from the model in Rails?

I have to edit a Rails application, but I am a novice when it comes to language (I am new to Ruby & Rails). When viewing the source files I raised some questions:
class Card < ActiveRecord::Base
class CardRenderer < AbstractController::Base
include AbstractController::Rendering
include AbstractController::Helpers
self.view_paths = Rails.application.config.paths["app/views"]
helper_method :res_url
def print(_card, _template)
#card = _card
render template: "prints/#{_template}.html"
end
protected
def res_url(_res_name)
"#{Rails.root}/app/views/prints/res/#{_res_name}"
end
end
.......
.......
.......
end
I saw a model that contained a classe inside another class
Do you have a name this language feature?
How I can call this class from an instance of the model?
for example from the controller (Obviously this does not work):
#card::CardRenderer.new.print(#card, #template)
The class that is inside the model class has a function that renders a view
I can make use this function from the controller?
for example from the controller (obviously this does not work either)
render Card::CardRenderer.new.print(#card, #template)
Is that the ideal is that I read a book!, And I'll do it! but in these days I can not (timing problems).
Do you have a name for this language feature?
"nested classes", I think
How I can call this class from an instance of the model?
#card.class::CardRenderer should work
The class that is inside the model class has a function that renders a view. Can I use this function from the controller?
Read the whole thing start to finish:
http://guides.rubyonrails.org/layouts_and_rendering.html
p.s. I have a sneaking suspicion the current design of the app goes completely against the ideas of Model-View-Controller

Rails: How to POST internally to another controller action?

This is going to sound strange, but hear me out...I need to be able to make the equivalent of a POST request to one of my other controllers. The SimpleController is basically a simplified version of a more verbose controller. How can I do this appropriately?
class VerboseController < ApplicationController
def create
# lots of required params
end
end
class SimpleController < ApplicationController
def create
# prepare the params required for VerboseController.create
# now call the VerboseController.create with the new params
end
end
Maybe I am over-thinking this, but I don't know how to do this.
Inter-controller communication in a Rails app (or any web app following the same model-adapter-view pattern for that matter) is something you should actively avoid. When you are tempted to do so consider it a sign that you are fighting the patterns and framework your app is built on and that you are relying on logic has been implemented at the wrong layer of your application.
As #ismaelga suggested in a comment; both controllers should invoke some common component to handle this shared behavior and keep your controllers "skinny". In Rails that's often a method on a model object, especially for the sort of creation behavior you seem to be worried about in this case.
You shouldn't be doing this. Are you creating a model? Then having two class methods on the model would be much better. It also separates the code much better. Then you can use the methods not only in controllers but also background jobs (etc.) in the future.
For example if you're creating a Person:
class VerboseController < ApplicationController
def create
Person.verbose_create(params)
end
end
class SimpleController < ApplicationController
def create
Person.simple_create(params)
end
end
Then in the Person-model you could go like this:
class Person
def self.verbose_create(options)
# ... do the creating stuff here
end
def self.simple_create(options)
# Prepare the options as you were trying to do in the controller...
prepared_options = options.merge(some: "option")
# ... and pass them to the verbose_create method
verbose_create(prepared_options)
end
end
I hope this can help a little. :-)

Call a controller's method in other controllers (staying DRY)

I'm slightly new to Rails (i.e. stupid and need some teachin').
I have a controller (call it ControllerFoo) that performs a particular task (theMethod) which could be useful in other controllers (say, from within ControllerBar). So, of course, the method is defined as self.theMethod in ControllerFoo (which means it's a class method, right?), and access in ControllerBar as ControllerFoo.theMethod. Confused yet?
Here's the problem: the ControllerFoo.theMethod uses session data, and when called from ControllerBar, session is nil. In fact, it seems that session is also nil when being called from itself. I guess what I'm saying is class methods can't access session data?
<rant>I hate how session data can't simply be accessed anywhere like in PHP</rant>
So for now, since I'm not smart enough to know how to do this correctly, I've just duplicated the logic in several places throughout my app. But this is not DRY at all, and I hate it.
So how can I create a method in a controller that's accessible to other controllers and can also access session data?
class ControllerFoo < ApplicationController
def self.theMethod (greeting)
p "#{greeting} #{session[:user]}!"
end
end
class ControllerBar < ApplicationController
def show
ControllerFoo.theMethod("Hello,")
end
end
Couple of options...
Put the shared method in the shared parent ApplicationController
Create a module that both ControllerFoo and ControllerBar will include
e.g.
module SharedModule
def theMethod (greeting)
p "#{greeting} #{session[:user]}!"
end
end
class ControllerFoo < ApplicationController
include SharedModule
end
class ControllerBar < ApplicationController
include SharedModule
def show
theMethod("Hello,")
end
end
The way you would do this is Ruby would be to create a module containing the class (or instance) methods you wish to share and include it in the classes you need to have those methods defined in.

How should I create a Class that has access to the same variables/scope as the Controller it's in? - Rails Presenter Pattern

As the application grows, I'm starting to use a Presenter Pattern similar to what's outlined here:
http://blog.jayfields.com/2007/03/rails-presenter-pattern.html
I would like the presenter to be able to access the same scope of the controller it's in, in a way that minimally impacts application performance, and that has a clear API. Something like this:
class UsersController < ApplicationController
def index
#view = UsersPresenter.new(self)
end
end
class Presenter
def initialize(controller)
end
end
class UsersPresenter < Presenter
end
I could use method_missing to access methods on the controller but that comes at a performance cost, and would probably be confusing to debug:
class Presenter
attr_reader :controller
def initialize(controller)
#controller = controller
end
def method_missing(method, *args, &block)
controller.send(method, *args, &block)
end
end
I would like it to work just like a module, but without being a module so I don't clutter the global controller namespace.
Any ideas what's best here? Maybe the delegate method?
Thanks for the tips.
An approach to work around method_missing: Enumerate all instance methods of the controller and define methods in the eigenclass of your presenter (in the initializer). However I doubt that it's better performance-wise when compared to method_missing.
Also this won't work for methods that were added to the controller via method_missing (like polymorphic routes).
I don't think that you can work around the method_missing completely if you want access to all the (missing) methods of the controller without explicitly writing controller.some_method in your presenter.

Resources