Rails 4 - setup, automatically create profile when new user created

时间:2015-12-10 01:22:47

标签: ruby-on-rails path routes resources

I have tried to tidy this post up so it doesnt seem so long for new comers. It includes errors resulting from following suggestions from others in the answers below.

I am trying to make an app with Rails 4.

I use devise and omniauth.

I have models for user and profile. User has one profile and profile belongs to user.

My aim is to use the user model for all the things that the user doesn't really touch. I use the profile model for things the user does maintain.

When a new user is created, I want the user to be redirected to their profile page, which should be partly populated with details created in the user model.

I'm having trouble.

I have:

User.rb

has_one :profile

Profile.rb

belongs_to :user

Omniauth_callbacks_controller.rb

if @user.persisted?
  sign_in_and_redirect_user(:user, authentication.user.profile)
  set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
  session["devise.#{provider}_data"] = env["omniauth.auth"]
  redirect_to new_user_registration_url
end

I have also tried in the call back redirect:

 sign_in_and_redirect @user,  event: :authentication

profiles/show.html.erb

<div class="container-fluid">
    <div class="row">
        <div class="col-xs 8 col-xs-offset-1">
            <%= @profile.user.formal_name %>


            <%= @profile.occupation %>
            <%= @profile.qualification.awards %>
            <%= @profile.overview %>
            <%= @profile.research_interests %>

        </div>

        <div class="col-xs 3">
            <div class="geobox">
            <%= render 'addresses/location' %>
            <%= @profile.working_languages %>
            <%= @profile.external_profile %>

            </div>
        </div>  
    </div>

    <div class="row">
        <div class="col-xs 10 col-xs-offset-1">
            <%= render 'profiles/activity' %>
        </div>
    </div>  

</div>

_nav.html.erb

<% if user_signed_in? %>
                    Hi <%= link_to(current_user.first_name.titlecase, profile_path(current_user)) %></span>

I have also tried:

_nav.html.erb

<% if user_signed_in? %>
                    Hi <%= link_to(current_user.first_name.titlecase, user_profile_path(current_user.profile)) %></span>

What do I need to do to add some kind of functionality to let a new user go to their profile page?

Currently, when I try logging in and clicking on the nav bar link, I want to go to the profile page. Instead, I go to an error message which says the page I am looking for does not exist. The path it is looking for starts at profile (rather than user/profile) -not sure if that's a clue.

My routes are:

Profiles:

profiles GET       /profiles(.:format)                    profiles#index
                         POST      /profiles(.:format)                    profiles#create
             new_profile GET       /profiles/new(.:format)                profiles#new
            edit_profile GET       /profiles/:id/edit(.:format)           profiles#edit
                 profile GET       /profiles/:id(.:format)                profiles#show
                         PATCH     /profiles/:id(.:format)                profiles#update
                         PUT       /profiles/:id(.:format)                profiles#update
                         DELETE    /profiles/:id(.:format)                profiles#destroy

User:

user_password POST      /users/password(.:format)              devise/passwords#create
       new_user_password GET       /users/password/new(.:format)          devise/passwords#new
      edit_user_password GET       /users/password/edit(.:format)         devise/passwords#edit
                         PATCH     /users/password(.:format)              devise/passwords#update
                         PUT       /users/password(.:format)              devise/passwords#update
cancel_user_registration GET       /users/cancel(.:format)                users/registrations#cancel
       user_registration POST      /users(.:format)                       users/registrations#create
   new_user_registration GET       /users/sign_up(.:format)               users/registrations#new
  edit_user_registration GET       /users/edit(.:format)                  users/registrations#edit
                         PATCH     /users(.:format)                       users/registrations#update
                         PUT       /users(.:format)                       users/registrations#update
                         DELETE    /users(.:format)                       users/registrations#destroy
       user_confirmation POST      /users/confirmation(.:format)          devise/confirmations#create
   new_user_confirmation GET       /users/confirmation/new(.:format)      devise/confirmations#new
                         GET       /users/confirmation(.:format)          devise/confirmations#show
             user_unlock POST      /users/unlock(.:format)                devise/unlocks#create
         new_user_unlock GET       /users/unlock/new(.:format)            devise/unlocks#new
                         GET       /users/unlock(.:format)                devise/unlocks#show
           finish_signup GET|PATCH /users/:id/finish_signup(.:format)     users#finish_signup

NEW ATTEMPT: Further to the suggestion below, I nested the resources as:

  resources :users do
     resources :profile
  end

and I changed the redirect to:

def self.provides_callback_for(provider)
    class_eval %Q{
      def #{provider}
        @user = User.find_for_oauth(env["omniauth.auth"])

        if @user.persisted?
           sign_in_and_redirect [current_user, current_user.profile || current_user.build_profile]

          set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
        else
          session["devise.#{provider}_data"] = env["omniauth.auth"]
          redirect_to new_user_registration_url
        end
      end
    }
  end

