Rails 4.x before_action with params? - ruby-on-rails

I am trying to make my rails code a bit nicer.
I have this:
class MyController < ApplicationController
before_action do
# #variable_defined_else_where is an object w/ accessors
#variable_defined_else_where.some_value = "string"
end
end
I would like to do this some how get to here:
class MyController < ApplicationController
variable_defined_else_where(some_value: "string")
# or
variable_defined_else_where.some_value = "string"
# or
some_method "string"
end
I looked at the rails actionview code, for "layout" which has a similar syntax-ness
class MyController < ApplicationController
layout "string"
end
However, it declares a method in the class, I need to modify a
#variable_defined_else_where
which then controls how several bits of helpers behave
module MyHelper
def do_if_that
if #variable_defined_else_where == "string"
# do so and so
end
end
end
Does anyone have any suggestions on how I can get to syntaxically happy-ness?

Since before_action executes in the instance context, your variable-in-question is an instance variable, which means it's only set on the instance of the controller (i.e., during the request lifecycle). On the other hand, layout is most likely setting a property on the controller class itself.
If your variable could be moved to the class-level without compromising thread-safety, you could make it a class attribute and set it directly like:
class MyController < ApplicationController
##my_variable = 3
def test
##my_variable # returns 3
end
end
But if you don't like how # signs look, maybe that's not better :)
Here's another option, which just wraps your before_action definition inside a class method:
module SetsSomeVariable
include ActiveSupport::Concern
module ClassMethods
def set_variable(value)
self.before_action do
#my_variable = value
end
end
end
end
# ...
class MyController < ApplicationController
include SetsSomeVariable # this could be in ApplicationController
set_variable 'string'
end

Related

Creating a global variable to all the controllers in Rails

I have a base URL common to all my controllers. I want to declare this as a variable in one place, and use it in all my controllers. That will make any future updates fast and simple. Is that possible? I'm declaring it in all my controllers like this:
#baseURL = "www.url.com/something/"
In your application controller.
before_action :set_variables
def set_variables
#baseURL = "www.url.com/something/"
end
This #baseURL instance variable will be accessible in all your actions and views as you make all controllers inherit the ApplicationController.
Typically, all your controllers will inherit from ApplicationController. You may then define the variable there, making it available to its descendants.
class ApplicationController < ActionController::Base
BASE_URL = "www.url.com/something/"
end
You could define a method inside ApplicationController and use that method like a helper_method to access it from the views.
class ApplicationController < ActionController::Base
helper_method :base_url
def base_url
#base_url ||= "www.url.com/something/"
end
end
I try to avoid before_actions to setup variables.
Inside your controllers and views you will able to call the base_url method.
It's the same to include this method in the application_helper.rb.
Rails controllers inherit from ApplicationController. Try putting it there:
def baseUrl
#baseURL = "www.url.com/something/"
end
You could define a class variable in your application controller:
class ApplicationController < ActionController::Base
##baseURL = "www.url.com/something/"
def self.baseURL
##baseURL
end
end
class SomeFrontendController < ApplicationController
end
Within all your controllers now you could access ##baseURL or call the class method:
SomeFrontendController.baseURL
# => "www.url.com/something/"
But this is dirty. Better use a constant:
class ApplicationController < ActionController::Base
BASE_URL = "www.url.com/something/"
end
class SomeFrontendController < ApplicationController
end
Within all your controllers now you could access BASE_URL or:
SomeFrontendController::BASE_URL
If it is only one variable and you are sure, that you need it only within controllers scope declaring a constant in ApplicationController should be enough:
class ApplicationController < ActionController::Base
BASE_URL = "www.url.com/something/"
end
class SomeOtherController < ApplicationController
def index
#base_url = BASE_URL
end
end
However usually custom URLs (and other things like email addresses) earlier or later are needed in other parts of an application so it is useful to have a single source of truth by using gem like https://github.com/settingslogic/settingslogic and store all such variables in one place (file).

How to execute an action from another controller without leaving the current controller

