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

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) %>

Related

Pundit::AuthorizationNotPerformedError attempting to adapt microposts to Devise/Pundit

I'm new to Rails and I'm working through Michael Hartl's excellent Rails Tutorial for a second time, this time I'm trying to adapt the chapter 11 and chapter 12 microposts to a simple Devise/Pundit application I'm working on. I am able to create microposts through the seed file and display them, but I'm getting an authorization error with Pundit when I actually try to create a new post through the site. The error I'm getting is:
Pundit::AuthorizationNotPerformedError in MicropostsController#create
My Microposts Controller looks like this:
class MicropostsController < ApplicationController
before_action :authenticate_user!
after_action :verify_authorized
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to current_user
else
#feed_items = []
flash[:danger] = "Unable to create micropost!"
end
end
def destroy
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
I'm thinking that do not have the authorization set up properly for the 'create' action, but I'm not sure exactly how it should be set. I do not have a policy for Pundit for Microposts. I tried adding a simple one but it didn't change anything. I'm learning to put all these pieces together, would someone point me in the right direction?
There is one after action filter verify_authorized because of which you are getting this error. If you have created a policy for the create action then use that to get rid of the error.

Paginated array would not show

I'm rather new to Ruby on Rails so would appreciate some help
Roles have Users in my application.
When showing a role I would like to render a paginated list of user names (and as a further refactor - links to users).
Currently my HAML template is as follows
...
%ul.assigned-users
- if #role.users.present?
- names = #role.users.collect {|u| u.name}
- links = Kaminari.paginate_array(names).page(params[:page]).per(20)
= paginate links
- else
%p No users have this role assigned
...
Rendering it gives me the pagination links BUT no names.
Kaminari's paginate_array method does not show the values in the array.
The best way to do so is get the query paginated from the database. Kaminari.paginate_array in your code takes in the whole array of users from the database and then paginates it which is highly inefficient and memory consuming.
You need to add the logic to the controller. If you paginate the #role.users query, it is generated with LIMIT which is the value you assign in the per method and OFFSET which equals to (params[:page] - 1) * per. This gets only the number of records you need from the database.
In your controller app/controllers/roles_controller.rb
class ConversationsController < ApplicationController
def show
#role = Role.find params[:id]
#users = #role.users.page(params[:page]).per(20)
end
end
In your view app/views/roles/show.html.haml
%ul.assigned-users
- if #users.present?
- #users.each do |u|
%li=u.name
= paginate #users
- else
%p No users have this role assigned

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).

Generating a unique URL with tokens in Rails 4 for an external form response

I have a 'Feedback' model whereby a user should be able to request feedback on his/her job performance. I have written basic actions for creating a new feedback request, and the mailer for sending the request to the provider (person who will respond with feedback).
I would like advice from the community on implementing the following:
Once a new feedback request is created, the email that is sent should contain a link to a form where the provider can input his feedback on the users performance.
The feedback provider should not be required to log-in or sign-up in any way (i.e. completely external to the application).
Once submitted, feedback from the provider should be captured in the
system.
Now, I have the following ideas to implement it, but am not sure if this is the best way to proceed:
Generate a unique token upon the creation of a new feedback request. Something like this: Best way to create unique token in Rails?.
The token should then be entered into 'feedbacks' table.
Mailer should then generate variable (e.g. #url) which generates link to another controller (let's say 'external_feedback' and action which does not require log-in (e.g. no before_filter :authenticate_user! from Devise).
That URL should contain a parameter with the token for the specific feedback request.
The action should be to update the 'feedback' request and a form generated with simple_form.
The whole thing is similar to responding to a questionnaire or survey (like Survey Monkey).
After some research I believe the Friendly ID gem may be useful here. I was also reading Section 8 of http://guides.rubyonrails.org/form_helpers.html and perhaps I need to implement an authenticity_token in the formal sense. What I am really looking for is:
Is the above approach the generally correct way to go about doing this?
If so, any specifics on how you would implement it (with or without Friendly ID)?
Do you know of any gems that exist for generating such URLs/tokens?
Thank you in advance. I am now including the current state of model and controller details:
feedback.rb
# == Schema Information
#
# Table name: feedbacks
#
# id :integer not null, primary key
# user_id :integer
# p_first_name :string(255)
# p_last_name :string(255)
# p_email :string(255)
# goal_id :integer
# u_comment :text
# p_comment :text
# created_at :datetime
# updated_at :datetime
#
class Feedback < ActiveRecord::Base
belongs_to :user
belongs_to :goal
has_many :feedback_attributes
validates_presence_of :p_first_name, :p_last_name, :p_email, :goal_id
end
And this is my mailer:
class FeedbackMailer < ActionMailer::Base
def feedback_request(user, feedback)
#user = user
#feedback = feedback
#url = 'http://thisistheexampleurlforfeedback'
mail(to: #feedback.p_email, subject: "#{#user.first_name} #{#user.last_name} has requested your feedback", from: #user.email)
end
end
Add a token field to the feedback model with an index and add a callback to populate it on create e.g.
feedback.rb
before_create :add_token
private
def add_token
begin
self.token = SecureRandom.hex[0,10].upcase
end while self.class.exists?(token: token)
end
now add a new route for the providers feedback
resources :feedbacks do
get 'provider'
put 'provider_update' # you might not need this one, if you are happy to use update
end
In your controller make sure they don't get rejected by devise
before_filter :authenticate_user!, except: [:provider, :provider_update]
...
def provider
#feedback = Feedback.find_by token: params[:token]
end
then in the app/views/feedback/provider.html.haml you can use url in simple_form to send it to the correct update location and only provide the input that they should see.
f.inputs :p_comment
Now update your mailer.
#url = provider_feedback_url(#feedback, token: #feedback.token)
You could do something similar to this using friendly id but you would still need to create some sort of unique slug and then use Feedback.friendly.find instead. I think you would want to combine it with a token to ensure it's still the provider giving the feedback - so the only benefit would really be hiding the true id/count. I think you should update p_* fields to provider_* so that the next dev knows what's in it - it's not the 90s!