Ruby on Rails 6 Turbolinks Stripe表单值无法持久使用

问题描述

我正在尝试构建一个表格,允许用户预订他们希望参加的武术课。我创建了一个表单,该表单会根据用户所做的选择动态更改,当我更改任何选择选项时,表单都会更新,并且在我提交表单时,它将重定向到Stripe检出。我遇到的问题是提交表单后,我单击了浏览器的“后退”按钮或“条纹”结帐页面上提供的“后退”按钮,已更新的选择选项已还原为认选项,而不是已更新的选项。谁能帮助我纠正此行为并获取正确的表单元素以持久化? 这是我用来执行此操作的代码: 我在以下位置呈现表单的视图:

<% content_for :banner_title,@page_data['bannerTitle'] %>
<% content_for :head do %>
    <Meta name="turbolinks-cache-control" content="no-cache">
<% end %>

<div class="content container py-5">
    <div class="row">
        <div class="col-12 col-md-7 mx-auto">
            <%= render "forms/booking",options: @options %>
        </div>
    </div>
</div>

我正在使用的表单:

<%= form_for @booking,class: 'booking clearfix' do |f| %>
    <div class="form-group">
        <%= f.label(:class_name,"Select a class you wish to attend: ") %>
        <%= f.select(:class_name,options_for_select(options[:class_names],@booking.class_name),{},class: 'form-control' ) %>
    </div>

    <div class="form-group">
        <%= f.label(:date,"Select a date:") %>
        <%= f.select(:date,options_for_select( options[:dates],@booking.date ),class: 'form-control' ) %>
    </div>

    <div class="form-group">
        <%= f.label(:time,"Select a time: ")%>
        <%= f.select(:time,options_for_select(options[:times],@booking.time),class: 'form-control') %>
    </div>

    <div class="form-group">
        <%= f.label(:attendees,"How many attending: ") %>
        <%= f.select(:attendees,options_for_select(options[:attendees],@booking.attendees),class: 'form-control' )%>
    </div>
    <%= f.submit 'Place Booking',class: 'btn btn-primary btn-lg text-light float-right',id: 'create-booking' %>
<% end %>

<%= javascript_pack_tag 'booking_form' %>
<script src="https://js.stripe.com/v3/"></script>

表单的模型(我不使用ActiveRecord,我不知道这有什么区别吗?):

class Booking
    include ActiveModel::Model

    MAX_ATTENDEES = 10
    attr_accessor :time,:class_data,:attendees,:date,:class_name

    def initialize(args={})
        @time = args['time']
        @class_name = args['class_name']
        @class_data = args['class_data']
        @date = args['date']
        @attendees = args['attendees']
    end

    def day
        @date.split(',').first
    end

    def available_dates
        days_index_array = class_data['times'].keys.map {|k| day_index(k) }
        days_within( days_index_array ) 
    end

    def available_times
        if !date
            class_data['times'][class_data['times'].keys.first.downcase]
        else
            class_data['times'][day.downcase]
        end
    end

    def total_cost
        @class_data['cost'].to_i * @attendees.to_i
    end

    def attending_string
        ActionController::Base.helpers.pluralize(attendees,'person')
    end

    private

    def days_within(days,timeframe=1.month)
        start_date = Date.tomorrow
        end_date = start_date + timeframe
        (start_date..end_date).to_a.select {|k| days.include?(k.wday) }
    end

    def day_index(day)
        DateTime::DAYNAMES.index(day.to_s.capitalize)
    end
end

我正在调用控制器的新动作:

class BookingsController < ApplicationController
    include BookingsHelper
    before_action :set_class_data
    skip_before_action :set_page_data,except: :new

    def new
        set_booking
        
        # store values to be passed to the form helper method options_for_select. Each value must be an array populated with arrays with the format [value,text]
        @options = {
            class_names: @class_data.map {|c| [ c['name'],c['name'] ]},dates: @booking.available_dates.map {|d| [d.strftime('%A,%d %B'),d.strftime('%A,%d %B')] },times: @booking.available_times.map {|t| [t,t]},attendees: Booking::MAX_ATTENDEES.times.map {|i| [i+1,i+1]}
        }
    end

    def create
    end

    def booking_form_data
        booking_form_data = set_booking_form_data(params)    
        update_session_booking(booking_form_data)
        render json: booking_form_data
    end
    
    private
    
    def set_booking
        if session[:current_booking]
            pp "session exists"
            @booking = Booking.new(session[:current_booking])
        else
            pp "session does not exist"
            @booking = Booking.new
            session[:current_booking] = @booking.instance_values
        end
        set_booking_class_data
    end

    def set_booking_class_data
        !@booking.class_name ? @booking.class_data = @class_data.first.except('information') : @booking.class_data = @class_data.find {|cd| cd['name'] == @booking.class_name}.except('information')
    end
    
    def booking_params
        params.permit(:class_name,:time,:update_type)
    end

    def update_session_booking(booking_form_data)
        if params[:update_type] == 'class_name'
            session[:current_booking]['class_name'] = params[:class_name]
            session[:current_booking]['date'] = booking_form_data[:date_options].first
            session[:current_booking]['time'] = booking_form_data[:time_options].first
        elsif params[:update_type] == 'date'
            session[:current_booking]['date'] = params[:date]
            session[:current_booking]['time'] = booking_form_data[:time_options].first
        elsif params[:update_type] == 'time'
            session[:current_booking]['time'] = params['time']
        elsif params[:update_type] == 'attendees'
            session[:current_booking]['attendees'] = params[:attendees]
        elsif params[:update_type] == 'load'
            session[:current_booking] = booking_params.except(:update_type) 
        end
        pp "Session Booking: #{session[:current_booking]}"
    end

    def set_booking_form_data(params)
        booking_form_data = {}
        selected_class = @class_data.find {|cd| cd['name'] == params[:class_name] }
        # when the class_name select is changed
        if params[:update_type] == 'class_name'
            booking_form_data[:date_options] = days_within( selected_class['times'].keys.map {|k| day_index(k) } ).map {|d| d.strftime('%A,%d %B') }
            booking_form_data[:time_options] = selected_class['times'][booking_form_data[:date_options].first.split(',')[0].downcase]
        # when date select is changed
        elsif params[:update_type] == 'date'
            booking_form_data[:time_options] = selected_class['times'][params[:date].split(',')[0].downcase]
        end
        booking_form_data
    end
