Rails Controller knowing which action brought me here - ruby-on-rails

The scenario is this:
I have 2 Model-View-Controllers , A and B.
In A's index.html.erb, I have a link to B's new.html.erb, that looks like /a/1/b/new. B can also be displayed by doing /b/new.
In B's new method is there a way to tell I got there from a?
I need to do if came from A, do some, otherwise don't.
Thanks

You can use the refer
refer_hash = Rails.application.routes.recognize_path(request.referrer)
now you can check the previous controller by
refer_hash[:controller]
and action by
refer_hash[:action]
So in the end you will have code like
refer_hash = Rails.application.routes.recognize_path(request.referrer)
if refer_hash[:action] == 'index' && refer_hash[:controller] == 'A'
# Do something
else
# Do something else
end

If your action is processing the request with A resource, you should be able to retrieve the a_id in the params hash. Here is the method I often use in my app:
class BController < ApplicationController
def new
if params[:a_id].present?
#do something with A here
else
#do something otherwise
end
end
end
If you want to be sure, run rake routes in your console to see how the requests look like. I imagine you would see a route like this:
a/:a_id/b/new
and another route like this
/b/new

FYI
If you'd like to know the name of your controller & action, you can use the two Rails helpers called action_name (now action it seems) and controller_name
Whether this will help you directly, I'm not sure
--
Referral
In B's new method is there a way to tell I got there from a?
I don't know if this will solve your issue as well as #sonnyhe2002's answer, but if you're requesting B from a nested resource, you could play with the params hash to achieve the functionality you desire
If your routes are like this:
#config/routes.rb
resources :a do
resources :b #-> domain.com/a/:a_id/b/new
end
This will mean if you render the b#new action as part of your nested route, you will have the variable params[:a_id] available, which means you'll be able to test if it's there in your b controller action:
#app/controllers/b_controller.rb
def new
if params[:a_id]
# logic
else
# logic
end
end
It's a different way of looking at it

Related

Rails 5 routing: Dynamic controller

Is it possible for the route below to dynamically select different controllers or at least for a single controller to dynamically call another controller?
get '*path' => 'routing#show
For example:
/name-of-a-person => persons#show
/name-of-a-place => places#show
I recall reading something about Rails 5 that would enable this but I can't find it again to save my life. It's possible I imagined it.
Another options is to have a RoutingController that depending on which path is received will call different controllers.
The use case is I have URLs in the database with a type, and the controller depends on what type is the URL. I'm thinking something like this:
get '*path' do |params|
url = Url.find_by!(path: params[:path])
case url.type
when 'person'
'persons#show'
when 'place'
'places#show'
end
end
I post my second best solution so far; still waiting to see if anyone knows how to do this efficiently within the routes.
class RoutingController < ApplicationController
def show
url = Url.find_by!(path: params[:path])
url.controller_class.dispatch('show', request, response)
end
end
Hat tip to André for the idea.
You could define one controller and inside its action make something like this:
def generic_show
url = Url.find_by!(path: params[:path])
case url.type
when 'person'
controller = PersonController.new
controller.request = request
controller.response = response
controller.show
when 'place'
...
end
end
However, I would recommend you to move the code you want to reuse to other classes and use them in both controllers. It should be easier to understand and maintain.
I think you may be able to do it using advanced routing constraints.
From: http://guides.rubyonrails.org/routing.html#advanced-constraints
If you have a more advanced constraint, you can provide an object that responds to matches? that Rails should use. Let's say you wanted to route all users on a blacklist to the BlacklistController. You could do:
class BlacklistConstraint
def initialize
#ips = Blacklist.retrieve_ips
end
def matches?(request)
#ips.include?(request.remote_ip)
end
end
Rails.application.routes.draw do
get '*path', to: 'blacklist#index',
constraints: BlacklistConstraint.new
end
I don't think the Rails guide example is particularly good, because this problem could essentially be solved in your application controllers before_action.
In this example, the constraint is used for IP filtering, but you could also implement matches? to check if it's a person. I would imagine something like
def matches?(request)
Person.where(slug: request.params[:path]).any?
end
And as such, the Rails router can decide whether or not to dispatch the request to the persons#show action.

Params and selecting the right one

