Persisting form input over login

2019-08-30 10:48发布

I have this app where a user can write a review for a school. A user must sign in with Facebook to save a review. The problem is if a user is unsigned and writes a review, then signs in with Facebook they have to write the same review again.

I am trying to fix this by storing the review data form in sessions, but I cant quite make it work.

What is the proper rails way to do this?

ReviewForm:

<%= form_for [@school, Review.new] do |f| %>
 <%= f.text_area :content %>
    <% if current_user %>
      <%= f.submit 'Save my review', :class => "btn" %>
    <% else %>
      <%= f.submit 'Save my review and sign me into facebook', :class => "btn" %>
    <% end %>
<%end %>

ReviewController

class ReviewsController < ApplicationController
    before_filter :signed_in_user, only: [:create, :destroy]

    def create
        @school = School.find(params[:school_id])
        @review = @school.reviews.new(params[:review])

        @review.user_id = current_user.id

        if @review.save
            redirect_to @review.school, notice: "Review has been created."
        else
            render :new
        end
    end

    def new
        @school = School.find_by_id(params[:school_id])
        @review = Review.new
    end

    def save_review(school, review, rating)
        Review.create(:content => review, :school_id => school, 
                       :user_id => current_user, :rating => rating)
    end

    private 
    def signed_in?
        !current_user.nil?
    end

    def signed_in_user
        unless signed_in?
            # Save review data into sessions
            session[:school] = School.find(params[:school_id])
            session[:review] = params[:review]
            session[:rating] = params[:rating] 
            # Login the user to facebook
            redirect_to "/auth/facebook"
            # After login save review data for user
            save_review(session[:school], session[:review], session[:rating])
        end
    end
end

2条回答
可以哭但决不认输i
2楼-- · 2019-08-30 11:12

My understanding is that it's not "The Rails Way" to store things in the session besides really tiny stuff like a user token, etc. You can read more about that idea in The Rails 3 Way by Obie Fernandez.

I would recommend that you store reviews in the database right from the start and only "surface" the review after the review has been connected to a Facebook-authenticated user. If you have any curiosities regarding how to accomplish that, I'm happy to elaborate.

Edit: here's a little sample code. First I'd take care of associating users with reviews, for "permanent" storage. You could just add a user_id to the review table, but it would probably be null most of the time, and that seems sloppy to me:

$ rails g model UserReview review_id:references, user_id:references

Then I'd create a user_session_review table with a review_id and a user_session_token. This is for "temporary" storage:

$ rails g model UserSessionReview review_id:integer, user_session_token:string

Then when a user signs up, associate any "temporary" reviews with that user:

class User
  has_many :user_reviews
  has_many :reviews, through: :user_reviews
  has_many :user_session_reviews

  def associate_reviews_from_token(user_session_token)
    temp_reviews = UserSessionReview.find_all_by_user_session_token(user_session_token)
    temp_reviews.each do |temp_review|
      user_reviews.create!(review_id: temp_review.review_id)
      temp_review.destroy
    end
  end
end

So in your controller, you might do

class UsersController < ApplicationController
  def create
    # some stuff
    @user.associate_reviews_from_token(cookies[:user_session_token])
  end
end

You'll of course have to read between the lines a little bit, but I think that should get you going.

Edit 2: To delete old abandoned reviews, I'd do something like this:

class UserSessionReview
  scope :old, -> { where('created_at < ?', Time.zone.now - 1.month) }
end

Then, in a cron job:

UserSessionReview.old.destroy_all
查看更多
我命由我不由天
3楼-- · 2019-08-30 11:20

You should save the review in the create sessions action (which is not included in your question). Assuming you are using omniauth, you can add something on the action that handles the callback

# review controller
def signed_in_user
  unless signed_in?
    # Save review data into sessions
    session[:school] = School.find(params[:school_id])
    session[:review] = params[:review]
    session[:rating] = params[:rating]

    # Login the user to facebook
    redirect_to "/auth/facebook"   
  end
end

# callback to login the user
def handle_callback
  # do your thing here to login the user
  # once you have the user logged in
  if signed_in?
    if session[:school] && session[:review] && session[:rating] # or just 1 check
      Review.create(
        content: session.delete(:review),
        school_id: session.delete(:school), 
        user_id: current_user.id,
        rating: session.delete(:rating)
      )
      #redirect_to somewhere
    end
  end
end

I used delete so the session will be cleared of these values.

UPDATE: since you're using a session controller

class SessionsController < ApplicationController
  def create
    if user = User.from_omniauth(env["omniauth.auth"])
      session[:user_id] = user.id 

      if session[:school] && session[:review] && session[:rating] # or just 1 check
        review = Review.new
        review.content = session.delete(:review)
        review.school_id = session.delete(:school)
        review.user_id = user.id
        review.rating = session.delete(:rating)
        review.save
      end
    end

    redirect_to :back
  end
查看更多
登录 后发表回答