class UsersController < ApplicationController
def create
# call the action do_something from ImagesController
# continue in the normal flow
end
end
class ImagesController < ApplicationController
def do_something
...
end
end
I want to call the action do_something in the ImagesController from UsersController but after it is executed I want to continue in the normal flow of the create action, few questions:
Is it a bad practice?
How can I do that? Do I have to create an instance of the ImagesController and then call the action or is there another way?
You could technically create an instance of the other controller and call methods on that, but it is tedious, error prone and highly not recommended.
If that function is common to both controllers, you should probably have it in ApplicationController or another superclass controller of your creation.
class ApplicationController < ActionController::Base
def common_to_all_controllers
# some code
end
end
class SuperController < ApplicationController
def common_to_some_controllers
# some other code
end
end
class MyController < SuperController
# has access to common_to_all_controllers and common_to_some_controllers
end
class MyOtherController < ApplicationController
# has access to common_to_all_controllers only
end
another method is
# lib/common_stuff.rb
module CommonStuff
def common_thing
# code
end
end
# app/controllers/my_controller.rb
require 'common_stuff'
class MyController < ApplicationController
include CommonStuff
# has access to common_thing
end
source - Calling a method from another controller
this is possible actually. And not that hard.
That said. It is bad practice. Very bad practice.
What you want to do is extract the logic you do in the controller you want to invoke into a service object or move it into a model. Alternativly you could also make your first controller inherit from the one you are trying to invoke.
So, how to call a controller?
TheController.new.dispatch(:index, request)

Global variable for language_id available in views and in models

I'm trying to share a session variable in both the controllers, the views and the model.
With the following code, it is working in the controllers and in the views :
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :best_language_id
# Returns the ID of the language to use to display the description.
def best_language_id
#best_language_id ||= session[:best_language_id]
#best_language_id ||= current_project.default_language.id
return #best_language_id
end
end
But I can't call it from the model.
I would like to be able to call best_language_id either in the controllers, views and in one model, to get a fallback of the best_language_id if a translation is not found.
Example in my model (not working) :
class Point < ActiveRecord::Base
# Retuns the attached word in the given language if exists.
# Otherwise, falls back on another translation
def word(preffered_language_id)
word = Word.find(:translation_id => self.translation_id, :language_id => preffered_language_id)
if word.blank?
word = translations.where(:translation_id => self.translation_id, :language_id => best_language_id)
end
return word
end
end
I know that model should not include applicationcontroller method calls, but how is it possible to share my best_language_id accross controllers and model ?
Edit : using i18n is not the question here. Translations are not fixed string but variables in a database.
Thanks for helping !
In your rails app, you have a base module in config/application.rb. It should be named after your application. Let's say its called MyApp. What you could do is define two methods like this:
module MyApp
...
def self.language_id=(value)
#language_id = value
end
def self.language_id
#language_id ||= 'en' # default vaule
end
...
end
Then, in app/controllers/application_controller.rb add a before_filter like this:
before_filter :language
def language
MyApp.language_id = session[:language_id] if session[:language_id]
end
Then, from all over the app, you can access the value via
MyApp.language_id
Needless to say that the approach is not thread safe so don't use it in a threaded environment.
I would suggest you switch the situation around, store the best_language_id in the model as a class accessor, then you can set and get it from your controllers and it will still be available in the models.
class Point < ActiveRecord::Base
cattr_accessor :best_language_id # to store the variable
end
# Persist the content of that variable at the start of every action
class ApplicationController < ActionController::Base
before_filter :set_best_language
def set_best_language
Point.best_language_id = session[:best_language_id]
Point.best_language_id ||= current_project.default_language.id
end
end
# Use the variable in a controller
class SomeOtherController < ActionController::Base
def show
#best_language = Language.find(Point.best_language_id)
...
end
end
# Use the variable in a model
class SomeOtherController < ActiveRecord::Base
def some_method
best_language = Language.find(Point.best_language_id)
...
end
end

How can I extend ApplicationController in a gem?