I get this error: NoMethodError (undefined method `profile' for nil:NilClass):

NEXT ATTEMPT:

I keep all of the above, but try replacing the redirect like so:

def self.provides_callback_for(provider)
    class_eval %Q{
      def #{provider}
        @user = User.find_for_oauth(env["omniauth.auth"])

        if @user.persisted?
           sign_in_and_redirect [@user, @user.profile || @user.build_profile]

          set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
        else
          session["devise.#{provider}_data"] = env["omniauth.auth"]
          redirect_to new_user_registration_url
        end
      end
    }
  end

RuntimeError (Could not find a valid mapping for [#<User id: 1 ... 

and then it lists all the attributes in the user table, and then shows:

#<Profile id: nil, user_id: 1, 

followed by all the attributes in the profiles table.

A NEW ATTEMPT

I found this post: Rails Trying to create a profile after the user has been created

And I tried adding:

 after_create do
    create_user_profile
  end

to my user model.

I kept the redirect the same as I had it (following Alexei's suggestion) and tried again.

I get the same error message:

RuntimeError (Could not find a valid mapping for [#<User id: 1

Can anyone see how to solve this?

A FURTHER ATTEMPT:

I then found this post: Ruby on Rails - Creating a profile when user is created

which says for Rails 4 (which I use), I need this callback only:

after_create :create_profile

When I try this, I get the same error as above.

AN IDEA:

I used scaffolding generator for my profile controller.

My create method is:

  def create
    @profile = Profile.new(profile_params)

    respond_to do |format|
      if @profile.save
        format.html { redirect_to @profile }
        format.json { render :show, status: :created, location: @profile }
      else
        format.html { render :new }
        format.json { render json: @profile.errors, status: :unprocessable_entity }
      end
    end
  end

Is this error maybe something to do with the user id not being specifically referenced in this method. I have whitelisted user_id in the strong params for the profiles controller.

A NEW ATTEMPT

This article suggests using build_profile instead of create_profile.

https://stackoverflow.com/questions/8337682/create-another-model-upon-user-new-registration-in-devise

I changed my user.rb to

  after_create :build_profile

When I save this and try I get the same error.

ANOTHER ATTEMPT

Matt's answer in this post suggests that I have to include a definition of build_profile as:

 after_create :build_profile

  def build_profile
    Profile.create(user: self) # Associations must be defined correctly for this syntax, avoids using ID's directly.
  end

I changed my user.rb to:

  after_create :build_profile

  def build_profile
    Profile.create(user: self) # Associations must be defined correctly for this syntax, avoids using ID's directly.
  end

When I save and try this, I get a similar error, but with more information. The logs still show:

RuntimeError (Could not find a valid mapping for [#<User id: 1,

But after all of that error message, it gives this new piece of information:

SQL (2.9ms)  INSERT INTO "profiles" ("user_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["user_id", 1], ["created_at", "2015-12-18 21:58:49.993866"], ["updated_at", "2015-12-18 21:58:49.993866"]]
2015-12-18T21:58:49.966648+00:00 app[web.1]:   Profile Load (1.5ms)  SELECT  "profiles".* FROM "profiles" WHERE "profiles"."user_id" = $1 LIMIT 1  [["user_id", 1]]
2015-12-18T21:58:50.007428+00:00 app[web.1]: Completed 500 Internal Server Error in 113ms (ActiveRecord: 21.8ms)
2015-12-18T21:58:49.941385+00:00 app[web.1]:   User Load (1.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 1]]

Does this offer any light on the problem?

UPDATE

I tried this again (without making any changes to the code). Now I get an error, but this time the logs say:

RuntimeError (Could not find a valid mapping for [#<User id: 1, first_name: "Me", last_name: "Ma", email: "mema@gmail.com", encrypted_password: "$2a$jqQn..HSvlcNtfAH/28.DQoWetO...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 16, current_sign_in_at: "2015-12-15 09:57:14", last_sign_in_at: "2015-12-15 09:27:07", current_sign_in_ip: #<IPAddr: IPv4:49.191.55>, last_sign_in_ip: #<IPAddr: IPv4:49.191.135.255.255>, confirmation_token: nil, confirmed_at: "2015-12-15 09:02:58", confirmation_sent_at: "2015-12-15 08:42:48", unconfirmed_email: nil, failed_attempts: 0, unlock_token: nil, locked_at: nil, created_at: "2015-12-09 23:53:20", updated_at: "2015-12-15 09:57:14", image: nil>, #<Profile id: 1, user_id: 1, title: nil, hero_image: nil, overview: nil, occupation: nil, external_profile: nil, working_languages: nil, created_at: "2015-12-18 21:58:49", updated_at: "2015-12-18 21:58:49">]):

The difference this time is that the profile has a user id key of 1. Previously, this was nil.

2 个答案:

答案 0 :(得分:1)

您可以使用嵌套资源

resources :users do
  resource :profile
end

然后

redirect_to [current_user, current_user.profile || current_user.build_profile]

答案 1 :(得分:0)

您已在回调控制器中添加了sing_in_and_redirect_user。 要编辑它重定向到的路径,还应覆盖和编辑 after_sign_in_path_for中的applications_controller

class ApplicationController < ActionController::Base
  def after_sign_in_path_for(resource)
    request.env['omniauth.origin'] || stored_location_for(resource) || root_path
  end
end

您可以在此处编辑重定向到的实际路径。不确定omniauth部分,但您可以添加profile_path(resource.profile)

参考: https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in