end

还有我用来更新表单的javascript:

getBookingFormData = (bodyData={},successCallback=()=>{}) => {
    $.ajax({
        url: '/booking_form_data',method: 'POST',beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token',$('Meta[name="csrf-token"]').attr('content'))},data: bodyData,success: successCallback
    })
}

createOptions = (values) => {
    let newOptions = [];
    $.each(values,(index,value) => {
        let newOption = $('<option></option>');
        newOption.attr('value',value);
        newOption.text(value);
        newOptions.push(newOption)
    })
    return newOptions
}

appendOptions = (options,element) => {
    $(element).empty();
    $(element).append(options)
}

currentFormValues = () => {
    return {
        class_name: $('#booking_class_name').val(),date: $('#booking_date').val(),time: $('#booking_time').val(),attendees: $('#booking_attendees').val()
    }
}

$('select#booking_class_name').on('change',() => {
    let bodyData = { 
        class_name: $('select#booking_class_name').val(),update_type: 'class_name'
    }
    let successCallback = (res) => {
        let dateOptions = createOptions(res.date_options);
        let dateSelect = $('select#booking_date');
        let timeOptions = createOptions(res.time_options);
        let timeSelect = $('select#booking_time');
        appendOptions(dateOptions,dateSelect);
        appendOptions(timeOptions,timeSelect);
    }
    getBookingFormData(bodyData,successCallback)
});

$('select#booking_date').on('change',() => {
    let bodyData = {
        class_name: $('select#booking_class_name').val(),date: $('select#booking_date').val(),update_type: 'date'
    };
    let successCallback = (res) => {
        let timeOptions = createOptions(res.time_options);
        let timeSelect = $('select#booking_time');
        appendOptions(timeOptions,successCallback)
});

$('select#booking_time').on('change',() => {
    let bodyData = {
        time: $('select#booking_time').val(),update_type: 'time'
    };
    getBookingFormData(bodyData);
});

$('select#booking_attendees').on('change',() => {
    let bodyData = {
        attendees: $('select#booking_attendees').val(),update_type: 'attendees'
    };
    getBookingFormData(bodyData);
});


$('#create-booking').on('click',(e) => {
    e.preventDefault();
    bookingDefault = false
    const stripe = Stripe(process.env.STRIPE_PUBLIC);
    
    let requestHeaders = new Headers({
        'X-CSRF-Token': $('Meta[name="csrf-token"]').attr('content'),'Content-Type': 'application/json'
    })

    fetch('/create_checkout_session',{ 
        method: 'POST',headers: requestHeaders,body: JSON.stringify(currentFormValues()) 
    })
    .then((res) => { return res.json() })
    .then((session) => { return stripe.redirecttocheckout({ sessionId: session.id }) })
    .then((result) => { 
        if (result.error) { alert(result.error.message) }
    })
    .catch((error) => { console.error('Error: ',error) })
})

从我读过的书中,我认为这可能是与缓存有关的问题,这使我认为这是Turbolink的问题,但我可能完全错了。香港专业教育学院试图添加禁用涡轮链接或强制其重新加载页面的元标记,但它们似乎没有用。 我已经坚持了好几天,所以对任何输入都将不胜感激。让我知道您是否需要更多信息

解决方法

这与Stripe无关,而与您的表单价值管理有关。如果要保留这些值,则需要以某种方式将其构建到前端应用程序中。有很多选择:

  • 使用本地存储
  • 使用查询参数(如果信息不敏感)
  • 使用Cookie和服务器会话,您可以使用default value重新获取f.select选项并将其混合。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...