Search code examples
ruby-on-railsactioncontroller

Why is my rails controller passing different values to 2 partials rendered by the same action?


I have an app with SalesOpportunities - beneath these there are SaleQualifiers (which belong_to SalesOpportunity) which fit into 4 stages ("Need", "Budget", "Fit", "Negotiation"). I'm displaying these 4 stages as tabs on the SalesOpportunity show page, and when the user clicks a tab it will call a custom controller action to select the relevant SaleQualifiers to display in the pane beneath. So far this works well, but I'm trying to ensure the tab updates with the selected stage as the "active" tab - and this isn't working. The pane holds the right info, but the tabs are not correct:

SalesOpportunity show Controller action:

def show
 @sales_opportunity = SalesOpportunity.find(params[:id])
 session[:sales_opportunity_id] = @sales_opportunity.id
 #set the SaleQualifier stages to show on the tabs
 @stages = Question.order(:id).pluck(:sale_stage).uniq
 @stage = @sales_opportunity.sale_status
 @questions = Question.where(sale_stage: @stage)
 #find any answered sale_qualifiers in this sale_stage
 @sale_qualifiers = SaleQualifier.find_by_sql(["SELECT sale_qualifiers.* FROM sale_qualifiers INNER JOIN questions ON questions.id = sale_qualifiers.question_id WHERE sales_opportunity_id = ? AND questions.sale_stage = ? ", @sales_opportunity.id, @stage])
 #find some unanswered sale_qualifiers and build them
 @sale_qualifier = SaleQualifier.new(sales_opportunity_id: params[@sales_opportunity.id])
  @answer = @sale_qualifier.build_answer
 #find the right question to render and link to the sale_qualifier
 @question = Question.find(@sales_opportunity.next_question)
end

This action just ensures the right pane is set on first loading the SalesOpportunity show page.

Here's the custom action to find the right SaleQualifiers according to their actions:

def prospect_qualifiers
 @sales_opportunity = SalesOpportunity.find_by(id: session[:sales_opportunity_id])
 @stage = params[:sale_stage]
 @questions = Question.where(sale_stage: @stage)
 @stages = Question.order(:id).pluck(:sale_stage).uniq
 @sale_qualifiers = SaleQualifier.find_by_sql(["SELECT sale_qualifiers.* FROM sale_qualifiers INNER JOIN questions ON questions.id = sale_qualifiers.question_id WHERE has_answer = 'true' AND sales_opportunity_id = ? AND questions.sale_stage = ? ", @sales_opportunity.id, @stage])
 #when there's no sale_qualifiers from this sale_stage we can just select the first one according to the question and build that
 if @sale_qualifiers.blank?
  @question = Question.order(:id).where(sale_stage: @stage).first
  @sale_qualifier = SaleQualifier.new(sales_opportunity_id: params[@sales_opportunity.id], question_id: @question.id)
  @answer = @sale_qualifier.build_answer
  render :modal_form
 else
  #when some of the sale_qualifiers of this sale_stage exist we'll need to be more specific about which question is next
  @sale_qualifier = SaleQualifier.new(sales_opportunity_id: params[@sales_opportunity.id])
  @answer = @sale_qualifier.build_answer
  #find the right question to render and link to the sale_qualifier
  @question = Question.find(@sales_opportunity.next_question)
  render :modal_form
 end
end

This takes the sale_stage as a parameter passed from the link to this action, and uses that to find the right SaleQualifiers.

The offending part of my view code is below:

<div  id="qualifier-tabs">
  <ul class="nav nav-tabs">
    <%= render partial: 'shared/tabs', collection: @stages, as: :stage %>
  </ul>
</div>
<div class="tab-content", id="qualifier_panel">
  <%= render partial: 'shared/panel', collection: @stages, as: :stage %>
 </div>
</div>

shared/tabs partial:

<% if stage == @stage %>
 <li class="active"><%= link_to stage, prospect_qualifiers_sale_qualifiers_path(:sale_stage => stage), :remote => true, data: { disable_with: "Loading...", toggle: 'tab' } %></li>
<% else %>
 <li><%= link_to stage, prospect_qualifiers_sale_qualifiers_path(:sale_stage => stage), :remote => true, data: { disable_with: "Loading...", toggle: 'tab' } %></li>
<% end %>

As you can see this compares the collection: @stages as: stage to the @stage variable passed from the controller action, determining whether they match - if so we make that the active tab.

The shared/panel partial:

