如何从一次提交更新两个_form?

How to update two _form from one submit?

如果 current_user 选择更新他的电子邮件,而不必使用单独的提交按钮,他如何才能更新他的电子邮件?

当前代码

<%= form_for(@challenge)  do |challenge| %>
  <%= challenge.action %>
  <%= challenge.send_email %>
<% end %>

<%= form_for(current_user)  do |user| %>
  <%= user.email %>
  <%= user.submit %>
<% end %>

<%= form_for(@challenge)  do |challenge| %>
  <%= challenge.submit %>
<% end %>

总体思路

<%= form_for(@challenge)  do |challenge| %>
  <%= challenge.action %>
  <%= challenge.send_email %>

  <%= form_for(current_user)  do |user| %> # Or somehow remove form_for(current_user) altogether while still being able to update current_user.email within the form_for(@challenge)
    <%= user.email %>
  <% end %>

  <%= challenge.submit %>
<% end %>

表格的图片

提供控制器代码,看看我们是否可以通过 fields_for

完成这项工作

challenges_controller

class ChallengesController < ApplicationController
  before_action :set_challenge, only: [:show, :edit, :update, :destroy, :challenging, :mark_accomplished, :mark_completed, :create_freebie, :like]
  respond_to :html, :json

  def show
    @challenge_to_deadline = current_user.challenges.group_by {|i| i.deadline} if current_user
    @notable = @challenge
    @notes = @notable.notes
    @note = Note.new
    @commentable = @challenge
    @comments = @commentable.comments
    @comment = Comment.new
    @correct_user = current_user.challenges.find_by(id: params[:id])
  end

  def new
    @challenge = Challenge.new
    respond_modal_with @challenge, location: root_path
  end

  def edit
  end

  def create
    @challenge = Challenge.new(challenge_params)
    if params[:step] == '2'
      if current_user == nil
        # If there is no user, store the lifetime values to the session.
        session[:challenge_action] = challenge_params[:action]
        session[:challenge_committed] = challenge_params[:committed]
        session[:challenge_deadline] = [params["challenge"]["deadline(3i)"], params["challenge"]["deadline(2i)"], params["challenge"]["deadline(1i)"]].join('/')
        session[:challenge_date_started] = [params["challenge"]["date_started(3i)"], params["challenge"]["date_started(2i)"], params["challenge"]["date_started(1i)"]].join('/')
        session[:challenge_order] = challenge_params[:order]
        session[:challenge_days_challenged] = challenge_params[:days_challenged]
        session[:challenge_why] = challenge_params[:why]
        session[:challenge_conceal] = challenge_params[:conceal]
        redirect_to signup_path
      else
        @challenge = current_user.challenges.build(challenge_params)
        if @challenge.conceal == true
          @challenge.save
          redirect_to root_path
          if @challenge.date_started.present?
            flash[:info] = 'habit Challenge secretly saved! Click "Strike 1" upon incompleting a day.'
          else
            flash[:info] = 'goal Challenge secretly saved! Click checkmark upon completing it.'
          end
        elsif
          @challenge.save
          track_activity @challenge
          redirect_to root_path
          if @challenge.date_started.present?
            flash[:info] = 'habit Challenge saved! Click "Strike 1" upon incompleting a day.'
          else
            flash[:info] = 'goal Challenge saved! Click checkmark upon completing it.'
          end
        else
          respond_modal_with @challenge
        end
      end
    end
  end

  def update
    @challenge.update(challenge_params)
    flash[:info] = 'Challenge updated'
    respond_modal_with @challenge, location: root_path
  end

private

  def set_challenge
    @challenge = Challenge.find(params[:id])
  end

  def challenge_params
    params.require(:challenge).permit(
      :action,
      :why, 
      :like, 
      :deadline, 
      :accomplished, 
      :tag_list, 
      :conceal,
      :archive,
      :trigger,
      :missed_days,
      :target, 
      :reward,
      :order,
      :date_started,
      :date_ended,
      :days_challenged,
      :completed_at,
      :freebie,
      :freebie_date, 
      :send_email => [],
      :committed => [])
  end
end

