Creating an empty profile on devise registration - ruby-on-rails

I have used devise for authentication in a rails app and I want a user to be able to sign up and once signed up be able to edit their own profile. I've followed along with other answers on stack but when I try and register using the default devise registration form I'm getting this error.
NoMethodError in Devise::RegistrationsController#create
undefined method `create' for nil:NilClass
app/models/user.rb:17:in `create_profile'
My User.rb is as follows
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile
attr_accessible :login, :username, :email, :password, :password_confirmation, :remember_me, :profile_attributes
attr_accessor :login
accepts_nested_attributes_for :profile
validates :username, :uniqueness => { :case_sensitive => false }, :presence => true
after_create :create_profile
def create_profile
self.profile.create
end
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["username = :value OR lower(email) = lower(:value)", { :value => login }]).first
else
where(conditions).first
end
end
end
Profile.rb is
class Profile < ActiveRecord::Base
attr_accessible :fax, :phone_1, :phone_2, :url
belongs_to :user
end
profiles controller is the norm apart from the edit action
def edit
#profile = current_user.profile
end

You need to use self.create_profile instead of self.profile.create for has_one association.
Example
An Account class declares has_one :beneficiary, which will add:
Account#beneficiary (similar to Beneficiary.where(account_id: id).first)
Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save)
Account#build_beneficiary (similar to Beneficiary.new("account_id" => id))
Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b)
Account#create_beneficiary! (similar to b = Beneficiary.new("account_id" => id); b.save!; b)
More docs here

Related

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.
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.
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

Initializing user profile on user creation with the same form with Devise 3.2 Rails 4.1

Each user in the application has a profile that has to be filled out by the user when registering. The user and profile classes are as follows:
user.rb:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :rememberable, :trackable, :validatable
has_one :profile
end
profile.rb:
class Profile < ActiveRecord::Base
belongs_to :user
end
the view form:
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
= f.input :email, required: true, autofocus: true
= f.simple_fields_for :profile do |pf|
= pf.input :name
= pf.input :bio
= f.input :password, required: true
= f.input :password_confirmation, required: true
= f.button :submit
The problem is that the profile object needs to be initialized before the form is rendered.
I decided to override the new method of the Devise::RegistrationsController:
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
def new
build_resource({}) # copied from super
resource.build_profile # my custom initialization code
respond_with self.resource # copied from super
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << { profile_attributes: [:name, :bio] }
end
end
This doesn't seem to be very DRY since I am duplicating the code in the super new method. I might also break things if the super controller method new changes when the gem is upgraded. Any better way to override the resource (user) creation without duplicating code?
You can try to only change the User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :rememberable, :trackable, :validatable
has_one :profile
accepts_nested_attributes_for :profile
def profile
super || build_profile
end
end

Rails: undefined method `primary_key' for ActiveSupport::HashWithIndifferentAccess:Class

I have a polymorphic relationship for my User model. I am using Devise.
When I try to edit the user's details, I get the following error:
undefined method `primary_key' for ActiveSupport::HashWithIndifferentAccess:Class
The data submitted through the form is:
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"Ap0JP2bs/w9J6iI9rZahiKR1K8UEIi7rp33a4OutMbo=",
"user"=>{"email"=>"some_email#yahoo.com",
"rolable"=>{"first_name"=>"Christopher",
"last_name"=>"Columbus",
"city"=>"16"}},
"commit"=>"Update"}
The controller method is:
def update
#user = User.find(current_user.id)
if #user.update_attributes(params[:user])
redirect_to edit_user_registration_path, notice: 'Your profile was successfully updated.'
else
redirect_to edit_user_registration_path, error: "Something went wrong, couldn't update your profile!."
end
end
The models:
1. User
class User < ActiveRecord::Base
belongs_to :rolable, :polymorphic => true
# Devise business
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :remote_avatar_url, :avatar, :login, :username, :email, :password, :password_confirmation, :remember_me
# For authenticating with facebook
attr_accessible :provider, :uid, :rolable
devise :omniauthable, :omniauth_providers => [:facebook]
# For authenticating with both username or email
attr_accessor :login
# Upload avatar
mount_uploader :avatar, AvatarUploader
# Makes username necessary
validates :username, :presence => true
end
2. Customer
class Customer < ActiveRecord::Base
has_one :user, :as => :rolable
has_one :preferences, :class_name => "CustomerPreferences"
belongs_to :city
attr_accessible :first_name, :last_name
end
What's the problem?
Based on your request hash, you are passing the rolable attribute as:
"rolable"=>{"first_name"=>"Cristian",
"last_name"=>"Gavrila",
"city"=>"16"}
You can't do this unless you specify that the User model accepts nested attributes for rolable. However, you have setup your User as belonging to a Rolable rather than the other way around. Nested attributes aren't designed to handle this inverse relationship - you may want to reconsider what you are trying to accomplish here, and modify the relationship accordingly.
For instance, you may want to turn your hash inside out and pass the rolable attributes with the user attributes embedded within. Or you may want to turn rolable into a has_one association.

