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

turkeyman84 picture turkeyman84 · Oct 1, 2013 · Viewed 6.9k times · Source

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

Answer

DavidLBatey picture DavidLBatey · Oct 1, 2013

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!