I've got this helper method in my application controller:
def current_team
#current_team ||= Team.find(params[:team_id])
end
Problem is, it works for urls of the format:
/teams/20/members/11
but it doesn't work for:
/teams/20
In order to get it to work for those, I have to change :team_id to be :id.
How can I tidy it up so it 'just works'?
Thanks!
Set instance variables (#current_team) in controllers, never in helpers. It's not what helpers are for.
If you follow this advice, you will naturally use params[:id] in TeamsController, but params[:team_id] in MembersController.
(Some people even go on to say that you shouldn't use helpers at all. For facilitating presentation (custom links, buttons, tables, etc), they propose to use Presenter pattern. But you don't have to listen to them. :))
It is not the best thing to do, but to accomplish that you can do the following:
def current_team
#current_team ||= Team.find(params[:team_id].presence || params[:id])
end
Documentation about the Object.presence method:
http://api.rubyonrails.org/classes/Object.html#method-i-presence
#SergioTulentsev is right, you shall not set instance variables in helpers, only in controllers.
I'm assuming you have other resources besides just Team. Rails is going to use the :id param for all of your resources. You will need to look into customizing the routes for your teams#show action. Easier in Rails 4 than in Rails 3.
Have a look at this post for the gory details: Change the name of the :id parameter in Routing resources for Rails
I wouldn't do params[:team_id] || params[:id], because of course in some controller contexts you'd get an id parameter that represents the id for something other than a Team. Assuming that the /teams/:id route is handled by the TeamsController, then you could do the following (to keep your method in ApplicationController and avoid repeating yourself in different controllers):
def current_team
id = controller_name == "teams" ? params[:id] : params[:team_id]
#current_team ||= Team.find(id)
end
Alternatively, you could change your routes so that the url to show a Team is /teams/:team_id and leave your helper as-is, but that would go against the grain of Rails routing conventions.

How to dynamically add a route to a scoped resource in Rails3?

right now I am trying to generalize some of my code. So far it went well, I wrote a few mixins which I can dynamically add to Controllers or Models in order to get things done while obeying DRY.
But with my "Searchform-Helper" I hit a corner in which, right now, I am a bit clueless.
I have a mixin 'SearchIndexController' which adds the methods needed to search for data within a searchindex-table.
After including the mixin I can initialize search-actions within the according controller calling this method:
def init_searchaction(object, name=nil)
singular = object.to_s.classify
plural = singular.pluralize
name = "search_#{singular}".to_sym if name.nil?
unless self.respond_to?(name)
define_method(name) do
# init
success=false
#TODO
# >>> DRAW NEW ROUTE TO THIS ACTION <<<
# evaluate searchform input for Searchindex-Call
needle = params[:query]
success, x, notice = execute_search("#{singular}", needle)
# send selected/filtered data to page
respond_to do |format|
format.js {
render :update do |page|
page.call "sidx_updateSearchResultContentAtIdTag", "##{plural.downcase} tbody", "#{render x}" if success
page.call "sidx_updateNotice", success, "#{notice}"
page.call "sidx_stopSpinner"
end
}
end
end
else
logger.warn("#{__FILE__}:#{__LINE__}:#{self.to_s}: search-action for '#{self.class.name}' can not be created, it already exists!")
end
end
So lets say I have a User-Controller. Within the Userform I have the need to search for several objects. Lets assume I want to be able to search for users, departments and clients... with my mixin I'd just have to initialize the searchactions like this:
init_searchaction :user
init_searchaction :department
init_searchaction :client, :find_clients
these would create actions within the including controller that are called
search_user
search_department
find_clients
The only thing missing is a way to get a route for them. I don't want to have to define the route upfront. I just want to 'init_searchaction' and have the mixin create the necessary route.
So... would it be possible to add the route to the accoring search-action from withing the mixins init_searchaction method dynamically? I think the necessary code would be placed at the #TODO mark in the code example above. But I still haven't found out how to do it... I mean, actually I would be surprised if it would not be possible.
Would anyone have an idea as how to do this? Thanks in advance for any idea that leads to the solution!
You can add work around standart dynamic route
match ':controller(/:action(/:id(.:format)))'
change it to your goals and enjoy :)

Prevent controller action from direct execution by user in Rails

I have a situation where i need to prevent users from explicitly calling say /town/addBuilding. Town is my controller and addBuilding is the action that is executed.
Now, the thing is that this action should only be executed in my program's code and not by a user requesting to execute it. Moreover, this action is executed like a callback. In my application_controller, when some condition is met, the controller action is triggered and there is a redirection. In php, a simple guard like defining a guard and checking against it would be enough. Is there an equivalent thing in rails and if so, what is the best way to implement it ?
Thanx for reading and i appreciate your help :)
EDIT: I'm pasting some code to make it clearer, note that /town/addBuilding was an example, the controller names and actions below are differently named.
Now, that is the actual application controller code, it is part of a browser game that i'm coding.
def checkQuest
if TavernQuest.hasQuest(current_user)
quest = TavernQuest.getQuest(current_user)
if quest.end_time < Time.now # get quest info and check if the quest has been completed
TavernQuest.deleteQuest(current_user)
redirect_to :controller => 'tavern', :action => 'monsterAttack'
end
end
end
The tavern controller action is just the plain code that i want to execute, but only if the redirection happens inside the application controller.
It seems that you are trying to put logic into a controller which actually should belong in a model or a library.
Why do i say this: aside from the current_user and the redirect, all the code is more related to your model (where the knowledge should be) and not your controller. Your model knows when a user's quest is expired.
Example implementation:
class TavernQuest
def self.user_quest_is_expired?(user)
quest = getQuest(current_user)
if quest && quest.end_time < Time.now
TavernQuest.deleteQuest(current_user)
true
else
false
end
end
end
and in your controller you just need to write
redirect_to :controller => 'tavern', :action => 'monsterAttack' if TavernQuest.user_quest_is_expired?(current_user)
Put the addBuilding method under a line that starts with protected, as follows
protected
def addBuilding
#your code
end
Enjoy!
EDIT: In addition to this you might also wanna use the before_filter in your controllers... I'll post the exact syntax soon.
before_filter :addBuilding, :only => :method_name
method_name is the method from which :addBuilding can be accessed, no other method can access this method after adding in this line..
EDIT: Ok, so based on the info you provided, protected wont work since if we put your secret action under protected only the tavern controller will have access to it.
EDIT: Please consider using Sessions to check if the users have a valid session when they try to to execute the monsterAttack action..

