How to store data in session from multiple models?

2019-07-11 21:02发布

问题:

With the help of @blnc we were able to store a user's goal in his session. Then once he signed up, save that goal to his new account and delete it form the session.

As an additional step in the sign up process I want the user to also create a habit before being able to sign up.

goals_controller

  def create
    if current_user == nil
        # If there is no user, store the goal values to the session
        session[:goal_name] = goal_params[:name]
        session[:goal_deadline] = goal_params[:deadline]
        redirect_to signup_url
    else
        @goal = current_user.goals.build(goal_params)
        if @goal.save
            track_activity @goal
            redirect_to @goal, notice: 'Goal was successfully created'
        else
            flash.now[:danger] = 'Required Field: "Enter Goal"'
            render 'new'
        end
    end

I mimicked the actions for storing the goal, but it didn't work. When the user creates his goal, then habit, then signs up, upon seeing his new account only the goal was stored. The habit was never stored. It's not even there when I search for it in the console.

What am I doing wrong?

habits_controller

  def create
    if current_user == nil
      # If there is no user, store the goal values to the session.
      session[:habit_committed] = habit_params[:committed => []]
      session[:habit_date_started] = habit_params[:date_started]
      session[:habit_trigger] = habit_params[:trigger]
      session[:habit_action] = habit_params[:action]
      session[:habit_target] = habit_params[:target]
      session[:habit_reward] = habit_params[:reward]
      session[:habit_order] = habit_params[:order]
      redirect_to signup_url
    else
      @habit = current_user.habits.build(habit_params)
      if @habit.conceal == true
        @habit.save_with_current_level
        redirect_to @habit, notice: 'Habit was successfully created'
      elsif
        @habit.save_with_current_level
        track_activity @habit
        redirect_to @habit, notice: 'Habit was successfully created'
      else
        flash.now[:danger] = 'Required Fields: "Committed to", "Started", and "Enter Habit"'
        render 'new'
      end
    end
  end

  def new
    if current_user == nil
      @habit = Habit.new
    else
      @habit = current_user.habits.build
    end
  end

users_controller

  def create
    @user = User.new(user_params)
    if @user.save
      # Grab the session variable at the same time deleting it
      name = session.delete(:goal_name)
      deadline = session.delete(:goal_deadline)
      committed = session.delete(:habit_committed)
      date_started = session.delete(:habit_date_started)
      trigger = session.delete(:habit_trigger)
      action = session.delete(:habit_action)
      target = session.delete(:habit_target)
      reward = session.delete(:habit_reward)      
      #You can make this more complex for error handling
      @user.goals.create(name: name, deadline: deadline)
      @user.habits.create(committed: committed, date_started: date_started, trigger: trigger, action: action, target: target, reward: reward)
      @user.send_activation_email
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      render 'new'
    end
  end

From the terminal after I post a habit:

Started POST "/habits" for 127.0.0.1 at 2015-08-09 16:56:20 -0400
Processing by HabitsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"IWbim9fUlU0Ix43Z+WGpJkKhbhPyM7o0JeZ1KFY1X5ugGukOs6asvRatior0c7vBfmaRiNmKkl9O1bka5Bdrcg==", "habit"=>{"committed"=>["sun", "mon", "tue", "wed", "thu", "fri", "sat", ""], "date_started(2i)"=>"8", "date_started(3i)"=>"9", "date_started(1i)"=>"2015", "trigger"=>"", "action"=>"test", "target"=>"", "reward"=>"", "tag_list"=>"", "conceal"=>"0"}, "button"=>""}
Redirected to http://0.0.0.0:3000/signup
Completed 302 Found in 11ms (ActiveRecord: 0.0ms)


Started GET "/signup" for 127.0.0.1 at 2015-08-09 16:56:21 -0400
Processing by UsersController#new as HTML
  Rendered shared/_error_messages.html.erb (0.1ms)
  Rendered users/new.html.erb within layouts/application (3.8ms)
  Rendered layouts/_header.html.erb (0.4ms)
Completed 200 OK in 1047ms (Views: 1046.2ms | ActiveRecord: 0.0ms)

回答1:

Your code does not check for possible errors when creating Habit & Goal instances, and most likely a simple mistake prevents validation and thus persistence in the Database. ie

  @user = User.new(user_params)
    if @user.save
      ...
      @user.goals.create(...) # What if it fails ?
      @user.habits.create(...) # What if it fails ?
      @user.send_activation_email
      ...
      redirect_to root_url
    else
      render 'new'
    end

If the commented lines fail, you will not have any feedback, because you redirect_to another page. If those lines succeeded (=return false), they will save the Goal/Habit, but if they fail (and return false), well the rest of the process will execute as normal

You should do something like this :

@user = User.new(user_params)
    if @user.save
      ...
      unless @user.goals.create(...) # unless = if not
        # The warnings variable will be used for user notification
        (warnings ||= []) << "Warning ! Goal was not saved because of validation errors"
        # Use Rails logger for administrative detection of errors
        # => (Rails.logger .warning, .error, .info, .debug) will add to the `rails_root/log/#{environment}` log file, depending on the `:log_level` selected in your configuration
        Rails.logger.warn("Warning, auto-creation of Goal for user #{@user.name} on sign_up failed !")
      end
      unless @user.habits.create(...) # What if it fails ?
        (warnings ||= []) << "Warning ! Habit was not saved because of validation errors"
        Rails.logger.warn("Warning, auto-creation of Habit for user #{@user.name} on sign_up failed !")
      end

      @user.send_activation_email
      ...
      flash[:warning] = warnings.join('<br>').html_safe if warnings # Assuming you have some HTML code that renders flash[:warning]
      redirect_to root_url
    else
      render 'new'
    end

Of course you could also show why it failed using something like

unless goal= @user.goals.create(...)
  (warnings ||= []) << "Warning ! Goal was not saved because of validation errors : #{goal.errors.map{|k,v| "#{k} : #{v}"}.join('<br>')"
end