Learning rails, using the treehouse videos to build a simple social network.
After making the following changes to add additional fields to devise:
new.html.erb (New Registration)
<div><%= f.label :username %><br>
<%= f.text_field :username %></div>
devise_create_users migration file
t.string :username,
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
I use rake db:reset and then rake db:migrate to attempt to initiate the database changes however the schema remains the same, without the username field.
I get the error: undefined method `username' for #
On the line:
<%= f.text_field :username %></div>
Any help really appreciated, not sure if this is a version related issue or whether treehouse has left out some information.
Thanks!
Devise Migration:
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :username,
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
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
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
schema:
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140720141519) do
create_table "articles", force: true do |t|
t.string "name"
t.string "title"
t.text "content"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "comments", force: true do |t|
t.string "name"
t.text "body"
t.integer "article_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "comments", ["article_id"], name: "index_comments_on_article_id"
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"
end
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
end
You have a typo in your migration. Comma is not needed at the end of line
t.string :username,
As I see there is no username column in users table in your schema. Just remove the comma and recreate you database with $ rake db:drop && rake db:create && rake db:migrate.
Related
I am implementing an authentication system with devise in rails, but when registering the user, in this case a student, the following error is skipped:
ActiveRecord::NotNullViolation in Devise::RegistrationsController#create
SQLite3::ConstraintException: NOT NULL constraint failed: students.codigo: INSERT INTO "students" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?)
I have been solving this error all day, but nothing that I can solve. I have implemented a before_action in the students_controller controller:
class StudentsController < ApplicationController
before_action :configure_devise_params, if: :devise_controller?
def configure_devise_params
devise_parameter_sanitizer.permit(:sign_up) do |user|
user.permit(:codigo, :documento, :nombres, :apellidos, :es_egresado, :email, :password, :password_confirmation)
end
end
...
end
but still the problem is not solved.
The same error happens when I uninstall devise, remove all reference from it, and also, implement strong params in the controller, without this the registry works satisfactorily. But with strong params and devise does not work, it passes data to null in the SQL command.
The student database is the following:
create_table "students", id: false, force: :cascade do |t|
t.integer "codigo", null: false
t.integer "documento", null: false
t.string "nombres", null: false
t.string "apellidos", null: false
t.integer "es_egresado", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.decimal "promedio_carrera"
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["documento"], name: "index_students_on_documento", unique: true
t.index ["email"], name: "index_students_on_email", unique: true
t.index ["reset_password_token"], name: "index_students_on_reset_password_token", unique: true
end
And its implementation migrate is the following:
class DeviseCreateStudents < ActiveRecord::Migration[5.1]
def change
create_table :students, {
:id => false,
:primary_key => :codigo
} do |t|
## Database authenticatable
t.integer :codigo, null: false
t.integer :documento, null: false
t.string :nombres, null: false
t.string :apellidos, null: false
t.integer :es_egresado, null: false
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.decimal :promedio_carrera, null: true
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
# 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
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :students, :email, unique: true
add_index :students, :reset_password_token, unique: true
add_index :students, :documento, unique: true
# add_index :students, :confirmation_token, unique: true
# add_index :students, :unlock_token, unique: true
end
end
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"6zdENP1iREYYM+qpqtskqRJ+aB38NIX/nG9jFsoGlImqUVyS9bsmBF6Uc7xvDD/J50/zamlZbbm2rwAaCgOmuw==",
"student"=>
{"codigo"=>"625762",
"documento"=>"107526792",
"nombres"=>"Carlos",
"apellidos"=>"Garnica",
"es_egresado"=>"0",
"email"=>"wcarlosfg.1234567890#hotmail.com",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
"commit"=>"Registrar"}
I also clarify that when I create another Student table, removed the previous one, and I remove the null option to the fields, the error does not appear, but the fields that supposedly have been saved remain in NULL in the database
UPDATE: Modify the database, making all fields null, and the form sends null fields whether they have data in the form or not, it is very rare. When doing inspect on the object, it shows the following:
irb(main):002:0> Student.last
Student Load (0.0ms) SELECT "students".* FROM "students" ORDER BY "students"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<Student id: 2, codigo: nil, documento: nil, nombres: nil, apellidos: nil, es_egresado: nil, promedio_carrera: nil, email: "wcarlosfg.1234567890#hotmail.com", created_at: "2018-09-30 07:46:39", updated_at: "2018-09-30 07:46:39">
SOLUTION: After a day looking at the error, the only solution was to overwrite the devise driver for records, with this, I was able to add the logic and implement strong params to make the registry work correctly.
When I try to load my rails app on my localhost I get an error telling me to resolve it by running rails db:migrate but when I try to run the command this is what I get
C:\Sites\CoolGuyGear>rails db:migrate
== 20180108004216 AddDeviseToViews: migrating =================================
-- change_table(:views)
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: duplicate column name: email: ALTER TABLE "views" ADD "email" varchar DEFAULT '' NOT NULL
C:/Sites/CoolGuyGear/db/migrate/20180108004216_add_devise_to_views.rb:7:in `block in up'
C:/Sites/CoolGuyGear/db/migrate/20180108004216_add_devise_to_views.rb:5:in `up'
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Caused by:
ActiveRecord::StatementInvalid: SQLite3::SQLException: duplicate column name: email: ALTER TABLE "views" ADD "email" varchar DEFAULT '' NOT NULL
C:/Sites/CoolGuyGear/db/migrate/20180108004216_add_devise_to_views.rb:7:in `block in up'
C:/Sites/CoolGuyGear/db/migrate/20180108004216_add_devise_to_views.rb:5:in `up'
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Caused by:
SQLite3::SQLException: duplicate column name: email
C:/Sites/CoolGuyGear/db/migrate/20180108004216_add_devise_to_views.rb:7:in `block in up'
C:/Sites/CoolGuyGear/db/migrate/20180108004216_add_devise_to_views.rb:5:in `up'
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
Migration:
# frozen_string_literal: true
class AddDeviseToViews < ActiveRecord::Migration[5.1]
def self.up
change_table :views do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
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
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
# Uncomment below if timestamps were not included in your original model.
# t.timestamps null: false
end
add_index :views, :email, unique: true
add_index :views, :reset_password_token, unique: true
# add_index :views, :confirmation_token, unique: true
# add_index :views, :unlock_token, unique: true
end
def self.down
# By default, we don't want to make any assumption about how to roll
#back a migration when your
# model already existed. Please edit below which fields you would like to remove in this migration.
raise ActiveRecord::IrreversibleMigration
end
end
create_table "views", 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.index ["email"], name: "index_views_on_email", unique: true
t.index ["reset_password_token"], name:
"index_views_on_reset_password_token", unique: true
end
The answer is in the question.
Whatever migrations you have added are creating duplicate columns in your database as seen in the log generated from the migration.
duplicate column name: email: ALTER TABLE "views" ADD "email"
Create a migration that either removes those columns from your database or go directly into your local mysql/sqlite database and remove those columns manually. Run the migration again and, since you don't already have those columns in your database table, you will not receive these errors.
I am trying to use Devise on my User model but when I go into rails console and try User.new I only get:
irb(main):002:0> User.new
=> #<User id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>
Why are the devise columns not showing up?
CreateUsers migration:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.timestamps null: false
end
end
end
AddDeviseToUsers migration:
class AddDeviseToUsers < ActiveRecord::Migration
def self.up
change_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
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
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
# Uncomment below if timestamps were not included in your original model.
# t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
def self.down
# By default, we don't want to make any assumption about how to roll back a migration when your
# model already existed. Please edit below which fields you would like to remove in this migration.
raise ActiveRecord::IrreversibleMigration
end
end
Schema shows the columns are there:
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
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"
end
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
Any ideas?
It's a security feature that Devise has in order to restrict its attributes and the critical information it contains to be exposed to API calls.
You can however override this, you need to override serializable_hash method.
# app/models/user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :recoverable, :confirmable, :rememberable, :validatable
...
protected
def serializable_hash(options = nil)
super(options).merge(encrypted_password: encrypted_password, reset_password_token: reset_password_token) # you can keep adding attributes here that you wish to expose
end
end
You can check http://www.rubydoc.info/github/plataformatec/devise/Devise/Models/Authenticatable where a constant is declared to blacklist attributes
BLACKLIST_FOR_SERIALIZATION =[:encrypted_password, :reset_password_token, :reset_password_sent_at, :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at, :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at]
Hope this answers your question!
If all you want to do it list all the attributes in the Rails console, it is easier to use User.first.serializable_hash(force_except: true)
See http://www.rubydoc.info/github/plataformatec/devise/Devise%2FModels%2FAuthenticatable:serializable_hash
Devise overrides the inspect method to not expose internal attibutes. You can try:
User.new.attributes
or
User.new.encrypted_password
(or whatever attribute you want)
You can check inspect method here
I'm following a tutorial on adding the Devise gem to Rails. One feature of the gem is generating a "user" using Devise, for further user authentication (Facebook, Twitter, etc.). I'm running into the following error:
== 20150906025001 AddDeviseToUsers: migrating =================================
-- change_table(:users)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: duplicate column name: email: ALTER TABLE "users" ADD "email" varchar DEFAULT '' NOT
NULL/Users/jaker/.rvm/gems/ruby-2.0.0-p643/gems/sqlite3-1.3.10/lib/sqlite3/database.rb:91:in `initialize'
I already have a User model in my app, that has an email, so this makes sense. However, when I try to run a migration and delete my "User" table, I'm still getting the same error.
[timestamp]_add_devise_to_users.rb:
class AddDeviseToUsers < ActiveRecord::Migration
def self.up
change_table(:users) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
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
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
# Uncomment below if timestamps were not included in your original model.
# t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
def self.down
# By default, we don't want to make any assumption about how to roll back a migration when your
# model already existed. Please edit below which fields you would like to remove in this migration.
raise ActiveRecord::IrreversibleMigration
end
end
Does anyone know how to fix this? I'm really confused, and no documentations have seemed to help. Thanks so much.
This error is happening because you already have a column called email in your User model
You could comment (or remove) the line:
t.string :email, null: false, default: ""
and the script will continue.
My setup - Rails 4, ActiveAdmin, Devise generated User model. The users authenticate with usernames and do not have the email attribute. I am mentioning the last, as Devise heavily relies on the email attribute, so this might have to do with the problem. My exact setup and code are described in this blog post.
In ActiveAdmin back-end, I go to Users -> New User -> fill in username, password, password confirmation -> Create User. Instead of creating a new user the New User form gets wiped out and under the Password field I get the error can't be blank. When I go to the Rails console and create a new user manually User.create(username: 'Joe', password: 'password', password_confirmation: 'password') everything works and the user is able to log in at localhost:3000/users/sign_in.
I saw this SO question. If I add to my User model:
def password_required?
new_record? ? false : super
end
I can create a new user, but all of the fields (including username, encrypted password) are blank.
UPDATE As Leger suggest, I am posting my code. As I am using the built-in contrllers of Devise and Activeadmin, I think it only makes sense to post the code of my User resource in Activeadmin and my db schema:
User resource in Activeadmin:
ActiveAdmin.register User do
# This determines which attributes of the User model will be displayed in the index page. I have left only username, but feel free to uncomment the rest of the lines or add any other of the User attributes.
index do
column :username
# column :current_sign_in_at
# column :last_sign_in_at
# column :sign_in_count
default_actions
end
# Default is :email, but we need to replace this with :username
filter :username
# This is the form for creating a new user using the Admin backend. If you have added additional attributes to the User model, you need to include them here.
form do |f|
f.inputs "User Details" do
f.input :username
f.input :password
f.input :password_confirmation
end
f.actions
end
# This is related to Rails 4 and the changes it introduced in handling strong parameters. Here we replace :email with :username.
controller do
def permitted_params
params.permit admin_user: [:username, :password, :password_confirmation]
end
end
end
schema.rb:
ctiveRecord::Schema.define(version: 20131031102826) do
create_table "active_admin_comments", force: true do |t|
t.string "namespace"
t.text "body"
t.string "resource_id", null: false
t.string "resource_type", null: false
t.integer "author_id"
t.string "author_type"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "active_admin_comments", ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id"
add_index "active_admin_comments", ["namespace"], name: "index_active_admin_comments_on_namespace"
add_index "active_admin_comments", ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id"
create_table "admin_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"
end
add_index "admin_users", ["email"], name: "index_admin_users_on_email", unique: true
add_index "admin_users", ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true
create_table "users", force: true do |t|
t.string "username", 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"
end
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
end
Any other code is available here.
I think the problem lies in the permitted_params method in your ActiveAdmin page handling the User model:
params.permit admin_user: [:username, :password, :password_confirmation]
If this is actually the User model, the line should read:
params.permit user: [:username, :password, :password_confirmation]
That fits the simptoms you describe–empty form after submit and everything working properly in the console.