Getting controller name from a request.referer in Rails - ruby-on-rails-3

I know I can use request.referrer to get the full referrer URL in Rails, but is there a way to just get the controller name from the URL?
I want to see if the URL of http://myurl.com/profiles/2 includes "profiles"
I know I can use a regex to do it but I wondered if there was a better way.

Keep in mind that request.referrer gives you the url of the request before the current one. That said, here is how you can convert request.referrer to controller/actionn information:
Rails.application.routes.recognize_path(request.referrer)
it should give you something like
{:subdomain => "", :controller => "x", :action => "y"}

Here is my try which works with Rails 3 & 4. This code extracts one parameter on logout and redirects user to customized login page otherwise it redirects to general login page.
You can easily extract :controller this way. Controller part:
def logout
auth_logout_user
path = login_path
begin
refroute = Rails.application.routes.recognize_path(request.referer)
path = subscriber_path(refroute[:sub_id]) if refroute && refroute[:sub_id]
rescue ActionController::RoutingError
#ignore
end
redirect_to path
end
And tests are important as well:
test "logout to subscriber entry page" do
session[:uid] = users(:user1).id
#request.env['HTTP_REFERER'] = "http://host/s/client1/p/xyzabc"
get :logout
assert_redirected_to subscriber_path('client1')
end
test "logout other referer" do
session[:uid] = users(:user1).id
#request.env['HTTP_REFERER'] = "http://anyhost/path/other"
get :logout
assert_redirected_to login_path
end
test "logout with bad referer" do
session[:uid] = users(:user1).id
#request.env['HTTP_REFERER'] = "badhost/path/other"
get :logout
assert_redirected_to login_path
end

Inside the controller, you have the method controller_name which returns you only the name. In your case, it would return "profiles".
You may also use params[:controller] which returns the same string.

Related

Sign in redirects to home page instead of staying on same page

I installed devise and it is working properly but the sign in redirect. When user signs in from any page of app, Devise redirects user to home instead of redirecting on same page. I tried implementing How_to solutions of devise but no help.
I wrote in application_controller:
def after_sign_in_path_for(resource)
current_user_path
end
But it gives error:
undefined local variable or method `current_user_path' for #<Devise::SessionsController:0xaeacc34>
When i write:
def after_sign_in_path_for(resource)
current_user_path
end
It gives :
undefined method `user_url'
Whats the solution for this problem? Can any body help in this?
You can use request.referer
def after_sign_in_path_for(resource_or_scope)
request.referer
end
You need to redirect to somewhere else in which scenerio(error or success)? Usually after_sign_in_path_for used after user logged in successfully. SO:
if you haven't override devise default functionality then use below code for navigate control to specific custom page. current_user is also accessible in this action.
def after_sign_in_path_for(resource)
scope = Devise::Mapping.find_scope!(resource)
scope_path = :"#{scope}_root_path"
respond_to?(scope_path, true) ? send(scope_path) : root_url
end
Update:
Another example is as follows:
def after_sign_in_path_for(resource_or_scope)
current_user.admin? ? dashboard_admin_home_index_path : current_user.sign_in_count >= 1 ? "/home/dashboard" : "/#{current_user.role}/dashboard"
end

No request params in constraint when testing route

