Redirecting a request in a routing constraint - ruby-on-rails-4

I have Sidekiq mounted in my routes file to the /sidekiq endpoint.
I use a constraints option to have it call an external class for validation as a way of preventing non-privelaged users from accessing that endpoint.
# config/routes.rb
mount Sidekiq::Web => "/sidekiq", constraints: Sidekiq::AdminConstraint.new
# lib/sidekiq/admin_constraint.rb
module Sidekiq
class AdminConstraint
def matches?(request)
return false unless request.session[:user_id]
user = User.find_by_id(request.session[:user_id])
user && Ability.new(user).can?(:manage, :sidekiq)
end
end
end
This setup works great. However, it only lets me return true / false on whether the request should go through or not. It does not let me -
Set a flash message (e.g. "You are not permitted to access that page") and
Redirect to some arbitrary page
In that sense, I'm looking for it to behave more like a controller's before_filter.
Is there a way I can modify the request object that's passed in to implement that redirect?
Thanks!

I don't have idea directly set the flash messages, But we can use in different way.
Use the following solution
In your routes.rb, add the following line in the end of the file
match "*path", :to => "application#error_404"
This basically means, any path that is not defined in your route will end up going to error_404 in application_controller. Its very important to put this at the end of your file
And in your ApplicationController, add
def error_404
redirect_to root_path
end
Thanks

Related

Rails 4 Dynamically Generate an Extra Public-Facing URL on Create