What's the correct way to run one controller action from another controller action without an HTTP redirect?

I'd like to be able to dispatch from one controller action to another conditionally, based on a combination of query parameters and data in the database.
What I have right now is something like:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_params = params.dup
new_params[:controller] = "new_controller_action"
redirect_to new_params
return
end
# rest of old and busted
end
end
class NewController < ApplicationController
def new_controller_action
# new hotness
end
end
This works just fine, but it issues an HTTP redirect, which is slow. I'd like to be able to do this same thing, but within the same HTTP request.
Is there a clean way to do this?
Edit: The bounty will go to someone who can show me a clean way to do this that leaves the controllers and their actions relatively untouched (other than the redirect code itself).
Instead of calling code across actions, extract the code to lib/ or something, and call that code from both controllers.
# lib/foo.rb
module Foo
def self.bar
# ...
end
end
# posts_controller
def index
Foo.bar
end
# things_controller
def index
Foo.bar
end
Create an instance of the controller class:
#my_other_controller = MyOtherController.new
Then call methods on it:
#my_other_controller.some_method(params[:id])
I prefer the module idea, but this should do the trick.
You can also pass parameters as a whole from another controller:
#my_other_controller.params = params
I suspect you want option 3, but lets go through the some alternatives first
Option 1 - Push the controller selection logic into a helper that inserts the right link into your view. Benifits - controllers remain clean, Cons - if decision logic depending on submitted values this approach won't work. If URL is being called by external websites then this won't work.
Option 2 - Push the logic back into your model. Pro's - keeps controller clean. Cons - doesn't work well if you've got lots of sesson, params or render / redirect_to interaction.
Option 3 - Stay within the same controller. I suspect you are trying to replace some existing functionality with some new functionality, but only in some cases. Pro's - Simple and have access to everything you need. Cons - only works if it makes sense to use the same controller i.e. you're working with the same entity such as user, place or company.
Lets look an an example for option 3. My links controller has totally diferent behavour for admins than other users ...
class LinksController < ApplicationController
#...
def new
#Check params and db values to make a choice here
admin? ? new_admin : new_user
end
#...
private
def new_admin
#All of the good stuff - can use params, flash, etc
render :action => 'new_admin'
end
def new_user
#All of the good stuff - can use params, flash, etc
render :action => 'new_user'
end
end
If two controllers are trying to do the same thing, there's a very good chance this should be in a model. Take a good look at your design and -- I'm sorry I don't know your experience level with MVC -- read up on thin controller techniques:
http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
http://www.robbyonrails.com/articles/2007/06/19/put-your-controllers-on-a-diet-already
http://andrzejonsoftware.blogspot.com/2008/07/mvc-how-to-write-controllers.html
If the problem is that you need the other controller to do the render, then maybe the route should have pointed there to begin with, and still the skinny controller technique should save the day.
If extracting the common code between controllers into a module doesn't work for you, I would use Rack middleware. I haven't seen code that uses ActiveRecord within middleware but I don't know of any reason why it shouldn't be possible since people have used Redis and the like.
Otherwise I think your only option would be to restart processing of the request with something like (untested, pseudo example):
env['REQUEST_URI'] = new_controller_uri_with_your_params
call(env)
This is similar to how integration tests are implemented. But I don't know if everything from call until you hit a controller is idempotent and safe to rerun like this. You could trace through the source and see. But even if it's ok now, it might break in any future version of rails or rack.
Using middleware would avoid this by letting you intercept the request before it's been run. You should still be able to share code with your rails application by extracting it out into common modules included in both places.
Honestly I think just doing the simple thing of factoring the common controller code is likely cleaner, but it's hard to know without the details of your situation so I thought I'd go ahead and suggest this.
Do this:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_controller_action
end
# rest of old and busted
end
end
and the new controller
class NewController < OldController
def new_controller_action
# new hotness
end
end

Resources