I thought I'd come up with a slick way to extend ApplicationController in a Rails 3.x gem.
In my gem's lib/my_namespace/my_controller.rb, I had:
class MyNamespace::MyController < ApplicationController
before_filter :some_method
after_filter :another_method
def initialize
# getting classname of the subclass to use for lookup of the associated model, etc.
# and storing the model_class in an instance variable
# ...
end
# define :some_method, :another_method, etc.
# ...
private
attr_accessor :subclass_defined_during_initialize # etc.
# etc.
end
but when the Gem is loaded, app/controllers/application_controller.rb is not yet loaded, so it fails:
/path/to/rvm/gemset/gems/activesupport-3.2.6/lib/active_support/dependencies.rb:251:
in `require': cannot load such file -- my_gem_name/application_controller (LoadError)
As a workaround, I had defined ApplicationController in my gem's lib/gem_namespace/application_controller.rb as:
class ApplicationController < ActionController::Base
end
I assumed that even though I had defined it there, it would be redefined in my Rails 3 application's app/controllers/application_controller.rb, such that both controllers in the application that extended ApplicationController and controllers that extended MyNamespace::MyController would directly or indirectly extend the ApplicationController defined in app/controllers/application_controller.rb.
However, we noticed that after loading the gem, controllers that extend ApplicationController were unable to access methods defined in app/controllers/application_controller.rb. Also, the ApplicationHelper (app/helpers/application_helper.rb) module was no longer being loaded by other helper modules.
How can I extend ApplicationController within the controller in my gem for the purpose of defining a before_filter and after_filter to and use initialize to access the class's name to determine the associated model's class that it could then store and use within its methods?
Update 2012/10/22:
Here's what I came up with:
In lib/your_gem_name/railtie.rb:
module YourGemsModuleName
class Railtie < Rails::Railtie
initializer "your_gem_name.action_controller" do
ActiveSupport.on_load(:action_controller) do
puts "Extending #{self} with YourGemsModuleName::Controller"
# ActionController::Base gets a method that allows controllers to include the new behavior
include YourGemsModuleName::Controller # ActiveSupport::Concern
end
end
end
and in lib/your_gem_name/controller.rb:
module YourGemsModuleName
module Controller
extend ActiveSupport::Concern
# note: don't specify included or ClassMethods if unused
included do
# anything you would want to do in every controller, for example: add a class attribute
class_attribute :class_attribute_available_on_every_controller, instance_writer: false
end
module ClassMethods
# notice: no self.method_name here, because this is being extended because ActiveSupport::Concern was extended
def make_this_controller_fantastic
before_filter :some_instance_method_available_on_every_controller # to be available on every controller
after_filter :another_instance_method_available_on_every_controller # to be available on every controller
include FantasticStuff
end
end
# instance methods to go on every controller go here
def some_instance_method_available_on_every_controller
puts "a method available on every controller!"
end
def another_instance_method_available_on_every_controller
puts "another method available on every controller!"
end
module FantasticStuff
extend ActiveSupport::Concern
# note: don't specify included or ClassMethods if unused
included do
class_attribute :class_attribute_only_available_on_fantastic_controllers, instance_writer: false
end
module ClassMethods
# class methods available only if make_this_controller_fantastic is specified in the controller
def some_fanastic_class_method
put "a fantastic class method!"
end
end
# instance methods available only if make_this_controller_fantastic is specified in the controller
def some_fantastic_instance_method
puts "a fantastic instance method!"
end
def another_fantastic_instance_method
puts "another fantastic instance method!"
end
end
end
end
For this specific kind of functionality I would recommend creating a module in your gem and include that module in your Application Controller
class ApplicationController < ActionController::Base
include MyCoolModule
end
To add before filters, etc (add this to your module)
def self.included(base)
base.send(:before_filter, my_method)
end
Update: you may be able to just do base.before_filter :my_method which is cleaner.
Here is a Gist
that shows how to access the class of the subclass and store it in an instance variable and access it in the before and after filters. It uses the include method.
Truth is much much simpler and flexible.
Add to lib/engine.rb this: class Engine < Rails::Engine; end
And then simply use:
ActionController::Base.class_eval do
include SomethingFromMineGemModule
# or:
def hello_from_gem
'Hey people!'
end
end
I was able to reference ApplicationController with an initializer callback.
gem code that subclasses/references ApplicationController:
class GemApplicationController < ApplicationController
before_filter :method_to_call
def method_to_call
#your code here
end
end
gem code callback to create subclassed controller:
module GemName
def self.load_gem_application_controller
require "path/to/gem_application_controller"
end
end
rails_app/config/initializers/gem_name.rb
GemName.load_gem_application_controller
Then have controllers that use this functionality subclass GemApplicationController
class SpecialCaseController < GemApplicationController
# this will inherit from the gem's controller,
# which inherits from the rails_app ApplicationController
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