0

I am trying to find a RESTful way to make my signup confirmation process work smoothly. I am using Rails 4 and Devise 3.0. The process flow works as so:

User sign up -> confirmation email sent -> user clicks confirm email link -> user is directed to the leads controller where it generates a lead (blank page to user for about 2 seconds) -> then the user is redirected to their dashboard.

The current process just doesn't flow. When a user clicks to confirm their email, they are taken to a blank page where I am submitting a hidden form to create a lead object with their information and once the hidden form is submitted the dashboard page is loaded- It's extremely messy. The URLS are changing after several seconds and It will confuse users.

I want to create a lead object once they click the confirm email link. I have the current process routing the user through the leads_controller new and create methods and then they are automatically directed to their dashboard. I am using the devise sign_in_count attribute to make sure this is their first time signing in. If this is their first time signing in, I direct them through the leads controller to generate a lead with their information.

I am looking to generate the lead object while I am loading the dashboard page once the user selects the email confirmation link.

application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def account_url
    return new_user_session_url unless user_signed_in?
    case current_user.class.name
    when "Business"
      business_root_path
    when "Lender"
      lender_root_path
    else
      root_path
    end if user_signed_in?
  end

  def after_sign_in_path_for(resource)
    if resource.sign_in_count == 1
      new_lead_path
    else 
      stored_location_for(resource) || account_url
    end
  end
end 

leads_controller.rb

class LeadsController < ApplicationController
    before_filter :authenticate_user!
    include Databasedotcom::Rails::Controller

    def new
      @lead = Lead.new
    end

    def create
      @lead = Lead.new(params[:lead])
      @lead['RecordTypeId'] = 'XXXXX'
      @lead['OwnerId'] = 'XXXXX'
      @lead['FirstName'] = "XXXXXX"
      @lead['LastName'] = "XXXXX"
      @lead['Company'] = "XXXXX"
      @lead['Email'] = current_user.email
      @lead['IsConverted'] = false
      @lead['IsUnreadByOwner'] = true
      if @lead.save
        redirect_to lender_root_path
      end 
    end 
end

new.html.erb (this is the hidden form that is submitted to create a new lead object

<div class="container content">
    <%= form_for @lead do |f| %>
    <div class="field">
      <%= f.hidden_field :LeadSource, :value => 'Lending Loop' %>
      <%= f.hidden_field :Status, :value => 'Registered' %> 
    </div>
    <div class="actions">
      <%= f.submit :id => "hidden-submit", :style => "display: none;" %>
    </div>
    <% end %>
</div>
<script>
$(function () {
    $('#hidden-submit').click();
});
</script>

lender_account_controller.rb

class LenderAccountController < ApplicationController
    before_filter :authenticate_user!
    include Databasedotcom::Rails::Controller

    def dashboard
      render layout: 'simple'
      @leads = Lead.all
    end
end
Questifer
  • 1,113
  • 3
  • 18
  • 48

1 Answers1

1

Unless I'm missing something here, why is it necessary to submit that hidden form if it is always posting the same data? Setting LeadSource and Status could be done in a before_create callback in your model. For that matter, all the behavior you have in the create action from @lead['RecordTypeId'] = 'XXXXX' to @lead['IsUnreadByOwner'] = true belongs in callbacks in the model, too.

Seems to me that the flow should go:

User sign up -> confirmation email sent -> user clicks confirm email link -> user is directed to the leads controller create action where it handles lead generation on the backend -> create action redirects to the dashboard. They never visit the new action in leads_controller.rb but you might want to leave it there in case you need it in the future. Your create action could be this simple:

def create
  @lead = Lead.new
  if @lead.save
    redirect_to lender_root_path
  end 
end 

And then move all the activity that sets values on the model to a private method in lead.rb, have it run in before_create callback or at whatever spot in the creation process feels right to you.

You could also skip the Leads controller entirely and have this handled in a "verify" action on your UsersController. User clicks the link, account is verified and the same lead creation actions could take place. I guess it should be mentioned that creating objects after the user clicks a link (a GET request) would be considered improper use of REST, so if you want to be semantically correct, you'd direct them to a page where they'd click a button to actually perform the final verification. It wouldn't be as seamless and you need to weigh adherence to standards VS the user experience you want to create. Still, the basics don't change: all the object creation should be done on the backend without directing the user to a blank page.

Since we're talking about messy things happening, there's a lot of logic happening in controllers that you should avoid. In particular, that case in ApplicationController has dependency issues that may cause problems for you in the future. Start by doing this:

application_controller.rb

def account_url
  return new_user_session_url unless user_signed_in?
  current_user.root_path
end

lender model

def root_path
  lender_root_path
end

business model

def root_path
  business_root_path
end

root_path.rb module

module RootPath
  def root_path
    root_path
  end
end

And then in all your other models that need to respond to root_path, just include RootPath and that method will become available. After that, you just call current_user.root_path and it will work.

subvertallchris
  • 5,282
  • 2
  • 25
  • 43
  • Thanks for the response. I don't have a lead model because I am using the Databasedotcom gem to use Salesforce to store my lead objects, that's why I have the "include Databasedotcom::Rails::Controller" in the leads controller. I do like the idea of getting rid of the hidden form if I can. I'm going to try and call the leads#create method, then display a loading screen and then redirect to the dashboard. I'll post back with an update after I try this. Thanks! – Questifer May 06 '14 at 01:11
  • In your answer you write.."user is directed to the leads controller create action where it handles lead generation on the backend". How exactly would I do that from my after_sign_in_path_for function. Currently I have new_lead_path. However, if I just want the create function to run as you recommend, how do I redirect to that function? I'm relatively new to rails and haven't encountered a redirect with a POST method yet. Thanks! – Questifer May 06 '14 at 02:44
  • 1
    Ah, I see. I'm not familiar with that gem. I guess that explains `@lead['RecordTypeId']` and those other values that couldn't have been Rails model attributes. I'm not entirely sure of what you're asking in that second question. Do you want to know how to trigger the create ACTION in your controller or a create METHOD on an object? – subvertallchris May 06 '14 at 03:53
  • 1
    Assuming you mean the create action, there's no clean way of having a link in an email go to a create action of a controller, since Rails router only recognizes POST for that. If you want to stick with the built-in actions, you'd still need to send them to a `show` action and have them interact with the page, which is what you're trying to avoid. – subvertallchris May 06 '14 at 03:57
  • 1
    Actually, you can just piggyback on the Devise confirmation. Here's a page that describes doing that: http://stackoverflow.com/questions/4558463/rails-devise-after-confirmation. See the comment on the answer to make it a one-time-only event. Change `send_the_email` to a method of your choosing. That would go on the model, so you'd just add a `create_lead` method onto User. Lead generation would then become part of the verification process and not an extra step that disturbs the user. – subvertallchris May 06 '14 at 04:10
  • The link you added in your comment was very helpful, It's exactly what I'm looking for. I am experiencing a few issues with it such as not being able to access current_user.email (or any attribute of the model for that matter) in the method. I'll be posting another question if I can't get this running. Thanks for sharing that link. – Questifer May 09 '14 at 01:59
  • If it's the User model, try using `self.email` from within your callback method to access current_user. – subvertallchris May 09 '14 at 07:21
  • That worked! I should've come up with that on my own. Thank you for your help with this. – Questifer May 09 '14 at 22:33