I have a Rails app that stores stock photos in each project. Upon creating a project, I want the app to not only create the url for the project that we will view internally, which is projects#show,i.e. /projects/4 but I also want it to create another URL that we can show to the client that will be a little different. It will allow the to approve the photos, etc. I want the url to be something like /projects/st53d where the end of the url will be a random number generated with random_string = SecureRandom.urlsafe_base64(5)
This way, I can pass this url to the clients and they can view all the photos in the project and approve the ones they want us to use, but cannot change or view the other internal stuff we have on the standard product show page
Currently, I have added a client_url column to the Project model and I was thinking of generating the random number within the Project#create method and then saving it to the project's client_url column. This way I can loop through all client urls and make sure I did not generate a duplicate. But I cannot figure out how to do the route creation part.
i have yet to do the part where I check if it is random but I know how to do that, just not the route creation.
I was thinking I needed to create a new method which I named show_client and somehow create a route for that in my config/routes.rb file
class ProjectsController < ApplicationController
before_action :authenticate_user!, only: [:show,:index]
def create
#project = Project.create(project_params)
#project.creator = current_user.email
require 'securerandom'
random_string = SecureRandom.urlsafe_base64(5)
#project.client_url = random_string
#project.save
redirect_to #project
end
def show_client
#project = Project.find(params[:id])
#photos = #project.photos
end
This seems like more of a roles issue. You could check out something like CanCanCan. If you only need two kinds of users, you could also just add a boolean column for admin to your user table. The url extension doesn't seem to matter in the scope of your problem. If you want to use a different view for the visiting the user show page, something in the show action like:
unless current_user.admin?
redirect_to client_show(current_user.id)
end
I was able to figure it out.
I created the client_url column in the database and upon creating a project, in the create method of the projects_controller, I generated a random base64 number and assigned it to project.client_url
in routes.rb i did:
get 'projects/clients/:client_id' => 'projects#clients', as: 'projects_client'
in my projects_controller.rb:
def clients
#project = Project.where(client_id: params[:client_id])
end
then the link to that route is:
<%= link_to 'Client Version', projects_client_path(#project.client_url) %>

In Rails4, using Trailblazer, how do I access the current_user

We are building a Rails4 app using Trailblazer. I have never worked with Trailblazer before and I am confused about how to do things.
We are building an auction site. I was previously using a traditional controller, and this route endpoint was working fine:
def bill
#profile = Profile.find_by user_id: current_user_id
#current_order = Order.order(created_at: :desc).find_by(user_id: current_user_id)
#batch = #current_order.batch
if #batch.nil?
puts "There was no batch linked to the current order of #{#current_order.id}"
flash[:error] = "We are sorry, but we could not determine which batch your order belongs to."
else
#price_shown_to_customer = #batch.price + ENV["FUELBID_FEE_PER_GALLON"].to_f
#amount = #current_order.quantity * #price_shown_to_customer
end
But now I'm suppose to create this as a Trailblazer api, using a Representer class.
So in routes.rb I added something for "charges":
namespace :api do
get '/price' => 'info#info'
post '/order' => 'orders#create'
get '/charges' => 'charges#bill'
end
I created this Api but copying-and-pasting another:
module Api
class ChargesController < ApiApplicationController
respond_to :json
def bill
respond_with OpenStruct.new.extend(ChargesRepresenter)
end
end
end
I tested the above with a simple Representer and it all worked fine, so everything is good up to this point. If I return simple data from the Representer, then I can see it fine here:
http://localhost:3000/api/charges.json
But I need to get the current_user. How is this done? Right now, this does not work:
module ChargesRepresenter
include Roar::JSON
collection :price_shown_to_customer
def price_shown_to_customer
current_order = Order.order(created_at: :desc).find_by(user_id: current_user_id)
puts "current_order"
puts current_order.id
batch = current_order.batch
batch.price + ENV["FUELBID_FEE_PER_GALLON"].to_f
end
end
current_user_id exists in my traditional controllers because we set up Devise and so my traditional controllers inherit it:
class ChargesController < SecuredController
But is there any way to get it in a Trailblazer Representer?
Hope this answer is not too late.
If you can switch to Decorator pattern instead of a Module.
Representer really doesn't need to know and doesn't care if it is called from controller or console or test. All it needs is a hash to build your json object from. So you can just pass another attribute called current_user_id to your Representer and then use it inside r presenter like you do.
FYI:
If you need a more immediate response you can also copy your question to https://gitter.im/trailblazer/chat . There are usually several people hanging out there. But it's also good to post a question here for posterity.

Rails controllers, rendering view from different controller, saving form inputs and error messages

Two controllers: Users and Tasks.
Main page for Users = Users/user_id.
Form on main page used to input data into tasks model.
This process handled by the Tasks Controller.
Successful input: redirect and load tasks from database, OK, all working.
Unsuccessful input, just need to refresh main page so we keep form input and specialised (non-flash) error messages
I can't seem to get the Tasks Controller to deliver the original page. Error is Missing template users/1(which is the correct syntax if I were to visit in my browser).
Should I be calling an action and passing params? Any help for this beginner would be really appreciated.
def create
#task = current_user.tasks.build(task_params)
if #task.save
flash[:success] = "New task created!"
redirect_to user_url(current_user)
else
flash[:error] = "Task not saved! Please see guidance by form labels"
render "users/#{current_user.id}"
end
end
private
def task_params
params.require(:task).permit(:label, :address, :content)
end
end
users/1 is not a template, it's a path. users/show is the template in this case.
mind that the only reason to use render is to render a template in the scope of your current controller action rather than the normal one.
i.e. you probably need to have #user etc set, or the users/show template will be upset about missing variables.
In this case it might be easier just to redirect_to user_path(id) and allow the users/#show controller action set up the #user variable etc.

Rails Routes not conforming to expected controller action

I've spent several hours trying to understand why my Rails 4 app doesn't seem to want to route to the expected controller action.
In summary: every single action that I attempt in the browser URL seems to go to the index view, even though my routes appear to be correct. I've attempted to restart the server, etc hoping that might fix it, but right now I'm completely lost.
For example, if I try to access the URL at localhost:3000/leads#new I get the following error message:
Missing template leads/index, application/index with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}. Searched in: * "/Users/me/Sites/azimuth/app/views"
If I add a template for index.html.erb to the app/views/leads folder, then I don't get the error message - however every single path goes to that same index view - leads#show, leads#edit, etc - all of them.
Here is the routes.rb:
Azimuth::Application.routes.draw do
# get 'leads', to: 'leads#new'
resources :applicants
resources :contacts
resources :leads
PagesController.action_methods.each do |action|
get "/#{action}", to: "pages##{action}", as: "#{action}_page"
end
root "pages#home"
end
note that the commented line - get 'leads', to: 'leads#new' - seems to be the only way to properly get the routing to work. Using resources :leads (which I understand is best practice?) is giving me fits.
Here's the leads_controller.rb:
class LeadsController < ApplicationController
def new
#lead = Lead.new
end
def create
#lead = Lead.new(lead_params)
if #lead.save
flash[:success] = "Thank you for reaching out! We'll be in touch soon."
redirect_to 'home'
else
render 'new'
end
end
def index
#lead = Lead.all
end
private
def lead_params
params.require(:lead).permit(:first_name, :last_name, :subject, :message)
end
end
Rake routes - appears that things should work fine. (Note this is just showing the routes relevant to the Leads object).
Prefix Verb URI Pattern Controller#Action
leads GET /leads(.:format) leads#index
POST /leads(.:format) leads#create
new_lead GET /leads/new(.:format) leads#new
edit_lead GET /leads/:id/edit(.:format) leads#edit
lead GET /leads/:id(.:format) leads#show
PATCH /leads/:id(.:format) leads#update
PUT /leads/:id(.:format) leads#update
DELETE /leads/:id(.:format) leads#destroy
I'm very confused, can't seem to track down what's going on, and would appreciate any help!
Correct me if you are wrong, but I think you are trying to access the wrong URL. You said you were visiting localhost:3000/leads#new in your browser. The correct URL for that route would be localhost:3000/leads/new
When are you are defining routes in your config/routes.rb file, the #'s are used to let rails know that you are specifying that a method of one of your controllers should respond to this URL. The actual URL's do not contain #'s (typically speaking).