users_controller

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
  before_action :correct_user,   only: [:edit, :update]

  def show
    @user = User.find(params[:id])
    @past_challenges = @user.challenges.publish.order("deadline ASC").select{ |challenge| challenge.deadline < Date.current if challenge.deadline.present? }
    @past_challenges_by_years = @past_challenges.group_by { |t| t.deadline.beginning_of_year }
    @present_oneshot_challenges = @user.challenges.unaccomplished.publish.order("deadline ASC").select{ |challenge| challenge.deadline == Date.current if challenge.deadline.present? }
    @present_habit_challenges = @user.challenges.unaccomplished.publish.order("date_started DESC").select{ |challenge| challenge.date_started <= Date.tomorrow if challenge.date_started.present? }
    @future_challenges = @user.challenges.unaccomplished.publish.order("deadline ASC").select{ |challenge| challenge.deadline > Date.current if challenge.deadline.present? }
    @future_challenges_by_years = @future_challenges.group_by { |t| t.deadline.beginning_of_year }
    @inspirations = @user.inspirations.publish
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      action = session.delete(:challenge_action)
      deadline = session.delete(:challenge_deadline)
      committed = session.delete(:challenge_committed)
      date_started = session.delete(:challenge_date_started)
      order = session.delete(:challenge_order)
      days_challenged = session.delete(:challenge_days_challenged)
      why = session.delete(:challenge_why)
      conceal = session.delete(:challenge_conceal)
      # Create
      if deadline.present?
        @user.challenges.create(action: action, deadline: deadline, why: why, conceal: conceal, date_started: date_started, committed: committed, days_challenged: days_challenged)
      end
      @user.send_welcome_email
      log_in @user
      redirect_to tutorial_url
      flash[:info] = 'Let the games begin! Add another challenge with + Challenge'
    else
      render 'new'
    end
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      redirect_to root_url
      flash[:success] = "Settings updated"
    else
      render 'edit'
    end
  end

  private

  def user_params
    if params[:conceal] = true
      params.require(:user).permit(:time_zone, :name, :email, :tag_list, :password, :conceal, inspirations_attributes: [:name, :tag_list, :conceal], activities_attributes: [:conceal, :action, :trackable_id, :trackable_type])
    else
      params[:user][:conceal] = false
      params.require(:user).permit(:time_zone, :name, :image, :tag_list, :email, :password, inspirations_attributes: [:name, :tag_list], activities_attributes: [:action, :trackable_id, :trackable_type])
    end
  end

  # Confirms a logged-in user.
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please sign in first"
        redirect_to root_url
      end
    end

    # Confirms the correct user.
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end
end

在另一个 form 中嵌入 form 无效 html。一个页面中可以有多个表单,但它们不能相互嵌入。

如果您希望不同模型的字段具有相同的表单,根据这些模型之间的关联,您可以使用 form_for 中的 fields_for 来呈现表单字段从那些模型。然后在提交表单时,再次根据模型之间的关联,您可以保留数据。

您还可以使用表格的 javascript 和 submit/update 部分。例如:当 text_field 的内容发生变化时,您可以触发 AJAX 请求并持久化数据。

有关详细信息,请参阅 FormHelper#fields_for

更新:

根据您在评论中的回复,您拥有以下型号:

class User < ActiveRecord::Base
  has_many :challenges
end

class Challenge < ActiveRecord::Base
  belongs_to :user
end

你有两种方法。

方法 1:通过 controller 操作过滤器。从controller中的params截取用户的email并更新。类似于:

class ChallengesController < ApplicationController
  before_action :update_user_email, if: proc {|c| c.current_user.present? && c.params[:email].present? }
  private
  def update_user_email
    email = params[:email]
    current_user.update_attribute(:email, email)
  end
end

并更新您的表单代码如下:

<%= form_for(@challenge)  do |challenge| %>
  # include fields for challenge

  <%= text_field_tag :email, current_user.email %>

  <%= challenge.submit %>
<% end %> 

方法 2:通过 model 回调。在模型中使用 attr_accessorcallbacks 的组合。

class Challenge < ActiveRecord::Base
  belongs_to :user

  attr_accessor :user_email
  before_save :update_user_email, if: :user_email_present?

  protected
  def user_email
    self.user.email if self.user.present?
  end 

  def user_email_present?
    self.user_email.present?
  end

  def update_user_email
    # based on your logic in controller, looks like the 
    # `current_user` and the `user` of the `challenge` 
    # are one and the same. So you could directly update
    # `email` on the `user` object.
    self.user.update_attribute(:email, self.user_email)
  end
end

并更新您的表单代码如下:

<%= form_for(@challenge)  do |challenge| %>
  # include fields for challenge

  <%= challenge.text_field :user_email, current_user.email %>

  <%= challenge.submit %>
<% end %>