When I run this in practice it works, but I can't seem to write a working test for my route constraint with rspec.
When the test runs the constraint is triggered, but the request params are empty, thus it does not validate and the test fails.
I am running Rails 3.0.9, rspec-rails 2.6.1 and rspec 2.6.0.
config/routes.rb
match ":param1-unique-:param2" => "controller#index",
:constraints => ParamConstraint.new
lib/param_constraint.rb
class ParamConstraint
def matches?(request)
#request ||= request
valid_param1? && valid_param2?
end
def valid_param1?
#request.params[:param1] == "lorem"
end
def valid_param2?
#request.params[:param2] == "ipsum"
end
end
spec/routing/param_constraint_spec.rb
require 'spec_helper'
describe "param constraint routing" do
it "recognizes route for param1 and param2" do
{ :get => "/lorem-unique-ipsum" }.
should route_to(
:controller => "controller",
:action => "index",
:param1 => "lorem",
:param2 => "ipsum"
)
end
end
Update
If I inspect the request in the constraint I get the following output:
#<ActionDispatch::Request:0x007fee140ff910 #env={
"rack.version"=>[1, 1],
"rack.input"=>#<StringIO:0x007fee1446da48>,
"rack.errors"=>#<StringIO:0x007fee1446e768>,
"rack.multithread"=>true,
"rack.multiprocess"=>true,
"rack.run_once"=>false,
"REQUEST_METHOD"=>"GET",
"SERVER_NAME"=>"example.org",
"SERVER_PORT"=>"80",
"QUERY_STRING"=>"",
"PATH_INFO"=>"/lorem-unique-ipsum",
"rack.url_scheme"=>"http",
"HTTPS"=>"off",
"SCRIPT_NAME"=>"",
"CONTENT_LENGTH"=>"0"
}>
I ran into this same issue today, searching for an answer brought me to this page's question. For what it's worth, I had to resort to writing a request spec instead.
context "passing params that satisfy ParamConstraint" do
before do
visit "/lorem-unique-ipsum"
end
it "should serve up a page with content" do
# replace this with some assertion that gets satisfied by
# pages served up when ParamConstraint.new.matches? returns true
page.should have_selector("html body div#foo")
page.should_not have_selector("html body div#bar")
end
end
context "passing params that DO NOT satisfy ParamConstraint" do
before do
visit "/other-unique-other"
end
it "should serve up a page with different content" do
# replace this with some assertion that gets satisfied by
# pages served up when ParamConstraint.new.matches? returns false
page.should_not have_selector("html body div#foo")
page.should have_selector("html body div#bar")
end
end
This doesn't answer your question, which I take to be "how to test routing constraint", as the proper way would be via a routing spec. But given this gap in how request.params works when you use "should route_to", this is a workaround. A request spec, as opposed to a routing spec, will fill request.params correctly.
Same issue exists years later, with rspec-core 3.4.4, rspec-rails 3.4.2, rails 4.2.6. Don't have time to dig into exactly why...
You can use a request spec as suggested above, but don't use it to test the page contents. Instead, replicate a routing test (route_to) by checking the conversion of URL paths to request params:
RSpec.describe 'routes', type: :request do
describe '/:slug' do
it 'routes correctly' do
get '/test-product-slug'
expect(request.params).to eq(
'controller' => 'product',
'action' => :index,
'slug' => 'test-product-slug'
)
end
end
end

How to use same cached page for different urls in rails?

