This should be a very simple rails question. I have a variable like the following.
#administration = Administration.first
I want this variable to be accessible through every controller action across all my controllers so for example if I have a Product controller and inside of it I have the usual CRUD actions, I want the #administration variable as defined above to be put into all the CRUD actions. (It would not be needed in destroy or create or update). I have many controllers throughout my project and I was wondering if there is an easier way than adding it manually through all of the actions that I want it in.
I tried a global variable
$administration = Administration.first
but I run into an issue where it is not updated when I update the actual content of the Administration.first table. Also, I would like to avoid global variables.
Any help would be much appreciated. Thanks! :)
You could add a before_filter to your ApplicationController that sets the administration variable before any action is called and you can limit it to only the actions you require.
class ApplicationController < ActionController::Base
...
before_filter :set_admin
def set_admin
#administration = Administration.first
end
..
http://api.rubyonrails.org/v2.3.8/classes/ActionController/Filters/ClassMethods.html
Just extending Christos post...
If you don't want #administration to be accessible to destroy, create and update controller actions then add :except => :action to before_filter like this:
before_filter :set_admin, :except => [:create, :update, :destroy]
On Rails 4 and 5 before_filter it's deprecated. You can use this instead:
before_action :set_admin, except: [:create, :update, :destroy]
Related
When using concerns in Rails controllers how do filters added via before_action operate? Specifically, if I have the following code for which actions in FeedsController will the before_action set_record run? All of them once (show, update, destroy, foobar), all of them once and twice before destroy or only before destroy, foobar (I presume authenticate still runs only before destroy)?
module JsonApiController
extend ActiveSupport::Concern
included do
before_action :authenticate, only %i[ destroy ]
before_action :set_record, only: %i[ show update destroy ]
end
def show/update/destroy
end
protected
def set_record
#record = controller_path.classify.constantize.find(params[:id])
end
end
class FeedsController < ApplicationController
include JsonApiController
before_action :set_record, only: %i[destroy, foobar]
def foobar
...
end
end
I'd like FeedsController to be able to add the set_record filter before any actions it wants without having to know what the JsonApiController did. So, ideally, the filter :set_record would execute once before each of show/update/destroy/foobar but I don't think that's what the code below accomplishes. But more than any particular solution I want to know how before_action works with concerns and included do so I minimize code duplication between concerns and classes.
If this was just inheritance then I know that before_actions are inherited. But this discussion suggests that a before_action in an included do in a module will be overwritten by one in the class but when I try to look at the source it suggests that append_callback is the default action so I'm confused.
From the Rails Guides: Action Controller Overview/Filters:
Calling the same filter multiple times with different options will not work, since the last filter definition will overwrite the previous ones.
That said if you want to change or update the configuration of a before_action then that new config will override all existing filters with the same method name.
Therefore, in your example, you will need to use the following new declaration to extend (actually override) the existing declaration from the included module:
before_action :set_record, only: %i[show update destroy foobar]
for example:
In controller file,what's difference between
append_before_action :calculate, only: :show
and before_action :calculate, only: :show ?
None. append_before_action is the same of before_action. Here's the code.
append_ exists for consistency with prepend_. For {before,after,around} callbacks you have
{when}_action
append_{when}_action, prepend_{when}_action
skip_{when}_action
Generally, you should use before_action, unless you really need to append/prepend specifically.
If I'm not mistaken, before_action will be executed in the order in which it's defined. If you extend a controller, any new before_actions will happen after the parent controller's before_action methods are executed. If you don't want this to happen, prepend_before_action will be necessary to throw your method on top of the stack.
Playing around with Rails 4, I noticed that it defines a before_action filter set_[model], that is called for the actions show, edit, update and destroy.
It is in fact very useful, but I don't see much sense in not having it to all member actions. After all, if you are using a member action, you want to act on that member, thus you need to recover it from database at some point.
Note that by member actions, I also means the ones configured on routes.rb in the members block.
Is there a straight-forward way to do this, without list all member actions on the before_action filter?
Edit: To clarify, the whole point is use some rails magic to get all member routes and generate the array that would be pass in the :only. So I can do something like
before_action set_model only: all_member_routes
where all_member_routes is a piece of code that returns all member routes for my model.
Or even better,
before_action set_model, only_member_actions: true
The show, edit, update, destroy actions are precisely all member actions. They are the only ones that require to find the model.
If you have your own member action, then you'll have to add it to the list yourself.
before_action :set_item, only: [:edit, ..., :my_member_action]
Or, you can use the :except option to exclude all the collection actions that do not need it:
before_action :set_item, except: [:index, :create]
This way, if you add other member actions, you wont have to change anything.
Personally, I prefer to be explicit and use :only.
I'm pretty sure there is no easier way to do it, you can't detect all member actions automatically.
edit:
I really don't think you should do that but...
You can access the name of your controller with the controller_name method.
Getting the routes related to the controller:
routes = Rails.application.routes.routes.select { |r| r.defaults[:controller] == controller_name }
Then, I think the best way to see if a route is a member route is that the #parts array includes :id. Maybe you can find a more robust way.
So I would do:
routes.select { |r| r.parts.include?(:id) }.map { |r| r.defaults[:action] }.map &:to_sym
That would give you: [:show, :preview, :my_challenges] for
resources :users, only: [:index, :show], controller: 'accounts/users' do
member do
get :preview
get :my_challenges
end
end
class ApplicationController < ActionController::Base
def member_routes
Rails.application.routes.routes
.select { |r| r.defaults[:controller] == controller_name && r.parts.include?(:id) }
.map { |r| r.defaults[:action] }
.map(&:to_sym)
end
end
class UsersController < ApplicationController
before_action set_model, only: member_routes
end
If you ask for a before_action without :only or :except options, it will apply to all member actions:
class ApplicationController < ActionController::Base
before_action :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
end
In this particular case, it will require login from all actions on all controllers, since controllers will inherit from ApplicationController.
You can skip a before_action if you need it (for example, you need to skip require_login if you want to login into the system or sign up) like this:
class LoginsController < ApplicationController
skip_before_action :require_login, only: [:new, :create]
end
Source: Rails Guides
So, in your particular case:
You could have one usual UserController:
class UserController < ApplicationController
def index
end
...
/* you define here `index`, `create`, `update`, ... */
def destroy
...
end
end
And you could have a separate controller with all your member actions:
class UserCustomController < ApplicationController
before_action :set_model
def profile
...
end
def preview
end
def custom_member_action
end
...
/* all your member actions */
end
This would be actually better than having a single controller with lots of methods.
I am trying to create a generic controller in Rails, to approach a similar functionality than the one offered in class-based views in Django. It would mean that just by making a subclass of this controller you would acquired all the methods generated by default with scaffold.
The code generated by scaffold in Rails is equal except for the name of the resource. Given an small example and based on the code generated by scaffold:
class ResourceController < ApplicationController
before_action :set_#{resource_under_scored}, only: [:show, :edit, :update, :destroy]
def index
##{resource_under_score_pluralized} = #{resource_model}.all
end
...
Given that definition I could write the following controller for resource big shoe:
class BigShoesController < ResourceController
end
Which would imply in the following code auto generated at runtime:
class BigShoesController < ApplicationController
before_action :set_big_shoe, only: [:show, :edit, :update, :destroy]
def index
#big_shoes = BigShoe.all
end
I still need to learn a lot of metaprogramming to achieve this but I though that there are more people who has wanted to acquired the same result. But I have not found the proper question.
How would you accomplish this ? I am looking for a way to implement the class, to see how it would be made to generated code based on variables. It is preferred this answer than a gem which will make the work.
It seems that you want InheritedResources: https://github.com/josevalim/inherited_resources.
It implements all the basic CRUD stuff. Example:
class ProjectsController < InheritedResources::Base
end
This controller has index, show, create, update and other methods, implemented in a standard manner. There are some possibilities for customization, see the readme file.
In application_controller.rb I have the following:
before_filter :get_events
before_filter :get_sitemap
def login_required
return true if session[:user]
# If we get to this point, I want to avoid running get_events and get_sitemap
flash[:warning] = 'Please login to continue'
redirect_to login_users_url
return false
end
And in other controllers I have for example:
before_filter :login_required, :except => [:show]
So essentially I have methods running that access the database for all pages except I don't want it to happen when the user is required to log in (i.e., being redirected from a page where normally get_events and get_sitemap are required).
I realize one way of doing this is to run login_required as a before_filter on ALL controllers before any other before_filters are set and then exclude certain controller models but I'd like to know if there's a way of doing it without having to change all my controllers.
For something like this, I usually create an AuthenticatedController (in app/controllers/authenticated_controller.rb):
class AuthenticatedController < ApplicationController
before_filter :require_login
end
Then I derive all controllers requiring authentication from that, specifying a skip_before_filter :require_login for actions I want to exclude on a controller-by-controller basis. For example:
class PlanetsController < AuthenticatedController
skip_before_filter :require_login, only: [:index]
end
This will require you to change all controllers, but only for the purpose of deriving from AuthenticatedController. As far as I know this is a perfectly acceptable way of handling it, and I don't think there's a way outside of monkey patching ActionController::Base to make this apply to all controllers, which is a pretty bad idea for a variety of reasons.
Well after considering Joshua's answer I still couldn't get what I wanted to go for...so what I did was do a little hybrid solution. All controllers still reference ApplicationController, but within ApplicationController I always run the before_filter :login_required FIRST before other before_filters. Redirects in rails seem to stop other before_filters from running, which is what I wanted. In the other controllers I use the skip_before_filter :login_required, :only => [:this_time] wherever necessary.