How would I change this to prevent numerous queries against the database to check the user role?

Last Updated: 29 Aug 2013 18:54 EST
I have the following module defined and then included into my model. I am using the rolify gem to give my users roles.
module Permissions::Offer
extend ActiveSupport::Concern
included do
# `user` is a context of security
protect do |user, offer|
# Admins can retrieve anything
if user.has_role? :administrator
scope { all }
# ... and view, create, update, or destroy anything
can :view
can :create
can :update
can :destroy
elsif user.present?
# Allow to read any field
can :view
can :create
# Checks offered_by_id keeping possible nil in mind
# Allow sellers to modify/delete their own offers
if offer.try(:offered_by_id) == user.id
can :update
can :destroy
end
else
# Guests can't read the text
cannot :view
end
end
end
end
What I am experiencing is that when I do the following...
respond_with Offer.restrict!(current_user)
It queries the roles table for every offer that is returned. Is there anyway to have it not make this request repeatedly when requesting a list of offers? I'm sure I could cache the response to avoid the database hit, but I'd rather it not hit the cache either.
If I open a rails console and do the following I get the same result:
current_user = User.first
Offer.restrict!(current_user).to_a
I have installed the bullet gem to see if it considers it an N+1 query, and it doesn't not detect it. I believe because the included gets called every time a new instance of offer gets created it fires off this call to verify permissions. That coupled with the fact that rolify does not cache it's user role checks for any length of time makes this less than ideal. I suppose rolify does this to allow for the changing of roles on the fly without having to deal with clearing the cache. As of now the only way I can see to solve this is to implement caching of my own.
I opened an issue with rolify to see if they are interested in creating a more permanent solution. For anyone else that encounters this, here's what I did int eh meantime.
def has_role?(role)
roles = Rails.cache.fetch(roles_for: { object_id: self.object_id }, expires_in: 10.seconds, race_condition_ttl: 2.seconds) { self.roles.map(&:name) }
roles.include?(role)
end
This doesn't do everything the real method does.. but it suits my purposes.
Here is a link to the source for anyone that wishes to implement something like this on all the methods.
https://github.com/EppO/rolify/blob/master/lib/rolify/role.rb