I have two urls that basically renders the same page. The minor differences can be easily executed via javascript, based on the location.href. Anyway, even when the routes point to the same controller#action, the second route is not using the page cached by the former. How can I achieve this?
I have a interesting requirement in my website opposite to you -- Different pages can be returned from a same url because of different themes. So I came up a solution called "anonymous cache", and I make my own cache key including the extra parameters. But I think this solution can give you some clues.
module AnonymousCache
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def caches_page_for_anonymous(*pages)
before_filter :check_cache_for_anonymous, :only => pages
after_filter :cache_for_anonymous, :only => pages
end
end
def check_cache_for_anonymous
return unless perform_caching
return if logged_in?
path = anon_cache_path
if content = Rails.cache.read(path)
send_data(content,
:type => 'text/html;charset=utf-8', :disposition => 'inline')
return false
end
end
def cache_for_anonymous
return unless perform_caching
return if logged_in?
path = anon_cache_path
#expires_in ||= 1.hour
self.class.benchmark "Cached page for guest: #{path}" do
Rails.cache.write(path, response.body, :expires_in => #expires_in.to_i)
end
end
protected :check_cache_for_anonymous
protected :cache_for_anonymous
private
def anon_cache_path()
path1 = File.join(request.host, current_theme, request.path)
q = request.query_string
path1 = "#{path1}?#{q}" unless q.empty?
path1
end
end
anon_cache_path method is where I make canonical key for the page cache. You can see I includes current_theme in it.
You can copy this and changes anon_cache_path according to your requirements.

How do I write a Rails 3.1 engine controller test in rspec?

I have written a Rails 3.1 engine with the namespace Posts. Hence, my controllers are found in app/controllers/posts/, my models in app/models/posts, etc. I can test the models just fine. The spec for one model looks like...
module Posts
describe Post do
describe 'Associations' do
it ...
end
... and everything works fine.
However, the specs for the controllers do not work. The Rails engine is mounted at /posts, yet the controller is Posts::PostController. Thus, the tests look for the controller route to be posts/posts.
describe "GET index" do
it "assigns all posts as #posts" do
Posts::Post.stub(:all) { [mock_post] }
get :index
assigns(:posts).should eq([mock_post])
end
end
which yields...
1) Posts::PostsController GET index assigns all posts as #posts
Failure/Error: get :index
ActionController::RoutingError:
No route matches {:controller=>"posts/posts"}
# ./spec/controllers/posts/posts_controller_spec.rb:16
I've tried all sorts of tricks in the test app's routes file... :namespace, etc, to no avail.
How do I make this work? It seems like it won't, since the engine puts the controller at /posts, yet the namespacing puts the controller at /posts/posts for the purpose of testing.
I'm assuming you're testing your engine with a dummy rails app, like the one that would be generated by enginex.
Your engine should be mounted in the dummy app:
In spec/dummy/config/routes.rb:
Dummy::Application.routes.draw do
mount Posts::Engine => '/posts-prefix'
end
My second assumption is that your engine is isolated:
In lib/posts.rb:
module Posts
class Engine < Rails::Engine
isolate_namespace Posts
end
end
I don't know if these two assumptions are really required, but that is how my own engine is structured.
The workaround is quite simple, instead of this
get :show, :id => 1
use this
get :show, {:id => 1, :use_route => :posts}
The :posts symbol should be the name of your engine and NOT the path where it is mounted.
This works because the get method parameters are passed straight to ActionDispatch::Routing::RouteSet::Generator#initialize (defined here), which in turn uses #named_route to get the correct route from Rack::Mount::RouteSet#generate (see here and here).
Plunging into the rails internals is fun, but quite time consuming, I would not do this every day ;-) .
HTH
I worked around this issue by overriding the get, post, put, and delete methods that are provided, making it so they always pass use_route as a parameter.
I used Benoit's answer as a basis for this. Thanks buddy!
module ControllerHacks
def get(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "GET")
end
# Executes a request simulating POST HTTP method and set/volley the response
def post(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "POST")
end
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "PUT")
end
# Executes a request simulating DELETE HTTP method and set/volley the response
def delete(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "DELETE")
end
private
def process_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
parameters ||= {}
process(action, parameters.merge!(:use_route => :my_engine), session, flash, method)
end
end
RSpec.configure do |c|
c.include ControllerHacks, :type => :controller
end
Use the rspec-rails routes directive:
describe MyEngine::WidgetsController do
routes { MyEngine::Engine.routes }
# Specs can use the engine's routes & named URL helpers
# without any other special code.
end
– RSpec Rails 2.14 official docs.
Based on this answer I chose the following solution:
#spec/spec_helper.rb
RSpec.configure do |config|
# other code
config.before(:each) { #routes = UserManager::Engine.routes }
end
The additional benefit is, that you don't need to have the before(:each) block in every controller-spec.
Solution for a problem when you don't have or cannot use isolate_namespace:
module Posts
class Engine < Rails::Engine
end
end
In controller specs, to fix routes:
get :show, {:id => 1, :use_route => :posts_engine}
Rails adds _engine to your app routes if you don't use isolate_namespace.
I'm developing a gem for my company that provides an API for the applications we're running. We're using Rails 3.0.9 still, with latest Rspec-Rails (2.10.1). I was having a similar issue where I had defined routes like so in my Rails engine gem.
match '/companyname/api_name' => 'CompanyName/ApiName/ControllerName#apimethod'
I was getting an error like
ActionController::RoutingError:
No route matches {:controller=>"company_name/api_name/controller_name", :action=>"apimethod"}
It turns out I just needed to redefine my route in underscore case so that RSpec could match it.
match '/companyname/api_name' => 'company_name/api_name/controller_name#apimethod'
I guess Rspec controller tests use a reverse lookup based on underscore case, whereas Rails will setup and interpret the route if you define it in camelcase or underscore case.
It was already mentioned about adding routes { MyEngine::Engine.routes }, although it's possible to specify this for all controller tests:
# spec/support/test_helpers/controller_routes.rb
module TestHelpers
module ControllerRoutes
extend ActiveSupport::Concern
included do
routes { MyEngine::Engine.routes }
end
end
end
and use in rails_helper.rb:
RSpec.configure do |config|
config.include TestHelpers::ControllerRoutes, type: :controller
end

URL Rewriting on Heroku

I have two domain names assigned to my heroku app. I want to make sure that all requests to one domain are permanently redirected to the other domain.
How can I do that on Heroku?
Assuming you are using Rails 3, you can take advantage of the new routing system.
constraints :host => "invalid.domain.com" do
match "/*path", :to => proc { |env|
req = ActionDispatch::Request.new(env)
[301, { "Location" => "http://valid.domain.com#{req.fullpath}" }, ["You are being redirected."]]
}
end
This is just an example. Feel free to refactor the lambda into a custom class.
class ApplicationController
before_filter :ensure_domain
TheDomain = 'myapp.mydomain.com'
def ensure_domain
if request.env['HTTP_HOST'] != TheDomain
redirect_to TheDomain
end
end
end
You can do this via a before_filter in the application controller - Heroku give an example at the bottom of their docs at http://docs.heroku.com/custom-domains or a contraint matched route in your application routes.rb using the redirect method.
John.