How to efficiently instantiate instance variables across multiple controllers - ruby-on-rails

consider the following instance variables
#categories = Category.all
#posts = Post.order("created_at DESC")
i want to use them across multiple controllers as part of the footer, what is the most efficient way to do it?

I see TWO ways to do so:
FIRST:
I would do it using inheritance.
class GenericController < ApplicationController
// Declare your class variables here
end
and then
class MyController01 < GenericController
end
class MyController01 < GenericController
end
.
.
.
Then the class variables would be available in the descendant classes.
SECOND:
Another possibility, easier to implement, is putting these variable directly in app/controller/application_controller.rb. Remember all controllers are descendant of this one.
Then you may just use these variables directly in your layout(s) and everything will be fine.

Personally, I prefer composition to inheritance. So, I might create
# app/controllers/shared_methods/load_footer_variables.rb
module SharedMethods
module LoadFooterVariables
def load_footer_variables
#categories = Category.all
#posts = Post.order("created_at DESC")
end
end
end
Now, in every controller where you want to load your footer variables, do
#app/controllers/foo_controller.rb
class FooController < ApplicationController
include SharedMethods::LoadFooterVariables
before_action :load_footer_variables, :only => [:method_a, :method_b]
def method_a
...
end
def method_b
...
end
def method_c
...
end
end
The include SharedMethods::LoadFooterVariables call makes the methods in the module available within the controller. The before_action call instructs the controller to call the load_footer_variables method prior to method_a and method_b (but not method_c).
If you always want the load_footer_variables called for every action in the controller, then omit the :only argument. You can also use :except if it's more convenient to exclude action rather than include them.

Related

How to use reusable controller methods in Rails?

I'm relatively new to Rails and am working on creating a simple user authentication system to get to grips with how Rails works.
I'm currently at the point where I'd like to create some methods that I can use in my controllers like so:
is_logged? # => true
and
current_user_id # => 6
These would be used to interact with sessions, mainly so I'm not repeating myself in the controller.
Where would I define these functions and how would I include them in a controller?
Thanks a lot in advance for any help.
Method 1
You can define these method in helper files, inside app/helpers/my_module.rb. You can create a module there, put all the methods inside of it, and then include the modules in your control to use these method.
module MyMoule
def is_logged?
...
end
end
Then in you class include the module
class MyClassController < ApplicationController
include MyModule
def my_method
#Use it like this
logged_in = MyModule.is_logged?
end
end
Method 2
If you using session related stuff you can always put them inside application_controller.rb. And since all your controller will inherit ApplicationController the methods will be available to you.
class ApplicationController < ActionController::Base
def is_logged?
...
end
end
In your other controller you can use them directly.
class MyClassController < ApplicationController
def my_method
logged_in = is_logged?
end
end

Ruby/Rails: trying to invent an inherited resource gem. Class instance variable in mixin

I want to DRY my views and controllers from a lot of similar code. I want to do it by myself in educational purpose, I know about the InheritedResourse gem.
So far I wrote:
class Admin::ResourcesController < Admin::AdminBaseController
before_filter :get_model_name
def index
result = #model.all #Resource.all
instance_variable_set "##{##collection_resource_name}", result # #resources = result
result # return it duo it can be used with super
end
def show
result = #model.find(params[:id])
instance_variable_set "##{##instance_resource_name}", result
result
end
protected
##collection_resource_name = 'resources'
##instance_resource_name = 'resource'
def self.set_resource_name(hash)
##instance_resource_name = hash[:instance_resource_name]
##collection_resource_name = hash[:collection_resource_name]
end
private
def get_model_name
#model = controller_name.classify.constantize # Resource
end
end
Only two actions, but you got the idea: abstract any model to a 'resource', set a list of model fields (or get it dynamicaly) and that's it.
First of all, I think instead of ##instance_resource_name (class variable), I need a class instance variable. I'm right?
... but, it is not a main question. I think that it's cool when this kind of code is wrapped in mixin. Because in my example it is Admin::ResourceController, but I can also have a User::ResourceController or just something another.
Ok, I wrapped it in a mixin. For usability, I want to call something like actions only: [:index, :show] in controller in section, where I put before_filter, for example.
How this section of code is called? Class instance?
Ok, the example:
require 'active_support/concern'
module ScaffoldResources
extend ActiveSupport::Concern
included do
def hello
self.class.action_list
end
end
module ClassMethods
#action_list = [:new, :show, :index, :edit, :update, :destroy, :create]
attr_accessor :actions_list
def actions(*params)
#actions_list = params
end
end
end
For testing, I create this small controller:
class Admin::UsersController < Admin::ResourcesController
include ScaffoldResources
#actions_list = 'hard set'
actions 'some','actions','i think'
def show
render json: hello
end
end
So, when I call hello method (it do only self.class.action_list) I want to see anything. I set class instance variable in mixin and in class – hardcode and through method defined in mixin. But it's nil!
I think you got the idea, what I'm trying to achieve. How it can be achieved?

How do I initialise objects in a partial view for use by multiple controllers?

