Allow anonymous/guest user to “try out” functional

2019-03-20 19:07发布

I'm developing a Rails 3 app using Devise and CanCan.

The app allows anonymous (not registered) users to access some of the app, and registered users to access other parts.

One aspect of the app (a yoga workout app) is that users can create Yoga sequences by stringing together yoga poses, then they can play them back in a video player.

I currently have all the functioning for registered users, but now want to allow anonymous users to be able to create a sequence, and play it back, but to "save" it (ie. be able to use it again on any subsequent session), they need to register.

Currently, this requires the actual creation of a Sequence model instance, which belongs to a User (simplified below)

class Sequence < ActiveRecord::Base

  validates_presence_of :name
  validate :status_must_be_private_if_user_nil

  # Associations
  has_many :ordered_asana_sequences, :class_name => "OrderedAsanaSequence", :order => 'position ASC', :dependent => :destroy
  has_many :asanas, :through => :ordered_asana_sequences

  belongs_to :user


  def status_must_be_private_if_user_nil
  errors.add(:status, "must be private is user is nil") if
    user == nil and status != :private
  end
end

But, of course, Anonymous users do not have a User model.

I have sort of hacked something up where Sequences DO belong to Users, but its not mandatory (user_id can be nil, as you can see above), and the ability to edit is secured through CanCan:

class Ability
    {snip}
    # A guest can play with creating sequences
    can :create, [Sequence]
    # A guest can edit/update their own Sequences
    can :update, [Sequence], :user_id => user.id
end

But, this means that any anonymous user (user_id = nil) can edit any other anonymous user's sequences. Which, I guess isn't the end of the world, but, I would prefer it to be more secure.

Here are the potential ways I've thought of doing it:

'Register' anonymous users

  • If a guest user goes to make a sequence, generate an email and password and 'register' this "guest" user. Then assign the new sequence model to this new user. If the user decides to register for real, just update the existing user record with the new email and password.
  • Advantages: Very few changes to existing app - only need to deal with it in the "registration" process.
  • Drawbacks: may end up with lots of dead user accounts that I need to sweep up after

Don't persist the Sequence, but serialize it and store it in the session

  • If a guest user goes to make a sequence, when they want to "save" it, shoot it to a controller method that serializes the model and stores it in the session. Then, the Sequence player and, I guess, the Sequence Editor can optionally pull it from the session instead of the Datastore
  • Advantages: No creation of user accounts for guests, so no need for cleanup. No need to deal with modifying Abilities, as the guest won't be fetching models from the Datastore, but from their session.
  • Disadvantages: Seems hacky. Will need a lot of touchpoints in the app to determine where to get the sequence from. Higher potential for bugs

Figure out some other way to assign a Sequence to a guest user, other then making the user_id nil

  • Advantages: It will be brilliant
  • Disadvantages: No clue what to do.

I'm hoping that there's something I haven't seen in Devise (inviteable, perhaps?) that would be cleaner then rolling it all myself.

Or, does anybody have any suggestions for this problem? This seems like a relatively common requirement, but was unable to unearth any guidance.

Thanks!

2条回答
兄弟一词,经得起流年.
2楼-- · 2019-03-20 19:14

In Ryan's introduction to CanCan he offers the following suggestion:

Make a New User object in memory for guest users on the site, but don't save it. This way all of your functions that need to associate to a user will still work, but they won't save.

See the railscast here: http://railscasts.com/episodes/192-authorization-with-cancan

Ryan's code example is:

class Ability  
  include CanCan::Ability  

  def initialize(user)  
    user ||= User.new # This initializer is creating your memory-only guest users

    if user.role? :admin  
      can :manage, :all  
    else  
      can :read, :all  
      can :create, Comment  
      can :update, Comment do |comment|  
        comment.try(:user) == user || user.role?(:moderator)  
      end  
      if user.role?(:author)  
        can :create, Article  
        can :update, Article do |article|  
          article.try(:user) == user  
        end  
      end  
    end  
  end  
end

So, in your app if you used this approach, then in your view you could check for current_user.new_record?, and rendering a different "save" button for registered users versus guests.

You could make this pretty simple (avoiding storing this in session etc.) by providing a hidden account signup form on the sequence creation page. Then just make your "save" button for guests reveal the account creation form, and when they submit that form they're submitting a user registration and a sequence creation at the same time.

Then all your Sequences#create action needs to do is something like:

...
current_user.update_attributes(params[:user]) if current_user.new_record?

if current_user.sequences.create(params[:sequence])
   redirect_to ...
else
   render ...
end
...

You'll need to turn that into working code but I'm confident the basic idea would work.

Good luck!

查看更多
爷的心禁止访问
3楼-- · 2019-03-20 19:20

I'm also working on a rails 3 project w/ devise and cancan. My needs are a little different in that I need to persist anonymous users' activity in the db (no need to sweep). Here's what I did to sign in the anonymous user. Hope this helps.

def sign_in_anonymous_user
  unless user_signed_in?
    user = User.new
    role = "anonymous"
    user.confirmed_at = Time.now-1.minute
    user.save :validate => false
    sign_in :user, user
  end
end
查看更多
登录 后发表回答