So, I've helped a friend add Omniauth to his app, and for some reason omniauth only worked the first time.
But now, it's returning:
PG::Error: ERROR: duplicate key value violates
unique constraint "index_users_on_email"
DETAIL: Key (email)=() already exists. : INSERT INTO "users"
("created_at", "provider", "uid", "updated_at", "username")
VALUES ($1, $2, $3, $4, $5) RETURNING "id"
It's like it only accepts one, and then says "already exists". How can we get this workin'?
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :omniauthable,
:recoverable, :rememberable, :trackable, :validatable
has_many :songs
has_many :comments
acts_as_voter
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.username = auth.info.nickname
end
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new session["devise.user_attributes"] do |user|
user.attributes = params
user.valid?
end
else
super
end
end
def password_required?
super && provider.blank?
end
def update_with_password(params, *options)
if encrypted_password.blank?
update_attributes(params, *options)
else
super
end
end
def email_required?
super && provider.blank?
end
end
schema snippit
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "admin"
t.string "provider"
t.string "uid"
t.string "username"
end
omni controller
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
alias_method :twitter, :all
end
All access keys are setup appropriately following railscasts latest ep on it.
You have a unique constraint on the email field. Because this field is defined as not nullable and has a default value of "" (empty string). This means if you don't specify the email, your DBMS will set this field to "". Of course, because it has a unique constraint, you'll only be able to add two users without email, because only one can have the empty string email.
If you didn't create the constraint manually, then I assume rails has created it for you (because there's an index on the email field too apparently) by assuming the first field is the table primary key, since you didn't specify any. This made it create both an index and a unique constraint on it.
A more simple explanation could be that this is caused due to your test data being generated without a unique email. Try looking in /test/fixtures/users.yml, if you see something like:
one: {
}
two: {
}
then change it to
one: {
email: tester1#stack.com
}
one: {
email: tester2#stack.com
}
Or you can delete the contents of the file.
You can remove the index from the email since devise sets it as unique. You can't have duplicate blank email addresses according to how devise sets up things by default. Here is a migration example.
class ChangeEmail < ActiveRecord::Migration
def change
remove_index :users, :email
end
end
Related
Apologies for the beginner question. I'm trying to use the Koala gem on my app. Currently, I have devise and omniauth (for facebook login). And they work great - I'm able to register users easily. What I want to do now though is utilize Koala to get access to Facebook data from my logged in Users. However, I'm getting an error "undefined method `oauth_token=' for #
User.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, omniauth_providers: [:facebook]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.username = auth.info.name
user.avatar = URI.parse(auth.info.image)
user.oauth_token = auth.credentials.token #Added this after learning about Koala
end
end
def facebook
#facebook ||= Koala::Facebook::API.new(oauth_token)
end
end
Application_Controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
before_filter :set_body_class
before_filter :set_current_user
def set_current_user
User.current_user = current_user
end
def set_body_class
#body_class = "#{controller_name} #{action_name}"
end
protected
def after_sign_in_path_for(resource)
# request.env['omniauth.origin'] || stored_location_for(resource) || root_path
items_path
end
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:username, :name, :avatar])
devise_parameter_sanitizer.permit(:account_update, keys: [:username,:name, :avatar])
end
end
Schema.rb
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.string "username"
t.string "provider"
t.string "uid"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Migration AddOmniauthToUsers
class AddOmniauthToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :provider, :string
add_column :users, :uid, :string
end
end
Try and call self.outh_token. outh_token belongs to a user and unless you pass a user as a parameter or call it on itself using "self", Rails doesn't know where outh_token belongs to.
def facebook
#facebook ||= Koala::Facebook::API.new(self.oauth_token)
end
or
def facebook(user)
#facebook ||= Koala::Facebook::API.new(user.oauth_token)
end
I'm using cancancan with rails_admin and devise gems. Even though the user has the role admin cancan shows the error You are not authorized to access this page. when trying to go to /admin route after logging in.
Here's my ability.rb
#models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
can :read, :all # allow everyone to read everything
if user && user.has_role?(:admin)
can :access, :rails_admin # only allow admin users to access Rails Admin
can :dashboard # allow access to dashboard
if user.role? :admin
can :manage, :all # allow superadmins to do anything
end
end
end
end
Here's my user model
#models/user.rb
class User < ActiveRecord::Base
ROLES = %i[admin moderator banned]
def roles=(roles)
roles = [*roles].map { |r| r.to_sym }
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
end
def roles
ROLES.reject do |r|
((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
end
end
def has_role?(role)
roles.include?(role)
end
def role?(base_role)
ROLES.index(base_role.to_s) <= ROLES.index(role)
end
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
if conditions[:username].nil?
where(conditions).first
else
where(username: conditions[:username]).first
end
end
end
validate :validate_username
def validate_username
if User.where(email: username).exists?
errors.add(:username, :invalid)
end
end
validates_format_of :username, with: /^[a-zA-Z0-9_\.]*$/, :multiline => true
# Include default devise modules. Others available are:
# , :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :confirmable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:login]
attr_accessor :login
def login=(login)
#login = login
end
def login
#login || self.username || self.email
end
end
Here's the rails_admin.rb
#config/initializers/rails_admin.rb
RailsAdmin.config do |config|
## == Devise ==
config.authenticate_with do
warden.authenticate! scope: :user
end
config.current_user_method(&:current_user)
## == Cancan ==
config.authorize_with :cancan
config.actions do
dashboard # mandatory
index # mandatory
new
export
bulk_delete
show
edit
delete
show_in_app
## With an audit adapter, you can add:
# history_index
# history_show
end
end
Here's the user in my schema.rb
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.string "username"
t.string "role"
t.integer "roles_mask"
end
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
add_index "users", ["username"], name: "index_users_on_username", unique: true
I created a regular user and changed it's role attribute to admin using
User.first.update_attribute :role, 'admin'
I don't exactly understand the use of roles_mask. Do I need to use both role and roles_mask in my database?
I don't fully understand what's going on in the User#roles but after arbitrarily assigning a role_mask of 0(the index of admin), I got an empty array. I doubt that this is the behaviour you anticipated.
I see that the check for roles(has_role?) uses the role_mask which you may not have assigned after the creation of your users.
To reduce some of these boilerplate code, I think it would be convenient to use ActiveRecord.enum.
#models/user.rb
class User < AR::Base
enum roles: %i[admin moderator banned]
end
note that to assign a default role, you could set that on your db or after_create.
#models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
can :read, :all # allow everyone to read everything
if user && user.admin?
can :access, :rails_admin # only allow admin users to access Rails Admin
can :dashboard # allow access to dashboard
if user.admin?
can :manage, :all # allow superadmins to do anything
end
end
end
end
You can also assign role to a particular user by user.admin!
Can somebody help me with this error? I add profile controller by console without model. Database it's working fine. I am sure that i have lastname and firstname in seed.rb and i just did db:setup/migration.
Here's the show.html
.page-header
.h1
=link_to #user.firstname + " " + #user.lastname, edit_user_registration_path
Database:
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "firstname"
t.string "lastname"
t.string "username"
t.boolean "admin", default: false
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :reviews
has_many :products
validates_presence_of :firstname, :lastname
end
Profile controler:
class ProfileController < ApplicationController
def show
#user = User.find_by_username(params[:id])
if #user
#reviews=#user.reviews.order(:created_at).reverse_order.limit(5)
render action: :show
end
end
end
This part
#user = User.find_by_username(params[:id])
returns nil, because you pass id from params as a username to find_by_username finder. In can't find the username (string) which is id (integer).
It should be
#user = User.find(params[:id])
Take a look at docs on how finders work
If you are using find_by_username then you should pass params[:username] instead of params[:id] .
Also this part:
if #user
#reviews=#user.reviews.order(:created_at).reverse_order.limit(5)
render action: :show
end
even if the if statement is false, render action: :show will still be called - it's the default.
if you defined a method that said nothing:
def test
end
rails would call: render action: :name_of_method
You need to use a redirect and unless:
redirect_to users_url, notice: 'user not found' unless #user
Good luck!
I am unable to find accurate information to what I want, so my second choice is to ask.
So, I want to know how to create a user profile from scratch and with your username replacing the ID.
Example: http://example.com/profile/{username}
In this case I have no problems with usernames, as are those working on a game server and they can not contain spaces or unusual characters.
I have done something, but I think it is wrong, even though I do not have any error on my website.
Notes:
My Devise Model: Player/s
Schema of Players
create_table "players", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "admin", default: false
t.integer "role_id"
t.string "nick"
t.string "username"
end
Variables "admin and role_id" are for charges will have on the web. I think this is not relevant to the subject.
Looked at other tutorials, but I have no more special things to add to my code.
My Controllers:
- registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
new_path(resource)
end
end
profile_controller.rb
class ProfileController < ApplicationController
def members
end
def myProfile
#attributes = Profile.attribute_names - %w(id player_id created_at updated_at)
end
def new
#profile = current_player.build_profile
end
def create
#profile = current_player.build_profile(params[:profile].permit( :nick))
end
end
My Models:
- player.rb
class Player < ActiveRecord::Base
validates :email, :presence => true, :email => true
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :role
before_create :set_default_role
has_one :profile
private
def set_default_role
self.role ||= Role.find_by_name('registered')
end
end
profile.rb
class Profile < ActiveRecord::Base
belongs_to :players
end
And in my view, have the controller profiles with:
myProfile => Only the profile of the user
profiles that is the global view of the profiles
route.rb
get 'profile/:id' => 'profile#perfiles'
resources :profile, only: [:edit]
map.resources :players, :has_one => :profile
map.resources :profiles
I hope I'm not asking for much, but hey, it's a question that I have and worth a try.
Thanks in advance, if anything is missing tell me.
I believe what you're looking to do is override the named route parameter. See this rails guide for more details: http://edgeguides.rubyonrails.org/routing.html (section 4.10).
Basically, you'll want to just add the param: :username to your routes file:
resources :profile, only: [:edit], param: :username
Which will make your route look something like:
profile GET /profile/:username(.:format) profile#show
And in your controller:
#profile = Profile.find_by(username: params[:username])
Hope that helps!
I have set up the necessary models and views to have a Devise resource, Account and User. Account has_many users & User belongs_to account. The schema reflects account_id as an attribute of user. I'm probably mistaken but I was under the impression that this account_id attribute would automatically be filled when an Account is logged in & creates a new user. Upon checking the Rails console, it seems all new users created this way had a nil value for account_id. Separate question but is this the ideal way to have multitenancy in an application using Devise?
Models:
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :account, :inverse_of => :users
accepts_nested_attributes_for :account
end
account.rb
class Account < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :users, :inverse_of => :account, :dependent => :destroy
accepts_nested_attributes_for :users
has_many :projects
end
schema.rb (just users & accounts)
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "account_id"
t.boolean "supervisor"
t.string "name"
end
create_table "accounts", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
end
users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate_user!
def new
#user = User.new
end
def create
#user.skip_confirmation! # confirm immediately--don't require email confirmation
if #user.save
flash[:success] = "User added and activated."
redirect_to users_path # list of all users
else
render 'new'
end
end
def index
#users = User.all
end
end
accounts_controller.rb
class AccountsController < ApplicationController
def new
#accounts = Account.new
#accounts.users.build
end
def create
#account = Account.new(params[:account])
if #account.save
flash[:success] = "Account created"
redirect_to accounts_path
else
render 'new'
end
end
end
from registrations > edit.html.erb
<% if account_signed_in? %>
<div class="jumbotron">
<span><%= link_to "Add user", new_user_registration_path, class: "btn btn-primary btn-sm" %></span>
</div>
<% end %>
I am not sure what how exactly you are trying to create a user
But creating a user with
#account.users.build
would automatically add account_id to user object.
Hope this helps! :)