Don Giulio
Don Giulio

Reputation: 3284

profile model for devise users on separate form

I have my devise users linked to a profile model with has_one :profile I would like to keep the initial user form very simple, with the standard username email and password. I would like then the users to be prompted the profile edit form at the first login, and I would like them to be forced to fill in some data.

at the moment my profile model is :

class Profile < ActiveRecord::Base
  attr_accessible :time_zone, :telephone, :country, :opt_out, 
    :first_name, :last_name, :address, :city, :postcode, :birthdate, 
    :currency_id

  belongs_to :currency

  validates_presence_of :telephone, :country, :first_name, :last_name, 
    :address, :city, :postcode, :birthdate, :currency

  belongs_to :user
end

my User model is:

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
    :recoverable, :rememberable, :trackable, :validatable, :confirmable

  before_create :initialize_user
  before_destroy :destroy_profile

  has_one :profile
  has_one :subscription

  attr_accessible :email, :password, :password_confirmation, :remember_me, 
    :username, :terms

  validates_acceptance_of :terms
  validates_presence_of :username

private
  def initialize_user
    generate_profile
    generate_free_subscription
  end

  def generate_free_subscription
    subscription = Subscription.new() do |s|
      s.expiration_date = nil
      s.plan = :free
      s.billing_name = username
      s.billing_street = "unknown"
      s.billing_zip = "unknown"
      s.billing_city = "unknown"
      s.billing_country = "unknown"
      s.billing_email = email
    end

    if subscription.save
      self.subscription = subscription
      self.roles = [:free]
    else
      msg = "Error generating free subscription for user, #{subscription.errors.to_yaml}"
      logger.error  msg
      raise msg
    end
  end

  def generate_profile
    p = Profile.new() do |p|
      p.daily_capital_exposure = 50
      p.risk_per_day = 60
      p.risk_per_trade = 30
      p.risk_per_week = 90
      p.user_id = self.id
      p.time_zone = "Rome"
    end

    if p.save
      self.profile = p
    else
      msg = "Error generating profile for user #{p.errors}"
      logger.error  msg
      raise msg
    end
  end

  def destroy_profile
    p = self.profile
    t = self.trades

    p.destroy
    t.destroy_all
  end
end

My problem is that when I create a User, the callback also creates its profile, which is missing some data and so fails creation of profile.

I wouldn't like to insert in profile temporary data just to make the profile validate correctly, because I would really like to have a nice way to force users to insert such information.

I guess my error is that I shouldn't be creating the profile at the time I create the User, but I'm not sure how else to make sure the Profile is created.

Upvotes: 1

Views: 1127

Answers (2)

Sankalp Singha
Sankalp Singha

Reputation: 4546

Try something like this to create a default profile in the beginning :

class User < ActiveRecord::Base
  rolify
  searchkick autocomplete: [:fullname]
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  has_one :profile

  before_create :build_default_profile



  private
  def build_default_profile
    # build default profile instance. Will use default params.
    # The foreign key to the owning User model is set automatically
    build_profile
    true # Always return true in callbacks as the normal 'continue' state
    # Assumes that the default_profile can **always** be created.
    # or
    # Check the validation of the profile. If it is not valid, then
    # return false from the callback. Best to use a before_validation
    # if doing this. View code should check the errors of the child.
    # Or add the child's errors to the User model's error array of the :base
    # error item
  end
end

This will create a profile when you create the user.

Also if you want to take the fullname during the registration itself, I do something like this :

#application_controller
 before_action :configure_permitted_parameters, if: :devise_controller?

private
    def configure_permitted_parameters
        devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:fullname, :email, :password, :password_confirmation) }
        devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:avatar, :fullname, :email, :password, :password_confirmation, :current_password) }
      end

This way, you should be able to take the firstname during the registration and the then create the profile and then after the user logs in you can redirect it to the profile creation page where the user can be asked to fill in the other details.

Hope I could help.

Upvotes: 2

Richard Peck
Richard Peck

Reputation: 76774

You could just use the on: option in your Profile validations:

#app/models/profile.rb
Class Profile < ActiveRecord::Base
   validates_presence_of :telephone, :country, :first_name, :last_name, 
    :address, :city, :postcode, :birthdate, :currency, on: :update #-> means this will not fire on create
end

--

In terms of building your Profile model on creation of a User, we use the following setup:

#app/models/user.rb
Class User < ActiveRecord::Base
   before_create :build_profile
end

This creates a profile for the User model upon creation of that parent model

Upvotes: 1

Related Questions