Does rails have a break statement?
I'm writing a controller that has some pretty complicated IF statements. In Sum what I'd like to do is something like this:
IF !All these variable exist and are not nil?
BREAK and don't continue bec something went wrong
END
If XXX = 'adsasd
do all this stuff
ELSE IF
ELSE
send out an error email
Is a break possible?
I don't know when all of your variables become available, but when I want to do checks in the controller, I usually use the before_filter callbacks to do that. For example:
class YourController
before_filter :check_if_variables_exist
def show
#prepare for render view
end
private
def check_if_variables_exist
unless #your_variable.nil?
#Do what you want
render :nothing => true
end
end
What this does is that when a request comes to action show in YourController, it will first call the private method check_if_variables_exist. If #your_variable is nil than it will end up at render :nothing => true and the filter_chain will be halted and the action show will never be called. If however your_variable is not nil, then the method will end without doing anything and the controller will then call the action show like usual.
So all the things you want to check beforehand can be placed in different before_filter callbacks. And if you don't want the check for all actions in the Controller, it can be specified like this:
class YourController
before_filter :first_check, :only => [:index, :show]
before_filter :second_check, :only => [:show, :edit]
In my opinion, this is the "rails" way to do it.
Ruby's return statement is what you're looking for. However...
I'm writing a controller that has some pretty complicated IF statements
Without knowing anything else about your application, this is a BIG red flag. Controllers are not the place to have a bunch of complicated logic. If you're using RESTful design patterns (and there's rarely a reason not to), then your controllers should be very lightweight and contain almost no logic. Models are where your business logic belongs. They allow you to isolate logic, simplifying your application and making it easier to test.
Related
I looked at the following questions:
Are multiple before_action calls bad code style?
Api Dock - before_filter
And I think there was something about creating a controller superclass, but I am not familiar with such.
The reason I need to do this is because, we have a bunch of setup views, and those views are renering a seperate layout. setup_screen
So instead of doing
def setup_step_1
render: layout => 'setup_screen'
end
def setup_step_2
render: layout => 'setup_screen'
end
def setup_step_3
render: layout => 'setup_screen'
end
We created the following:
before_action :setup_layout, only: %i[ setup_step_1 setup_step_2
setup_step_3]
The problem is, we want to run some extra logic in the setup_screen's. But we can't add another before_filter. It's also not possible to add the logic in the def setup_screen because of the case that not ALL setup_screen's will need it.
As stated in those answers, there is no objection to using multiple before-filters. So you can easily add multiple before_action. E.g. a typical example
before_action :authenticate_user!
before_action :get_post, only: [:show, :edit, ...]
In your case for the render I would use a after_action, since the render is the last action. Then you could add your conditionial code in each separate action, or an extra before_action.
An alternative method, which I would prefer in your case is something like the following:
def setup_step_1
setup_step(1)
end
def setup_step_2
setup_step(2)
end
def setup_step_3
setup_step(3)
end
protected
def setup_step(step)
if step == 1
# .. do something for step 1
elsif step == 2
else
end
render: layout => 'setup_screen'
end
I would prefer this approach since it is a little more expressive/explicit. Looking at the methods it is clear they share the same core. Personally I prefer to use before_action for setting up pre-conditions: e.g. authentication, authorization, fetching the data if very simple.
But as usual in programming, there are a lot of roads leading to a working application and sometimes it is just a matter of taste which you prefer.
I would know how the load_and_authorize_resource works inside.
I searched the github page Link and tried to undestand , but i didn't find nothing usefull. I only understand that load_and_authorize_resource is like a before_filter and it loads (in some way) the ability that we have written in ability.rb
I would know better how this is possible. I mean, i don't want to study ALL the gem, but i want just to see how cancan load the ability of a resource in a controller and if the load_and_authorize_resource is really a sort of before_filter.
disclaimer: for the sake of simplicity, I omit some calls to short inner methods intentionally. The full chain of calling can be obtained by following load_and_authorize_resource method definition and so forth.
As stated in documentation, load_and_authorize_resource sets up a before_filter...
# cancan/lib/cancan/controller_additions.rb
def load_and_authorize_resource(*args)
cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
end
...which calls two methods: load_resource and authorize_resource.
# cancan/lib/cancan/controller_resource.rb
def load_and_authorize_resource
load_resource
authorize_resource
end
To get the idea of their behaviour we're going to look at both of them closely.
Based on params hash which was passed to your controller action, load_resource makes a decision on whether it should obtain a new instance of a class (e.g. Post.new) or find a particular instance based on params[:id] (e.g. Post.find(params[:id])). That instance (or a collection of instances for actions like index) is assigned to corresponding instance variable of your controller action.
# cancan/lib/cancan/controller_resource.rb
def load_resource
unless skip?(:load)
if load_instance?
# here you have obtained your object, e.g. Post with id=5
# and placed it into cancan resource_instance variable.
# it has automatically set up #post instance variable for you
# in your action
self.resource_instance ||= load_resource_instance
elsif load_collection?
self.collection_instance ||= load_collection
end
end
end
Later on, authorize_resource gets called. Its inner logics syntax should be familiar to you: checking abilities by hands looks just the same as what happens inside of this method. Basically you take a resource_instance obtained at the previous step, params[:action] which is the name of a current action, and check if particular action can be accessed for given object(s).
# cancan/lib/cancan/controller_resource.rb
def authorize_resource
unless skip?(:authorize)
# similar to what happens when you call authorize!(:show, #post)
#controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
end
end
As long as raising exceptions inside of before_filter stops controller action from being executed, failing to pass authorization here gets you redirected to your application's home url, shown 500 error page or whatever behaviour you defined for CanCan::AccessDenied handling.
On the other hand, in case you've passed authorization successfully, your action code gets executed. Now you've got access to instance variable (e.g. #post) which has been set up by CanCan at load_resource step.
If I am going about this wrong please let me know I can change it. I have a file in config/initializers/payload_signer.rb. I am trying to use this file in the controller that is called device_enrollment_controller.rb.
PayloadSigner.sign(get_profile)
get_profile is a method in the controller that gets the file I need and returns it. PayloadSigner references the other file. When I try to run this (keeping in mind im sure changes will have to be made in payload_signer for it work right) the error I get is uninitialized constant DeviceEnrollmentController::PayloadSigner. This leads me to believe I am referencing the payload_signer.rb file incorrectly. I have tried things like include and load but so far they are not working.
Any help or guidance is appreciated.
Rails Initializers are called before Controllers or Models. So it won't work. Initializers are not intended for this kind of use. Instead I suggest placing your code in a controller before_filter. Either in the ApplicationController or only in those controllers that require it (e.g. DeviceEnrollmentController). Something like this:
class DeviceEnrollmentController # Or ApplicationController
before_filter :sign_payload
protected
def get_profile
# Magic
end
def sign_payload
PayloadSigner.sign(get_profile)
end
end
EDIT: Another example:
class DeviceEnrollmentController
# The filter is only applied to the sign action
# (that's what the :only parameter does).
before_filter :sign_payload, :only => [:sign]
# Browsing to /show, you render this magic button of yours.
def show
# Render page that holds the button
end
# The magic button is bound to the /sign route.
# Clicking on the button calls this action.
def sign
# When you get here, the #sign_payload method
# has already been called.
end
protected
def get_profile
# Magic
end
def sign_payload
PayloadSigner.sign(get_profile)
end
end
I inherited this code but can't get my head around this part:
I have the following controller:
class MenuCategoriesController < ApplicationController
load_and_authorize_resource
after_filter lambda { |controller| controller.update_other_locals_for(#menu_category) }, :only => [:create, :update]
# GET /menu_categories
# GET /menu_categories.json
def index
...
In the application_controller is this:
def update_other_locals_for(item)
available_locals = item.menu.shop.chosen_locals
...
Normally update_other_locals_for should be hit when an item is updated or created. I set a breakpoint and this never seems to happen. Does this need a lambda expression? What could be the reason why it's not hit after creation or update?
When something in Rails isn't happening like you think it should in a controller, remember to check that:
you routes are properly defined
your abilities are correct
To start with you can check that you hit breakpoints set in the create and update actions for MenuCategoriesController. If your actions are not being called, then of course you won't see the after_filter.
If you suspect the lambda, try replacing it with a method and verify that the method gets called.
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.