Admin role isn't being assigned in my seed data when I have roles in my model?

In my seed file I am trying to create 3 users, 1 admin and 2 default users but it keeps assigning all 3 users to the default role before creation. Here is my code:
User.rb
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :username
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :user_prices
has_many :products, :through => :user_prices
validates_presence_of :username, :email, :password, :password_confirmation
validates_format_of :username, :with => /\A[a-z0-9]{5,20}\z/i
validates_uniqueness_of :username, :email
before_create :setup_default_role_for_new_users
ROLES = %w[admin default]
private
def setup_default_role_for_new_users
if self.role.blank?
self.role = "default"
end
end
end
Seed.rb
puts 'Loading seed data now....'
user1 = User.create(:email => 'admin#email.com', :role => 'admin')
user2 = User.create(:email => 'user1#email.com')
user3 = User.create(:email => 'user2#email.com')
puts 'Users added'
I know user2 and user3 will have the default role but user1 should be admin. How is this done?
since :role isnt in your accessible attributes, its protected from mass assignment, which is what you are doing in your seed file.
so in order to set role, you can use something like this
user1 = User.create(:email => 'admin#email.com')
user1.update_attribute(:role, 'admin')
Use if not unless:
def setup_default_role_for_new_users
if self.role.blank? # if not unless
self.role = "default"
end
end

Devise - uninitialized constant User::PasswordHistory

I have a rails 3 application that I am working on and have implemented devise. I have it working, and now I wish to extend it so that a user is unable to use a old password more than once. Found this functionality on github which to my suprise was good. Disallow previously passwords - Git Hub
I thought this would straight forward but it is clearly not. My code looks like the following:
create_passwrod_histories.rb
class CreatePasswordHistories < ActiveRecord::Migration
def self.up
create_table(:password_histories) do |t|
t.integer :user_id
t.string :encrypted_password
t.timestamps
end
end
def self.down
drop_table :password_histories
end
end
User.rb
class User < ActiveRecord::Base
include ActiveModel::Validations
has_many :roles_users
has_many :roles, :through => :roles_users
has_many :projects
has_many :password_histories
after_save :store_digest
# authorization include this in whichever model that will use ACL9
acts_as_authorization_subject
def has_role?(role_name, object=nil)
!! if object.nil?
self.roles.find_by_name(role_name.to_s) ||
self.roles.member?(get_role(role_name, nil))
else
method = "is_#{role_name.to_s}?".to_sym
object.respond_to?(method) && object.send(method, self)
end
end
def login(user)
post_via_redirect user_session_path, 'user[username]' => user.username, 'user[password]' => user.password
end
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable #:registerable,
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :timeoutable
acts_as_authorization_subject :association_name => :roles
attr_accessor :login
# Setup accessible (or protected) attributes for your model
attr_accessible :id, :login, :username, :full_name, :email, :password, :password_confirmation, :remember_me, :role_ids
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates_presence_of :username, :full_name
validates_format_of :username, :with => /^[-\w\._#]+$/i, :allow_blank => true, :message => "should only contain letters, numbers, or . - _ #"
validates_length_of :username, :minimum => 1, :allow_blank => true
validates_uniqueness_of :username, :email
validates :email, :presence => true,
:format => { :with => email_regex }
validates :password, :unique_password => true
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
end
private
def store_digest
if encrypted_password_changed?
PasswordHistory.create(:user => self, :encrypted_password => encrypted_password)
end
end
end
unique_password_validator.rb
require 'bcrypt'
class UniquePasswordValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.password_histories.each do |ph|
bcrypt = ::BCrypt::Password.new(ph.encrypted_password)
hashed_value = ::BCrypt::Engine.hash_secret([value, Devise.pepper].join, bcrypt.salt)
record.errors[attribute] << "has been used previously." and return if hashed_value == ph.encrypted_password
end
end
end
I then run my app and try to use the same password. It then throws up the follwoing error uninitialized constant User::PasswordHistory
The only way that I can see from your code why that would be happening is if you didn't have the PasswordHistory model object. That code from Github doesn't actually explicitly tell you to do it, but you certainly need it. So, maybe you created and ran the migration but forgot to create the model, as in:
class PasswordHistory < ActiveRecord::Base
...
end

Resources