<% if stage ==  @stage %>
 <div class="tab-pane active" id='<%=stage %>'>
    <table class="table table-striped display responsive no-wrap">
             <thead>
              <tr>
                 <th>Question</th>
                 <th>Answer</th>
                 <th>Action</th>
              </tr>
             </thead>
             <tbody>
             <!-- if a sale_qualifier exists with an answer show the following -->
              <% @sale_qualifiers.each do |qual| %>
              <tr>
                <td><%= qual.question.question_text %></td>
                <% if qual.question.answer_type == "Datetime"%>
                    <td><%= qual.answer.answer_text.to_date.readable_inspect %></td>
                <% elsif qual.question.answer_type == "Boolean" %>
                    <% if qual.answer.answer_text == 'true'%>
                        <td>Yes</td>
                    <% elsif qual.answer.answer_text == 'false' %>
                        <td>No</td>
                    <% end %>
                <% else %>
                    <td><%= qual.answer.answer_text %></td>
                <% end %>
                <td><%= link_to('edit', edit_sale_qualifier_path(qual), class: "btn btn-sm btn-warning", data: { disable_with: "Loading...", dismiss: 'modal' }, :remote => true )%></td>
              </tr>
              <% end %>
              <!-- if there's no sale_qualifier with the question id then build one -->
                <%= form_tag('/sale_qualifiers', :id => 'new_sale_qualifier', :class => 'form', method: :post, remote: true, data: { model: "sale_qualifier" }) do -%>
                    <%= fields_for :sale_qualifier do |ff| %>
                        <%= ff.hidden_field :sales_opportunity_id, :value => @sales_opportunity.id %>
                        <%= ff.hidden_field :question_id, :value => @question.id %>
                      <tr>
                        <td><%= @question.question_text %></td>
                        <td>
                          <%= ff.fields_for :answer_attributes do |answer| %>  
                            <div class="form-group">
                              <% if @question.answer_type == 'Text Field' %>
                                <%= answer.text_area :answer_text, :placeholder => "Enter your answer"%>
                              <% end %>
                              <% if @question.answer_type == 'Datetime' %>
                                <div class='input-group date' id='datetimepicker' data-date-format="YY.MM.DD">
                                  <%= answer.text_field :answer_text, class: "form-control", data: { date_format: 'YYYY/MM/DD' }, :placeholder => "YYYY/MM/DD" %>
                                  <span class="input-group-addon">
                                    <span class="glyphicon glyphicon-calendar"></span>
                                   </span>
                                </div>
                              <% end %>
                              <% if @question.answer_type == 'Boolean' %>
                                <%= answer.select :answer_text, [['Yes', true], ['No', false]] %>
                              <% end %>
                              <span class="warning-block"></span>
                              <span class="help-block"></span>
                             </div>
                          <% end %>
                        </td>
                        <td>
                            <%= submit_tag "Submit", class: "btn btn-large btn-success", id: "sale_qualifier_submit", data: { disable_with: "Submitting..." }, autocomplete: 'off' %>
                        </td>
                     </tr>
                    <% end %>     
                <% end %>   
            </tbody>
        </table>
 </div>
<% else %>
 <div class="tab-pane" id='<%=stage %>'>
 </div>
<% end %>

The pane does the same equivalence test and decides how to show the SaleQualifiers.

Here's the modal_form code:

$('#qualifier_tabs').html("<%= j render :partial => 'shared/tabs', collection: @stages, as: :stage %>");
$('#qualifier_panel').html("<%= j render :partial => 'shared/panel', collection: @stages, as: :stage %>");

The problem is that the pane shows the right SaleQualifiers but the active tab remains on "Need". If I put in <%= @stage %> and <%= stage %> outputs into the tab partial and also into the panel partial I get the reason - the tab panel always shows "Need" "Need" as the output from those erb snippets, whereas the pane panel shows whatever sale_stage is actually coming from my prospect_qualifiers action.

How can the prospect_qualifiers action (which calls the modal_form file and therefore renders both partials into the view) be passing different values for @stage and stage to these two partials when they're being rendered by the same controller action?

Edit - adding the show view

Per Chloe's request - here's the show view (or the relevant part of it) from the SalesOpportunity show page:

<div class="panel panel-default">
  <div  id="qualifier-tabs">
    <ul class="nav nav-tabs">
      <%= render partial: 'shared/tabs', collection: @stages, as: :stage %>
     </ul>
  </div>
  <div class="tab-content", id="qualifier_panel">
    <%= render partial: 'shared/panel', collection: @stages, as: :stage %>
  </div>
</div>

Solution

  • I see now that you have <div id="qualifier-tabs"> in your view, but $('#qualifier_tabs') in your JQuery.