Single Table Inheritance Errors - ActiveRecord::SubclassNotFound - ruby-on-rails

My intent is to implement STI with two types: Staff and Clinician. My previous implementation was using roles with enums, and after doing my best to follow answers to similar questions, take out all references in tests etc. to enum roles and replace with references to types , I am getting many versions of the following error when I run my testing suite:
ERROR["test_valid_signup_information_with_account_activation", UsersSignupTest, 1.01794000000001]
test_valid_signup_information_with_account_activation#UsersSignupTest (1.02s)
ActiveRecord::SubclassNotFound: ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'Staff'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite User.inheritance_column to use another column for that information.
app/controllers/users_controller.rb:19:in `create'
test/integration/users_signup_test.rb:27:in `block (2 levels) in <class:UsersSignupTest>'
test/integration/users_signup_test.rb:26:in `block in <class:UsersSignupTest>'
Here are a couple areas where I am confused that could potentially be hiding issues:
In my user model user.rb, I think I am defining the sub Classes correctly (Staff and Clinician), but I'm unsure if I'm wrapping everything correctly. Does all the other code have to be contained in one of these classes? Am I misusing "end"?
class User < ApplicationRecord
end
class Staff < User
end
class Clinician < User
end
belongs_to :university
has_many :referral_requests
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :type, presence: true
validates :university_id, presence: true, if: lambda { self.type == 'Staff' }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
def feed
ReferralRequest.where("user_id = ?", id)
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
Here's the specific test code that is failing (one of many in the test suite that are failing - all the user parameters are defined similarly though). Am I passing the staff parameter appropriately?
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User",
email: "user#example.com",
university_id: 1 ,
type: "Staff",
password: "password",
password_confirmation: "password" } }
Here is my users table schema:
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "remember_digest"
t.string "activation_digest"
t.boolean "activated", default: false
t.datetime "activated_at"
t.string "reset_digest"
t.datetime "reset_sent_at"
t.integer "university_id"
t.integer "role"
t.string "type"
t.index ["email"], name: "index_users_on_email", unique: true
end
Thanks very much for any ideas! I ask a lot of questions on here but it's only after trying to work through similar answers for quite a while.

Assuming that the code sample above is accurate, you are seeing this error because the user.rb file is invalid Ruby and failing to parse. You should also be seeing an interpreter error about that.
class User < ApplicationRecord
belongs_to :university
has_many :referral_requests
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :type, presence: true
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# etc...
end
class Staff < User
validates :university_id, presence: true
end
class Clinician < User
end
Standard class-inheritance practices apply, so if there is code in there that is only appropriate for a specific subclass it should move there (e.g. university_id validation moving to Staff).
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
These should be written as
def self.digest(string)
# ...
end
def self.new_token
# ...
end
or, alternatively,
class << self
def digest(string)
# ...
end
def new_token
# ...
end
end

Related

Can't disable validation of email in devise-auth-token

I am using devise-auth-token and want to sign in with field another than email.
So I used phone_number instead and everything is working properly but I can't remove validation on email, here are my codes
user.rb
class User < ActiveRecord::Base
include DeviseTokenAuth::Concerns::User
devise :database_authenticatable, :confirmable, authentication_keys: [:phone_number]
mount_uploader :avatar, AvatarUploader
validates :first_name, presence: true,format: {with: /[[:word:]]/}
validates :last_name , presence: true,format: {with: /[[:word:]]/}
validates :email , format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }, uniqueness: true
validates :phone_number, presence: true, uniqueness: true
validate :birthdate_in_the_future_invalid
validates :country_code, presence: true
validates :birthdate, presence: true
validates :gender, presence: true
# validates :file, presence: true
validates :phone_number, phone: { possible: true, types: [:mobile], country_specifier: -> phone { phone.country_code.try(:upcase) } }
validate :e164_phone_number
after_create :set_provider_uid
def email_required?
false
end
def email_changed?
false
end
def password_required?
super if confirmed?
end
def password_match(password, password_confirmation)
self.errors[:password] << "can't be blank" if password.blank?
self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
password == password_confirmation && !password.blank?
end
private
def birthdate_in_the_future_invalid
begin
if Date.strptime(birthdate, '%d-%m-%Y') > Date.today
errors.add(:birthdate, "in_the_future")
end
rescue
errors.add(:birthdate, "invalid format")
end
end
def e164_phone_number
if phone_number[0] != '+'
errors.add(:phone_number, "invalid format")
end
end
def set_provider_uid
self.update_column(:uid, phone_number)
self.update_column(:provider, "phone_number")
end
end
and this is my initializers/devise.rb
Devise.setup do |config|
config.mailer_sender = 'aliabdelrahmanweka74#gmail.com'
require 'devise/orm/active_record'
config.authentication_keys = [:phone_number]
config.case_insensitive_keys = [:phone_number]
config.strip_whitespace_keys = [:phone_number]
config.skip_session_storage = [:http_auth]
config.stretches = Rails.env.test? ? 1 : 11
config.reconfirmable = false
config.expire_all_remember_me_on_sign_out = true
config.password_length = 6..128
config.email_regexp = /\A[^#\s]+#[^#\s]+\z/
config.reset_password_within = 6.hours
config.sign_out_via = :delete
end
I used funciton email_required? and make it return false but nothing happens
I call localhost:3000/users , Method Post
params:
{
"user": {
"first_name": "ali",
"last_name": "weka",
"gender": "male",
"birthdate": "25-06-2016",
"email": "",
"phone_number": "+201118574649",
"country_code": "EG",
"uid": "+201118574649",
"provider": "phone_number"
}
}
and here's the response
{"status":"error","data":{"id":null,"first_name":"ali","last_name":"ali","country_code":"EG","phone_number":"+201119574649","gender":"male","birthdate":"25-06-2016","created_at":null,"updated_at":null,"provider":"email","uid":"+201119574649","avatar":{"url":null},"email":""},"errors":{"email":["can't be blank","is invalid"],"full_messages":["Email can't be blank","Email is invalid"]}}
so any help, please?
I had the same problem. I wanted some users with email, and some without.
Solution
You should first remove the null: false restriction from users table with a migration. Should you want to remove the default "", do it to. Don't remove the index, so it still validates that emails are unique (in case you still use emails sometimes). Null values won't have a problem with the unique constraint.
If you are using devise validatable, add this method in your User model:
def email_required?
false
end
After doing the previous part, you should do one of the following:
Option 1:
Going deep into devise_token_auth gem, at least in my case, I realized that this concern was being included.
It's included by default unless you modify the DeviseTokenAuth initializer with
config.default_callbacks = false
But maybe you don't want that.
Option 2:
If you check the code in the concern I linked, you can see that it validates email in case the provider of the user is 'email'.
The fix in my case was to change the provider field to something else for the users that didn't have email.
With any of those 2 changes, your email won't be validated anymore.
For somebody who needs email sometimes:
Of course, do what Devise wiki says
apart from the other field include email in case_insensitive_keys, strip_whitespace_keys
and you can have a method in the User model with:
def email_required?
true unless phone_number?
end

Rails Validation with two attributes of a variable

I really need your help, because I can't find a solution on my own since 2 hours. I tried many different approaches, but I just don't get how I can validate an attribute of a variable by using another attribute as a condition.
This was my last try:
class User < ApplicationRecord
validates :admin, absence: true,
if: ["student?", :email]
def student?(user)
user.email.include? 'stud'
end
end
I want to validate, that a user with the attribute "admin" can't have an email address, which includes 'stud'. Even better would be a validation, that 'stud' users can't be admin.
When I run db:seed I get this error, which stays if I put the 'stud' in brackets:
rails aborted!
ArgumentError: wrong number of arguments (given 0, expected 1)
/home/studi/Bachelorarbeitsapp/sample_app/app/models/user.rb:22:in `student?'
Please have a look at this issue!
I am willing to share any other code, which is necessary to solve the problem. Thanks!
Here is the full helper code:
class User < ApplicationRecord
has_many :preferences, dependent: :destroy
accepts_nested_attributes_for :preferences
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
has_secure_password
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#(stud.uni-hannover.de|wiwi.uni-hannover.de)+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false}
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
validates :mat_number, presence: true, length: { minimum: 7, maximum: 8 }, numericality: { only_integer: true }
validates :admin, absence: true,
if: ["student?", :email]
def student?(user)
user.email.include? 'stud'
end
validates :ects, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 170, only_integer: true }, allow_nil: true
validates :grade_avg, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 4 }, allow_nil: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Activates an account.
def activate
update_columns(activated: true, activated_at: Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
Based on the Active Record Validations Guide section 6.2 Custom Methods, it seems like you should be able to do something like:
class User < ApplicationRecord
validate :admin_cant_be_student
def admin_cant_be_student
if admin and email.include?('stud')
errors.add(:admin, "can't have a student email address")
end
end
end
You don’t need a custom validation:
Could use validates_exclusion_of
validates_exclusion_of :email, in: %w( stud ), if: -> { |user| user.email.include? “admin” }, message: “admins can’t have stud in email”
validates_exclusion_of :email, in: %w( admin ), if: -> { |user|user.email.include? “stud” }, message: “students can’t have admin in email”

Rails - Unknown attribute password

I have been following Michael Hartl's Ruby on Rails tutorial book to try and add users to my application. Reading chapter 6, I have added what I believe to be the necessary fields for my user, specifically password and password confirmation via "has_secure_password".
I thought that adding "has_secure_password" to my user model would include the attributes "password" and "password_confirmation" provided I add a "password_digest" to the model. I have done that as the book instructed me to. However, when I run a test, Rails gives me the following error:
Error:
UserTest#test_should_be_valid:
ActiveModel::UnknownAttributeError: unknown attribute 'password' for User.
test/models/user_test.rb:8:in `setup'
I tried this solution and it still gave me the same error, not recognizing the attributes "password" or "password_confirmation". I installed bcrypt using "gem install bcrypt" and included the following in my gem file:
gem 'bcrypt-ruby', :require => 'bcrypt'
I am using Rails 5 and it seems like "has_secure_password" is not supplying the password attributes that I need. Can anyone see what I missed or did wrong that caused "has_secure_password" to not work as intended? Thanks
User Model:
class User < ApplicationRecord
has_many :activities
class User < ActiveRecord::Base
attr_accessor :name, :email, :password, :password_confirmation
has_secure_password
validates :first_name, presence: true, length: {minimum: 1}
validates :last_name, presence: true, length: {minimum: 1}
validates :email, presence: true, uniqueness: true, length: {minimum: 5}
validates :username, presence: true, uniqueness: true, length: {minimum: 1}
validates :password_digest, length: {minimum: 6}
validates :password, :confirmation => true, length: {minimum: 4}
validates :password_confirmation, presence: true
#-----------------------New Stuff ---------------------------------------
acts_as_authentic do |c|
c.crypto_provider = Authlogic::CryptoProviders::Sha512
end
#------------------------------------------------------------------------
#---------------Unsure if working--------------
#validates_presence_of :password, :on => :create
#validates_presence_of :email
#validates_uniqueness_of :email
#----------------------------------------------
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
end
Apologies for the messy code on the model as I am still learning Rails.
User Controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
flash[:success] = 'Account created'
else
flash[:notice] ='ERROR: Account was not created'
redirect_to 'users/new'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
end
User Table:
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "username"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "persistence_token"
t.string "password_digest"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
end
User Test:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
def setup
#user = User.new(first_name: 'test', last_name: 'tester', password: '1234',email: 'test1#mail.com',
password: 'foobar', password_confirmation: 'foobar')
end
test 'should be valid' do
assert #user.valid?
end
end
Update:
I have tested this out and it works. So hope will work for you as well :) Looks like MiniTest doesn't work well with BCrypt. I received the same error - undefined password, but later implemented my change and it went further well.
Original answer:
As of your founded solution it made me think that this makes no sence - adding getter and especially setter methods for :password and :password_confirmation. Because has_secure_password creates those virtually that runs through BCrypt. So doesn't it goes around crypting / encrypting? If so it is not safe. So only option left for testing I see take the BYcript into the testing suite. I think something like this might do the trck:
In User Test:
require 'bcrypt'
def setup
#user = User.new(first_name: 'test', last_name: 'tester', password: BCrypt::Password.create("my password") ,email: 'test1#mail.com', password_confirmation: 'my password')
end
test 'should be valid' do
assert #user.valid?
end
Note that I removed duplicated password: 'foobar. Since with that particular test you are testing if User can be created, so shouldn't pass a different password or even duplicated attribute... Make another test for this (also checkout fixtures, they are great for creating test objects, as well as factories for more complicated cases).
And of course, remove the atr_accessor :password, :password_confirmation form your User model.
p.s. and please fix you code snippet for User class. Or is it really defined twice like this?:
class User < ApplicationRecord
has_many :activities
class User < ActiveRecord::Base

Creating a has_and_belongs_to_many relationship in Rails

I have a User model which is designed after the Michael Hartl RoR tutorial and I am trying to create a new Teacher model. I would like the teacher to have many users but each user to have only one teacher. I created the teacher model with
class CreateTeachers < ActiveRecord::Migration
def change
create_table :teachers do |t|
t.string :name
t.string :email
t.string :phone
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
and added has_one :teacher to user.rb. Here is the teachers.rb model
class Teacher < ActiveRecord::Base
has_and_belongs_to_many :users
validates :user_id, presence: true
before_save :downcase_email
validates :name, presence: true,
length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true,
length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
end
However in my teacher_test.rb test file, things get a little fuzzy. I try this
def setup
#user = users(:michael)
#user2 = users(:archer)
#user3 = users(:lana)
#user4 = users(:mallory)
#teacher = Teacher.new(name: "Phred Willard",
email: "pwillard#test.com",
phone: "1234567890",
user_id: [#user.id,
#user2.id,
#user3.id,
#user4.id])
end
test "should be valid" do
assert #uac.valid?
end
but that fails outright. Did I set my relationship up correctly? I obviously am not adding users correctly since the model fails a validity test. How would I add more users to that teacher? Thanks in advance.
I would like the teacher to have many users but each user to have only one teacher
You only need has_many / belongs_to...
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :teacher
end
#app/models/teacher.rb
class Teacher < ActiveRecord::Base
has_many :users
end
You'll need to add a teacher_id column in your users table (the opposite of what you have now):
class UpdateUsers < ActiveRecord::Migration
def change
change_table :users do |t|
t.references :teacher, index: true, foreign_key: true #-> teacher_id
end
end
end
--
The error you have is that you're calling user_id on teacher; it should be teacher_id on user:
#teacher = Teacher.new(name: "Phred Willard",
email: "pwillard#test.com",
phone: "1234567890",
user_ids: [#user.id,
#user2.id,
#user3.id,
#user4.id])
This should associate #teacher with the defined #users you've listed.
You'll also want to look at collection_singular_ids for the has_many association, which is why your test is failing.
Your teacher.rb should be
class Teacher < ActiveRecord::Base
has_many :users
before_save :downcase_email
validates :name, presence: true,
length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true,
length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
end
and user.rb
class User < ActiveRecord::Base
belongs_to :teacher
# rest of your code here ....
end
You need teacher_id column in users table.

Unable to submit add action form - Rails 4

So my problem is more or less as follows. I have a rails applications with the following classes:
Users
Skills
Each user can have multiple skills and they are added from the users profile screen which is the show action for the users controller. Currently I have the Skills set up and able to be created but I cannot add a pre-built skill onto a users profile. When I try to submit I get the following error Empty list of attributes to change
The following is my add skill to user action:
def add_skill_to_user
user = User.find(params[:id])
user.skills.create(skill_params) #skill name, level...
#skills_options = Skill.all.map{|s| [ s.name, s.id] }
#whatever happens when this is is done, redirect, json answer etc...
if user.skills.update_all(skill_params)
flash[:success] = "Skill Added"
else
render 'add_skill_to_user'
end
end
private
# Set skills params whitelist
def skill_params
params.permit(:name, :user_id)
end
and the following to routes
post 'users/:id/add_skill_to_user' => 'users#add_skill_to_user'
And this is my form
<%= form_tag({controller: "users", action: "add_skill_to_user"}, method: "put") do %>
<%= collection_select(:skill, :name, Skill.all, :id, :name) %>
<%= submit_tag 'Submit' %>
<%end%>
Let me know any additional information you need.
---------Added Models Code------------------
User.rb
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
belongs_to :group
has_many :ranks
has_many :skills
has_many :mission_notes
has_and_belongs_to_many :training_events
accepts_nested_attributes_for :skills
validates :username, presence: true
validates :email, presence: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :group_id, presence: true
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver
end
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
end
Skill.rb
class Skill < ActiveRecord::Base
belongs_to :user
end
Your collection_select will generate something like
<select name="skill[name]">
<option value="1">First Skill</option>
...
</select>
where "skill[name]" doesn't reflect what's in the option values (skill ids).
Furthermore, your params whitelist doesn't handle the fact that the POST data generated by the select is an array.
I'd suggest
<%= collection_select(:skill, :id, Skill.all, :id, :name) %>
with this whitelist filter in the controller:
params.require(:skill).permit(:id)
But your add_skill_to_user method is confusing. If all you want to do is assign pre-existing skills to a user, there is a RailsCast for that.

Resources