This is probably a stupid question, can you have a function available to multiple controllers?
I have 2 controllers
class C1Controller < ActionController::Base
def add(input_value)
#output_value = input_value * 2
end
end
now I would like to use this add function in another controller like so
class C2Controller < ActionController::Base
#new_value = add(2)
end
Define that function in app/controllers/application_controller.rb, and it will be available in all controllers.
ApplicationController can be used for this.
Make sure your other controllers are derived from ApplicationController and not directly from ActionController, as they should be. Then you can configure filters, common methods etc in the single ApplicationController.
But it might be wise to define a parent controller and have your controllers derive from it, as the ApplicationController is not really meant for random code sharing, but for specific purposes like filters and forgery protection.
You could also define the function in a helper and then include this helper in your ApplicationController. As follows:
include SessionsHelper
Related
What is the best and correct approach to do caching for actions?
Am I forced to ActionController::Base?
Is there another way (keeping ActionController::API present)?
Do I have to push caching down to Model layer?
I saw that Rails 6 (maybe prior) does not support action caching out of the box anymore. The caching was extracted to a gem: actionpack-action_caching https://github.com/rails/actionpack-action_caching. I installed it, but it seems not working with ActionController::API, it only works with ActionController::Base.
class ApplicationController < ActionController::API
must be changed to
class ApplicationController < ActionController::Base
Then, and only then I can cache an action like so:
class CategoryController < ApplicationController
caches_action :index
def index
#root_categories = Category.roots
end
end
Thank you in advance
The gem adds these methods to your controllers by including ActionController::Caching in ActionController::Base (see here). To add this behaviour to controllers that don't extend from ActionController::Base, simply include ActionController::Caching.
e.g.
class ApplicationController < ActionController::API
include ActionController::Caching
end
I have a controller with several actions. Many follow this pattern:
def favorites
#favorites = Favorite.where(organization_id: #resource.id).page(params[:page]).per(50)
end
It's not just favorites, but there's also downloads, searches, lists etc and they're all so similar that I wanted to create a method that I could call in a before_filter. Something like this:
def set_instance_variable
subject = __method__
class = __method__.singularize.constantize
instance_variable = self.class.instance_variable_set("##{subject}", "#{class}.where(organization_id: #resource.id).page(params[:page]).per(50)")
end
The syntax might be a little off here, but I know this won't work because __method__ will always be set_instance_variable and not the parent method where it is called.
Is there a way to dynamically set instance variables based on the method that defines them? Is this example above even on the right track?
I like the way the CanCan library handles this problem. With CanCan, you call a class method at the top of your controller:
load_resource
CanCan then looks at:
the action you're in to determine whether you want a collection or singular resource,
the name of the controller to determine the class to load
authorization rules to add scopes like your organization_id restriction (cancan is also a library for defining these)
I think pagination and resource loading are separate things, and you shouldn't put them in the same method. I'd shoot for an interface like this:
class FavoritesController
load_resource
paginate_resource only: [:index]
def show
# #favorite loaded here
end
def index
# #favorites loaded and paginated here
end
end
https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/controller_resource.rb#L29
If it makes more sense in your application to have non-restful resources, then you can't re-use the convention-based thing cancan is and instead have to define your own function. Consider something like this:
def favorites
#favorites = load_resource Favorite
end
private
def load_resource(klass)
klass.where(organization_id: #resource.id).page(params[:page]).per(50)
end
I am trying to set up an application where I can route via a String key instead of an id attribute. To illustrate this, please consider the following:
I have a class Foo which inherits from the ActiveRecord::Base and is implemented as follows:
class Foo < ActiveRecord::Base
attr_accessible :subject
Subject is a string type which exists in my database.
I have a controller which I implement as follows:
class SubjectController < ApplicationController
def index
#snip
end
As you can see, the SubjectController inherits from the ApplicationController.
Running rake routes gives me the standard (GET, POST, PUT, DELETE) routes for my subject. This is the expected behavior and I understand this functionality.
I want to know how I can extend the routes.rb file so that I can use a string url in order to access a subject. For example:
Instead of typing in localhost:3000/subject/1, I would like this /:id to resolve when I type in the url: localhost:3000/subject/grumpy-cat-says-hello
What does this implementation look like?
How should I setup my routes.rb file to accommodate this?
How should I configure my application to allow for this type of implementation?
Thank you in advance.
I've always used https://github.com/FriendlyId/friendly_id for this stuff.
If you prefer something simpler, this will do
class Foo < ActiveRecord::Base
def to_param
[id, subject.parameterize].join("-")
end
end
Then you can access your resource with: localhost:3000/foos/1-grumpy-cat-says-hello
Basically, since the name of the resource still starts with a number, it will be converted to a number by Rails and everything will work seamlessly.
This Gist goes in much greater detail about this topic :)
Sorry if this seems daft (not much sleep lately) but I'm trying to figure out the best way to share controller (and method) functionality between controllers without writing the same code for each one.
Let's say I have various controllers with the same method called search. Inside this method I have situations for the same term for different controllers, like:
case term
when "beginning_parens"
#books = Book.where("title REGEXP ? AND localeLanguage LIKE ?", '^\\([0-9]*\\)', params[:language]).not_locked.reorder(order).paginate(:page => params[:page])
when 'search_all_caps'
#books = Book.detect_uppercase.not_locked.reorder(order).where("localeLanguage LIKE ?", params[:language]).paginate(:page => params[:page])
Now I don't want to have to write all of that for each controller. And I'd like not to have to pass the params[:language], paginate, etc. to each of the cases since I want each one to search by language and paginate as well.
Or course, the instance variable would change among controllers, instead of #books we would have #dvds, #games, etc.
I also find myself having to pass all the params in the menu links:
= link_to 'with all CAPS', books_search_path(:term => 'search_all_caps', :language => params[:language])
And I'm sure there has to be a better way to do this.
I'm already sharing common methods for the models with a file I call at the top of each model with extend ModelFunctions. This file contains methods such as detect_uppercase which is used above and is common for many controllers. So at least I have that part working :)
Any help would be appreciated.
If you're going to be writing a bunch of controllers that do just about the same thing, it seems like inheritance might be the way to go here.
Make one controller (let's call it the BaseController) and give it all the methods you want the other controllers to have. Then each of your other controllers can inherit from BaseController and just override the methods you need them to and set class variables for things like which model to search, what instance variable to set, and so on. For example:
class BaseController < ActionController::Base
def search
##model.where("title REGEXP ? AND localelanguage ...")
end
end
class BooksController < BaseController
##model = Book
end
I understand that application_controller.rb is the place to put all the methods, etc that you would like made available in all your controllers since they all inherit from this class. Great.
But what is the equivalent for Models? In other words, I want a place where I can create a couple of super classes that my models will inherit from.
For example, I have a method that searches different tables for entries in all CAPS via REGEXP in Mysql. I'd like to be able to create the method only once and call it for different tables/models.
What is the Rails way of doing this?
I thought I could create a class that would inherit from ActiveRecord::Base (as all models do) , put the methods in there and then inherit all my models from that class. But thought there would surely be a better way to do it.
Thanks.
Edit
Per Semyon's answer I'm editing the post to show the routes I am using. It works now:
# models/dvd.rb
require 'ModelFunctions'
class Dvd < ActiveRecord::Base
extend ModelFunctions
...
end
# lib/ModelFunctions.rb
module ModelFunctions
def detect_uppercase(object)
case object
...
where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]]{5,}' ").not_locked.reorder("LENGTH(#{field}), #{table}.#{field} ASC")
end
end
In config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
Take a look at mixins, for example here:
http://ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
In a Rails app you could create a module in the lib directory that defines your methods and then include it in your models.
EDIT: To be specific for your example, you're trying to define a class method. You can do this in a mixin like this:
module Common
module ClassMethods
def detect_uppercase(object)
case object
when 'dvd'
field = 'title'
...
end
where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]] {5,}').not_locked.reorder('LENGTH(title), title ASC')"
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
Now when you include Common in your model, that model's class will be extended to include the new class methods, and you should be able to call Dvd.detect_uppercase.
Put the reusable method in some module next to your Dvd class. You can move it in a separate file later.
# app/models/dvd.rb
module CaseInsensitiveSearch
def case_insensitive_search(field, value)
# searching field for value goes here
end
end
class Dvd
end
After extending a class with the module you can use case_insensitive_search on the class. Including the module will make case_insensitive_search an instance method which is not what you want.
class Dvd
extend CaseInsensitiveSearch
end
Dvd.case_insensitive_search("title", "foo")
And of course you can use it inside Dvd class.
class Dvd
def self.search(query)
case_insensitive_search("title", query)
end
end
Dvd.search("foo")
Now when you made sure it works, you will probably want to move it in a separate file and use it across multiple classes. Place it in lib/case_insensitive_search.rb and make sure you have this line in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Now you can require it anywhere you want to use it:
require 'case_insensitive_search'
class Dvd
extend CaseInsensitiveSearch
end
The last thing I'd like to suggest. Create multiple modules with meaningful names. So instead of CommonModel have CaseInsensitiveSearch and so on.