1

I have two controllers in different namespace(a and b), like below:

class A::TechnologiesController < ApplicationController def index render json: Technology.all end end class B::TechnologiesController < ApplicationController def index render json: Technology.all end end 

The two actions execute the same logic, and I belive it is a repetition. I want to eliminate the repetition, so how can I borrow the code in namespace a like below?

class B::TechnologiesController < ApplicationController def index A::TechnologiesController.method(:index).call self end end 
5
  • 1
    Either make both controllers inherit from a common ancestor (class A::TechnologiesController < BaseTechnologiesController) or include a module that provides the methods. Commented Jan 3, 2018 at 8:24
  • @Stefan So there isn't a method to borrow the method in another class like javascript? Commented Jan 3, 2018 at 8:26
  • In my own opinion, in different controller, you don't need do any mixins. It is more readable. Commented Jan 3, 2018 at 8:29
  • You can use ActiveModel::Concern here. You can define one method in concern and include in your controller. Commented Jan 3, 2018 at 8:29
  • @Run What do you mean, "like in javascript"? You can call a **static**(!!) method from another class in both languages, but this method is not - and cannot be - static. Commented Jan 3, 2018 at 12:47

4 Answers 4

3

Answering an implicit question stated in comments: there is an ability to borrow the method with UnboundMethod#bind if and only the object calling bind is_a?() instance of the class the method belongs to:

def index A::TechnologiesController.instance_method(:index).bind(self).() end 

but this is neither idiomatic nor readable. One should either use a mixin:

module Mixins::TechnologiesController def index render json: Technology.all end end class A::TechnologiesController < ApplicationController include Mixins::TechnologiesController end class B::TechnologiesController < ApplicationController include Mixins::TechnologiesController end 

or a common ancestor:

class Base::TechnologiesController < ApplicationController def index render json: Technology.all end end class A::TechnologiesController < Base::TechnologiesController; end class B::TechnologiesController < Base::TechnologiesController; end 

Bonus track: in Rails one might use Module#delegate monkeypatch.


Bonus track #2: the implementation on procs stored as constants:

class A::TechnologiesController < ApplicationController INDEX = -> { render json: Technology.all } def index INDEX.() end end class B::TechnologiesController < ApplicationController def index A::INDEX.() end end 
Sign up to request clarification or add additional context in comments.

9 Comments

@SergioTulentsev I did, thanks! [INFO] bound to the answer.
The first code raise: TypeError: bind argument must be an instance of A::TechnologiesController.
Although it is clearly overkill in this instance, one could also use a presenter, no?
@Run indeed, the object should be an instance of the source class. I never knew that before because nobody writes code in Ruby that way.
@SergioTulentsev nope. Check the example in the documentation. It makes a perfect sense in class hierarchy when one borrows a method from the middle of the hierarchy chain and tries to bind it to the instance of class being an ancestor.
|
1

This is a good example of a mixin.
With this you can DRY up your code and you don't have to call the method of another controller.

Here is the module:

module CommonInterface def render_technology render :json, Technology.all end end 

And this would be your controller

class B::TechnologiesController < ApplicationController include CommonInterface def index render_technology end end 

7 Comments

Please provide a complete answer; simple links are not welcome on SO, according to ToS.
So there isn't a method to borrow the method in another class like javascript?
@Run: this is possible in ruby, but it's not done. Unlike javascript, ruby has good tools for sharing functionality.
@SergioTulentsev can you provide the borrowing style?
“can you provide the borrowing style?”—what’s wrong with my answer? I doubt @SergioTulentsev might invent something more fashionable, but let’s see :)
|
0

The ancestor and mixin answers are really good if you do want to DRY things up, but in my opinion you don't necessarily have to. The render json: bit belongs in the controller action. You expect your action to render something in both controllers.

The query is something that might change in the same way over time I guess Technology.all, so you could abstract that somewhere, but again, it might be a good idea to wait and see if that is the case.

If you do go with mixins or a base class, you will couple the two controllers, which might be fine. But again, you might just want to take this decision later on.

Comments

0

You can solve the above problem by making a super method in the ApplicationController and you need to add one more method in each controller to pass the values to super method you can not only used for Technology model you can also used it for any other model.

For sample example

In the application controller

 class ApplicationController < ActionController::Base def index @collection = model end end 

In each controller we can call the above method like this

 class StudentsController < ApplicationController def index super end def model Student end end class JobsController < ApplicationController def index super end def model Job end end 

The data you will recived in the @collection that can be used in the views for example

For the app/views/students/index.html.erb

 <% @collection.all.each do |student| %> <tr> <td><%= student.Name %></td> <td><%= student.Email %></td> </tr> <% end %> 

For the app/views/jobs/index.html.erb

 <% @collection.all.each do |job| %> <tr> <td><%= job.id %></td> <td><%= job.exp %></td> </tr> <% end %> 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.