Dynamic routes in rails with condition - ruby-on-rails-3

I have a set of pages with public attribute. I want the routes file handle dynamic routes but only for public pages.
I currently have the following, but there is no restriction, all pages are visible. What I'd like is go to page only if page is public else raise 404.
Page.public.each do |page|
get "/:slug", controller: 'pages', action: 'show' if page.public?
end

I would put this behaviour in the controller rather than in routes.rb, since a page could change from private to public during runtime and routes in production are initialised only once at the start.
class PagesController
before_filter :is_public, only => [:show]
protected
# Check if the page is public, otherwise raise a routing error.
def is_public
raise ActionController::RoutingError.new unless Page.find(params[:slug]).public?
end
end

The working code is (was missing 'Not Found')
class PagesController
before_filter :is_public, only => [:show]
protected
# Check if the page is public, otherwise raise a routing error.
def is_public
raise ActionController::RoutingError.new('Not Found') unless Page.find(params[:slug]).public?
end
end

Related

NoMethodError undefined method `admin?' for nil:NilClass Pundit, Devise Rails

I'm trying to integrate pundit with my active admin and devise configuration. But the app works weirdly. It takes in model/record as current_user.
my policy file:
class AdminUserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
Rails.logger.info '--------------- initialize called-------------------'
Rails.logger.info current_user
Rails.logger.info model
#current_user = current_user
#record = model
end
def index?
#current_user.admin?
end
end
controller:
controller do
include Pundit
protect_from_forgery
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
before_action :authenticate_admin_user!
def index
authorize current_admin_user
super
end
private
def user_not_authorized
flash[:alert]="Access denied"
redirect_to (request.referrer || admin_root_path)
end
end
The log is as follows:
--------------- initialize called----------------------------------------
#<AdminUser:0x007f27733f8a80>
Completed 500 Internal Server Error in 364ms (ActiveRecord: 312.8ms)
NoMethodError (undefined method `admin?' for nil:NilClass):
app/policies/admin_user_policy.rb:13:in `index?'
app/admin/dashboard.rb:19:in `index'
Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout
Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (4.8ms)
Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (2.5ms)
Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.1ms)
Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (39.5ms)
The weird part is, according to logs, current_user=nil and model=#<AdminUser:0x007f27733f8a80>
I swapped current_user with model my policy file as
def index?
#record.admin?
end
And it works!
I don't understand this strange behaviour.
Pundit policy doc says that it calls the current_user method to retrieve what to send into the first argument of the initialize method inside the Policy class. If you have configured ActiveAdmin to retrieve the current logged in user by using current_admin_user, then you have to override the pundit default method in your ApplicationController class like so: Ref
class ApplicationController < ActionController::Base
// ...
def pundit_user
current_admin_user // or whatever based on ActiveAdmin initializer config
end
end
In order to make the defined policy working, you have to invoke authorize inside the controller action with the instance of the corresponding policy model. So if you have a PostPolicy and you want to authorize the update action, you have to do the following:
controller do
def update
#post = Post.find(params[:id])
authorize #post // the current user will be automatically sent to the PostPolicy
super
end
end
The authorize method automatically infers that Post will have a matching PostPolicy class, and instantiates this class, handing in the current user and the given record. It then infers from the action name, that it should call update? on this instance of the policy. In this case, you can imagine that authorize would have done something like this:
unless PostPolicy.new(current_user, #post).update?
raise Pundit::NotAuthorizedError, "not allowed to update? this #{#post.inspect}"
end
Having all these, in your case, if you want that a user should be authorized before viewing the list of all users, you can define the AdminUserPolicy like you have done already. Then in the index action of your AdminUserController,
controller do
def index
#users = AdminUser.all
authorize #users // NOT `authorize current_admin_user`
super
end
end
You can pass a second argument to authorize if the name of the permission you want to check doesn't match the action name. For example:
def publish
#post = Post.find(params[:id])
authorize #post, :update?
#post.publish!
redirect_to #post
end

Devise after login redirect - double render error

I'm working with rails 3.2 and Devise (the lastest version)
The main idea if the test some variables of the current logged user after sign in. So, for example, if the user has pending creating an address i want to redirect the new address path. But what i get is a double render error.
Here is the code
class ApplicationController < ActionController::Base
protect_from_forgery
# Devise: Where to redirect users once they have logged in
def after_sign_in_path_for(resource)
if current_user.is? :company_owner
if $redis.hget(USER_COMPANY_KEY, current_user.id).nil?
redirect_to new_owner_company_path and return
else
#addr_pending = $redis.hget(PENDING_ADDRESS_KEY,current_user.id)
unless #addr_pending.nil? || !#addr_pending
redirect_to owner_company_addresses_path and return
end
end
end
root_path
end
end
my routes definition
root :to => "home#index"
devise_for :users, :controllers => {
:omniauth_callbacks => "users/omniauth_callbacks"
}
resources :users, :only => :show
namespace :owner do
resource :company do # single resource /owner/company
get 'thanks'
get 'owner' #TODO: esto hay que sacarlo de aquí y forme parte del login
resources :addresses
end
end
So, when i login with a user with a pedding address creation i get
"render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
what is wrong with?
redirect_to owner_company_addresses_path and return
So, i just want to redirect to the new address path. I don't understand why i get the error.
Thanks in advance.
---- edit ----
Seems that only one path must be returned (I thought with redirect_to and return was enough, but it does not)
def after_sign_in_path_for(resource)
#final_url = root_path
if current_user.is? :company_owner
if $redis.hget(USER_COMPANY_KEY, current_user.id).nil?
#final_url = new_owner_company_path
else
#addr_pending = $redis.hget(PENDING_ADDRESS_KEY,current_user.id)
unless #addr_pending.nil? || !#addr_pending
#final_url = owner_company_addresses_path
end
end
end
#final_url
end
You should remove redirect_to method call and return statement. after_sign_in_path_for should return only a path:
E.g:
def after_sign_in_path_for(resource)
new_owner_company_path
end

Why is Rails 3.2.2 Generating URLs prefixed with /assets when using redirect_to?

Well the title Question pretty much sums it up, But I'd like to detail a scenario anyways,
I've created a DemoController, (I have not created a Resource model), and my routes.rb looks like this:
DispatchMe::Application.routes.draw do
root to: "demo#index"
end
From the demo controller I'm dong the following:
class DemoController < ApplicationController
def index
redirect_to :action => 'show'
end
def show
end
end
There is a file in: app/views/demo/show.html.erb of course, And I'd expected that template to be rendered but instead I'm getting the following error:
ActionController::RoutingError (No route matches [GET] "/assets")
and this URL is generated as a result from the redirect:
/assets?action=show&controller=demo
Am I missing something here? I thought rails was supposed to render the action's template for such cases.
Note. I understand that If I create a route like get 'show' => "demo#show" and call redirect_to show_path it'll work just fine, But I need to know if that's mandatory?
Thank you very much!
For the desired behavior, use render instead of redirect_to:
class PagesController < ApplicationController
def index
render :action => "show"
end
def show
end
end
EDIT:
You can use redirect_to on other actions, but from what I can tell, the index action sets the base path. To simplify route definition, use resources :controller_name. You can view the routes generated by resources by typing rake routes in your command line.
Example:
demo_controller.rb
class DemoController < ApplicationController
def index
end
def show
redirect_to :action => 'index'
end
end
routes.rb
DispatchMe::Application.routes.draw do
root to: "demo#index"
resources :demo
end
development.log
Started GET "/demo/show" for 127.0.0.1 at 2012-04-04 14:55:25 -0400
Processing by DemoController#show as HTML
Parameters: {"id"=>"show"}
Redirected to http://dispatch.dev/
Completed 302 Found in 0ms (ActiveRecord: 0.0ms)

Forcing 404 error in the right way in Rails [duplicate]

I'd like to 'fake' a 404 page in Rails. In PHP, I would just send a header with the error code as such:
header("HTTP/1.0 404 Not Found");
How is that done with Rails?
Don't render 404 yourself, there's no reason to; Rails has this functionality built in already. If you want to show a 404 page, create a render_404 method (or not_found as I called it) in ApplicationController like this:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Rails also handles AbstractController::ActionNotFound, and ActiveRecord::RecordNotFound the same way.
This does two things better:
1) It uses Rails' built in rescue_from handler to render the 404 page, and
2) it interrupts the execution of your code, letting you do nice things like:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
without having to write ugly conditional statements.
As a bonus, it's also super easy to handle in tests. For example, in an rspec integration test:
# RSpec 1
lambda {
visit '/something/you/want/to/404'
}.should raise_error(ActionController::RoutingError)
# RSpec 2+
expect {
get '/something/you/want/to/404'
}.to raise_error(ActionController::RoutingError)
And minitest:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
OR refer more info from Rails render 404 not found from a controller action
HTTP 404 Status
To return a 404 header, just use the :status option for the render method.
def action
# here the code
render :status => 404
end
If you want to render the standard 404 page you can extract the feature in a method.
def render_404
respond_to do |format|
format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
format.xml { head :not_found }
format.any { head :not_found }
end
end
and call it in your action
def action
# here the code
render_404
end
If you want the action to render the error page and stop, simply use a return statement.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
ActiveRecord and HTTP 404
Also remember that Rails rescues some ActiveRecord errors, such as the ActiveRecord::RecordNotFound displaying the 404 error page.
It means you don't need to rescue this action yourself
def show
user = User.find(params[:id])
end
User.find raises an ActiveRecord::RecordNotFound when the user doesn't exist. This is a very powerful feature. Look at the following code
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
You can simplify it by delegating to Rails the check. Simply use the bang version.
def show
user = User.find_by_email!(params[:email])
# ...
end
The newly Selected answer submitted by Steven Soroka is close, but not complete. The test itself hides the fact that this is not returning a true 404 - it's returning a status of 200 - "success". The original answer was closer, but attempted to render the layout as if no failure had occurred. This fixes everything:
render :text => 'Not Found', :status => '404'
Here's a typical test set of mine for something I expect to return 404, using RSpec and Shoulda matchers:
describe "user view" do
before do
get :show, :id => 'nonsense'
end
it { should_not assign_to :user }
it { should respond_with :not_found }
it { should respond_with_content_type :html }
it { should_not render_template :show }
it { should_not render_with_layout }
it { should_not set_the_flash }
end
This healthy paranoia allowed me to spot the content-type mismatch when everything else looked peachy :) I check for all these elements: assigned variables, response code, response content type, template rendered, layout rendered, flash messages.
I'll skip the content type check on applications that are strictly html...sometimes. After all, "a skeptic checks ALL the drawers" :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: I don't recommend testing for things that are happening in the controller, ie "should_raise". What you care about is the output. My tests above allowed me to try various solutions, and the tests remain the same whether the solution is raising an exception, special rendering, etc.
You could also use the render file:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Where you can choose to use the layout or not.
Another option is to use the Exceptions to control it:
raise ActiveRecord::RecordNotFound, "Record not found."
The selected answer doesn't work in Rails 3.1+ as the error handler was moved to a middleware (see github issue).
Here's the solution I found which I'm pretty happy with.
In ApplicationController:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :handle_exception
end
def not_found
raise ActionController::RoutingError.new('Not Found')
end
def handle_exception(exception=nil)
if exception
logger = Logger.new(STDOUT)
logger.debug "Exception Message: #{exception.message} \n"
logger.debug "Exception Class: #{exception.class} \n"
logger.debug "Exception Backtrace: \n"
logger.debug exception.backtrace.join("\n")
if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class)
return render_404
else
return render_500
end
end
end
def render_404
respond_to do |format|
format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def render_500
respond_to do |format|
format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
format.all { render nothing: true, status: 500}
end
end
and in application.rb:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
And in my resources (show, edit, update, delete):
#resource = Resource.find(params[:id]) or not_found
This could certainly be improved, but at least, I have different views for not_found and internal_error without overriding core Rails functions.
these will help you...
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery
unless Rails.application.config.consider_all_requests_local
rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
end
private
def render_error(status, exception)
Rails.logger.error status.to_s + " " + exception.message.to_s
Rails.logger.error exception.backtrace.join("\n")
respond_to do |format|
format.html { render template: "errors/error_#{status}",status: status }
format.all { render nothing: true, status: status }
end
end
end
Errors controller
class ErrorsController < ApplicationController
def error_404
#not_found_path = params[:not_found]
end
end
views/errors/error_404.html.haml
.site
.services-page
.error-template
%h1
Oops!
%h2
404 Not Found
.error-details
Sorry, an error has occured, Requested page not found!
You tried to access '#{#not_found_path}', which is not a valid page.
.error-actions
%a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path}
%span.glyphicon.glyphicon-home
Take Me Home
routes.rb
get '*unmatched_route', to: 'main#not_found'
main_controller.rb
def not_found
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
end
<%= render file: 'public/404', status: 404, formats: [:html] %>
just add this to the page you want to render to the 404 error page and you are done.
I wanted to throw a 'normal' 404 for any logged in user that isn't an admin, so I ended up writing something like this in Rails 5:
class AdminController < ApplicationController
before_action :blackhole_admin
private
def blackhole_admin
return if current_user.admin?
raise ActionController::RoutingError, 'Not Found'
rescue ActionController::RoutingError
render file: "#{Rails.root}/public/404", layout: false, status: :not_found
end
end
Raising ActionController::RoutingError('not found') has always felt a little bit strange to me - in the case of an unauthenticated user, this error does not reflect reality - the route was found, the user is just not authenticated.
I happened across config.action_dispatch.rescue_responses and I think in some cases this is a more elegant solution to the stated problem:
# application.rb
config.action_dispatch.rescue_responses = {
'UnauthenticatedError' => :not_found
}
# my_controller.rb
before_action :verify_user_authentication
def verify_user_authentication
raise UnauthenticatedError if !user_authenticated?
end
What's nice about this approach is:
It hooks into the existing error handling middleware like a normal ActionController::RoutingError, but you get a more meaningful error message in dev environments
It will correctly set the status to whatever you specify in the rescue_responses hash (in this case 404 - not_found)
You don't have to write a not_found method that needs to be available everywhere.
To test the error handling, you can do something like this:
feature ErrorHandling do
before do
Rails.application.config.consider_all_requests_local = false
Rails.application.config.action_dispatch.show_exceptions = true
end
scenario 'renders not_found template' do
visit '/blah'
expect(page).to have_content "The page you were looking for doesn't exist."
end
end
If you want to handle different 404s in different ways, consider catching them in your controllers. This will allow you to do things like tracking the number of 404s generated by different user groups, have support interact with users to find out what went wrong / what part of the user experience might need tweaking, do A/B testing, etc.
I have here placed the base logic in ApplicationController, but it can also be placed in more specific controllers, to have special logic only for one controller.
The reason I am using an if with ENV['RESCUE_404'], is so I can test the raising of AR::RecordNotFound in isolation. In tests, I can set this ENV var to false, and my rescue_from would not fire. This way I can test the raising separate from the conditional 404 logic.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404']
private
def conditional_404_redirect
track_404(#current_user)
if #current_user.present?
redirect_to_user_home
else
redirect_to_front
end
end
end

Why is respond_with not returning json from my model?

Why doesn't respond_with respond with the json in this case? I'm invoking the action with an explicit .json (/tasks/4e3c1163a19d461203000106/items/4e4c27dfa19d46e0e400000a.json)
In my controller --
class Tasks::TasksController < Tasks::BaseController
respond_to :html, :js, :json
def update
#task = #taskgroup.update_task(params[:id], params[:task])
#taskgroup.save
respond_with #task
end
end
When I overrode to_json and added a breakpoint, it isn't hit. The response is:
{}
If I replace respond_with with an explicit call to to_json:
respond_with #task do |format|
format.json { render json: #task.to_json }
end
The response is perfect:
{
"_id":"4e4c27dfa19d46e0e400000a",
"assigned_to":null,
"comments" [{"_id":"4e4c2fd7a19d46e127000014",
[SNIP]
It works fine in the later case, but I'd like to figure out why the first one doesn't work. This happens for other controllers and models in my app. Not sure if its a mongoid thing? (rails 3.0.9 / mongoid 2.1.8)
Here is a monkeypatch I wrote to always respond_with what you tell it to do regardless of protocol. Be warned this does break RESTful best practices and if you respond_with in a RESTful manner then it may break. However if your JSON/XML responses are separate from the main application then it is useful and your other controllers will not break.
Usage, Include this in any controller to override the respond_with functionality.
class ApiController < BaseController
include ValidResponder
end
Then anything that extends ApiController will include this functionality.
Save the following in app/lib/valid_responder.rb:
#Override restful responses.
module ValidResponder
def self.included(base)
ActionController::Responder.class_eval do
alias :old_api_behavior :api_behavior
define_method :api_behavior do |error|
if controller.class.ancestors.include?(base)
raise error unless resourceful?
display resource
else
old_api_behaviour(error)
end
end
end
end
end
For reference, the actual method source is available here: http://api.rubyonrails.org/classes/ActionController/Responder.html#method-i-api_behavior
Ok look how it goes. When you call respond_with inside the update action then (if the object is valid) it will redirect to the show action (if you do not want this default behaviour you must provide location: "other_action" to respond_with).