I hope this is something obvious that I've just consistently overlooked and the community can set me on the right path.
I have a news article controller, but I want to be able to use a "common" ticker list on different views. How do I initialise this "#article_list" if I'm using the partial in a few controllers? Apparently it is of the opinion that using a helper is not the solution, since helpers are just for view logic. So where do I put this initialiser that would be available to every controller as required? I shouldn't put them in application controller should I?
You can use before_filter method, i.e. something like this:
class ApplicationController < ActionController::Base
def set_article_list
#article_list = ArticleList.all # or any onther selection
end
end
class NewsArticleController < ApplicationController
before_filter :set_article_list, only: :action1
def action1
end
end
class AnotherNewsArticleController < ApplicationController
before_filter :set_article_list, only: :another_action1
def another_action1
end
end
UPDATE:
Indeed, there will be problem with a fat ApplicationController. To avoid it it's possible to use module (almost #carolclarinet describe it below):
module ArticleList
def set_article_list
#article_list = ArticleList.all # or any onther selection
end
end
class NewsArticleController < ApplicationController
include ArticleList
before_filter :set_article_list, only: :action1
def action1
end
end
class AnotherNewsArticleController < ApplicationController
include ArticleList
before_filter :set_article_list, only: :another_action1
def another_action1
end
end
And
You can create, essentially, a query object that is only responsible for returning what you need for #article_list, for example, building off of Psylone's answer:
class ArticleList
def ticker_articles
ArticleList.all # or any onther selection
end
end
This class could go in lib, app/models, app/query_objects, app/models/query_objects, wherever it makes sense for you. This is a bit outside The Rails Way so there's no convention about where these types of objects should live.
Then in whatever controller you need this, do:
#article_list = ArticleList.new.ticker_articles
For more explanation of query objects, see this codeclimate article #4. Depending on what you're doing to set #article_list, this might also be called a service object (#2) or something else entirely. No matter what you call it though, its responsibility would be to return the value you need for #article_list and that's it.

How to reuse the rendering actions from nested controllers in Rails?

I have a question regarding the reuse of code among controller actions. I think it is a fairly standard situation, so I am interested in what's the best practice in Rails.
Let's say I have a films resource with a corresponding FilmsController, which has a nested resource comments served by CommentsController. The nested resource can be rendered on its own using its index and show actions. However, it should also be possible to render the comments embedded in the corresponding film page.
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
1) Force the CommentsController.index to render to a string and then pass it in a variable to the film view?
Or 2) call the CommentsController.index directly in the film view as a kind of "partial", executing the database queries from there?
Or 3) create a separate method in CommentsController responsible for the database handling, call it from both CommentsController.index and FilmsController.show, and use the corresponding view in both the places, too?
To me the options 1) and 2) seem a bit messy, while 3) is not modular and involves some repeating of code. Is there any better way to accomplish this?
Thanks a lot!
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
You could move the shared controller logic into a inside your application controller (or a lib and require it appropriately), a la:
class ApplicationController < ActionController::Base
def foo
#foo = "foo"
end
end
Comments Controller:
class CommentsController < ApplicationController
before_filter :foo, :only => [:index]
def index
end
end
Films Controller:
class FilmsController < ApplicationController
before_filter :foo, :only => [:show]
def show
end
end
For repeated view logic you can move that to a common folder, say your_app/app/views/shared/_foo.html.erb and render that appropriately.
Another option is to place the relevant code into an external module:
lib/mymodule.rb
module MyModule
def foo
end
end
And then you can include the module inside your controller or anywhere you want access to your foo method.
class CommentsController < ApplicationController
include MyModule
def index
foo()
end
end

how do you put before filters in modular controllers?

I have several controllers that are in a module:
class SoapTest::DashboardController < ApplicationController
class SoapTest::TestCasesController < ApplicationController
etc.
I want to be able to check if a user has certain permissions for a module, and since I don't have a "parent" controller where the above ones inherit, i thought to put the check in a before filter in applications. But I can't seem to get the module name:
in application controller, i have:
before_filter :check_company_features
def check_company_features
puts controller_name
end
but controller_name just returns "dashboard". I need to get the "SoapTest" clause
Be attention, what you currently call modules actually are namespaces.
The reason why controller_name returns only the class name (and not the fully qualified name) is because Rails explicitly strips the namespaces. You can get them by calling the Ruby #name method on the controller class.
class SoapTest::DashboardController < ApplicationController
before_filter :check_company_features
def check_company_features
puts controller_name
# => "dashboard_controller"
puts self.class.name
# => "SoapTest::DashboardController"
end
end
There are several String inflection methods you can call on the #name to get the formatted version.
However, I strongly encourage you to use a namespaced main controller.
Instead of using
class SoapTest::DashboardController < ApplicationController
you can extend a SoapTest::ApplicationController
class SoapTest::ApplicationController < ApplicationController
before_filter :check_company_features
def check_company_features
# ...
end
end
class SoapTest::DashboardController < SoapTest::ApplicationController
end

Resources