I am trying to seed my production database with a record for the table Users. Here is the model:
class User < ActiveRecord::Base
include Person
include Contact
has_many :coworkers, :class_name => 'Coworker'
has_many :customers, :class_name => 'Customer'
has_many :locations, :class_name => 'Location'
has_many :appointment_types, :class_name => 'AppointmentType'
before_save { self.email = email.downcase }
#before_create :create_remember_token
# password
has_secure_password
attr_accessor :remember_token, :activation_token, :reset_token
validates :password, length: { minimum: 6 }, on: :create
# rem_notice_hrs
validates :rem_notice_hrs, presence: true
validates :rem_notice_hrs, numericality: true
# rem_text
validates :rem_text, presence: true
# mandatory email:
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
after_initialize :init
#def create_remember_token
# self.remember_token = Account.digest(Account.new_remember_token)
#end
# 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
# 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
private
def init
if self.new_record?
if self.rem_notice_hrs.nil?
self.rem_notice_hrs = 24
end
if self.rem_text.nil?
if self.company.nil?
self.rem_text = "Dear [customer title: automatic] [customer family name: automatic], this is a reminder of your appointment with %{title} %{family_name} on [date/time]."
else
self.rem_text = "Dear [title] [customer family name], this is a reminder of your appointment with %{company} on [date/time]."
end
end
if self.start_day.nil?
self.start_day = Time.now
end
end
end
end
And here is the seed:
User.create!(first_name: "Nick",
email: "user#example.com",
password: "user12",
password_confirmation: "user12",
phone: 7933336337,
rem_notice_hrs: 24,
rem_text: "This is a reminder",
start_day: "2014-11-21 21:50:16",
)
I am running the seed task via capistrano and I am getting:
ActiveRecord::RecordInvalid: Validation failed: Password is too short (minimum is 6 characters)
/home/deploy/appmate/shared/bundle/ruby/2.1.0/gems/activerecord-4.2.0/lib/active_record/validations.rb:79:in `raise_record_invalid'
/home/deploy/appmate/shared/bundle/ruby/2.1.0/gems/activerecord-4.2.0/lib/active_record/validations.rb:43:in `save!'
/home/deploy/appmate/shared/bundle/ruby/2.1.0/gems/activerecord-4.2.0/lib/active_record/attribute_methods/dirty.rb:29:in `save!'
/home/deploy/appmate/shared/bundle/ruby/2.1.0/gems/activerecord-4.2.0/lib/active_record/transactions.rb:291:in `block in save!'
What is going wrong here? The password has exactly 6 characters. The validation error should not arise?!
I just had the same issue in Development and I haven't found a work around. For now I'm leaving password validation off.
validates_confirmation_of :password
validates_length_of :password, :within => 4..20
validates_presence_of :password, :if => :password_required?
From localhost:3000/users/new I input newuser#emailaddress.com / password and I received the error:
2 errors prohibited this user from being saved:
Password is too short (minimum is 4 characters) Password can't be
blank
Related
I am having trouble making a test pass for my posts controller.
What is the proper way to test my create action inside the Posts controller with a polymorphic association?
I'm using Rails 4
Here's my code
Models:
class Topic < ActiveRecord::Base
belongs_to :user
belongs_to :category
has_many :posts, as: :postable
has_many :posts, dependent: :destroy
validates :name, presence: true, length: {maximum: 50}
validates :description, presence: true, length: {maximum: 80}
validates :user_id, presence: true
is_impressionable
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :topic
belongs_to :category
belongs_to :postable, polymorphic: true
has_many :posts, as: :postable
validates :comment, presence: true
validates :user_id, presence: true
validates :topic_id, presence: true
validates :category_id, presence: true
default_scope { order(created_at: :asc) }
end
Post controller
class PostsController < ApplicationController
before_action :auth_user
before_action :set_post, only: [:destroy]
before_action :correct_user, only: [:destroy]
before_action :find_postable, only: [:create, :new]
def new
#post = #postable.posts.build
end
def create
#post = #postable.posts.build(post_params)
set_topic_id
set_category_id
#post.user_id = current_user.id
if #post.save
redirect_to topic_path(#post.topic.id)
else
redirect_to request.referer, notice: "Post unsuccessful!"
end
end
def destroy
#post.destroy
flash[:success] = 'Post deleted'
redirect_to request.referer || root_url
end
private
def set_post
#post = Post.find(params[:id])
end
def correct_user
#post = current_user.posts.find_by(id: params[:id])
redirect_to root_url if #post.nil?
end
def find_topic
#topic = Topic.find(params[:topic_id])
end
def find_postable
#postable = Post.find_by_id(params[:post_id]) if params[:post_id]
#postable = Topic.find_by_id(params[:topic_id]) if
params[:topic_id]
end
def post_params
params.require(:post).permit(:comment)
end
end
Post controller test:
require 'test_helper'
class PostsControllerTest < ActionController::TestCase
def setup
#topic = topics(:topicone)
#post = posts(:postone)
#posttwo = posts(:posttwo)
#category = categories(:categoryone)
#user = users(:user1)
end
test 'should create post when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, post: { user_id: #user.id, category_id:
#category.id, topic_id: #topic.id, comment: "First reply!",
postable_id: #post.id, postable_type: "Post" }
end
end
end
When I run the test above I get this error:
ERROR["test_should_create_post_when_logged_in", PostsControllerTest,
2016-12-04 14:23:25 -0500]
test_should_create_post_when_logged_in#PostsControllerTest
(1480879405.93s)
NoMethodError: NoMethodError: undefined method `posts' for nil:NilClass
app/controllers/posts_controller.rb:13:in `create'
test/controllers/posts_controller_test.rb:28:in `block (2
levels) in <class:PostsControllerTest>'
test/controllers/posts_controller_test.rb:25:in `block in
<class:PostsControllerTest>'
app/controllers/posts_controller.rb:13:in `create'
test/controllers/posts_controller_test.rb:28:in `block (2 levels)
in <class:PostsControllerTest>'
test/controllers/posts_controller_test.rb:25:in `block in
<class:PostsControllerTest>'
From my understanding, I believe it's telling me that it can't find whether the create action is posting to a post or to a topic.
The site works great in development and production. The problem is in this test.
How can I rewrite this test and make it so it recognizes to whom it's posting to?
I found a solution for this shortly after.
In this case, to successfully test if a post is created on a topic, make sure to provide the topic_id.
test 'should create post on a topic when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, topic_id: #topic, post: {
user_id: #user.id,
category_id: #category.id,
comment: 'First post on a topic!' }
end
end
Now to test if a post is created on another post, make sure to provide the post_id.
test 'should create post on another post when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, post_id: #post, post: {
user_id: #user.id,
category_id: #category.id,
comment: 'First post on a topic!' }
end
end
New to RoR, here couldn't find the solution I was looking for so I'm typing my problem here:
I have a web app where after the user is logged in they will be redirected to a dashboard that shows them their various information.
For now - as I'm getting the feel of RoR - the view of the dashboard was suppose to get a lot of information:
def index
#logged_user = Person.includes(:user, :addresses).find(session[:user_id])
end
but when I checked using #logged_user.inspect it only gave me this
#<Person (person model attributes here) >
I checked the log and it seems the User and Addresses were loaded
printscreen of my log http://prntscr.com/c40fee
but they aren't in the #logged_user variable... did I miss a step here? where did I go wrong?
EDIT:
here's the model of Person
class Person < ActiveRecord::Base
validates :first_name, :last_name, presence: true
has_one :user, :dependent => :destroy
has_one :medical_record
has_many :addresses, :dependent => :destroy
has_many :contacts,:dependent => :destroy
has_many :enrollments_as_student, :class_name => 'Enrollment', :foreign_key => :student_id, :dependent => :destroy
accepts_nested_attributes_for :addresses, allow_destroy: true
accepts_nested_attributes_for :contacts, allow_destroy: true
accepts_nested_attributes_for :medical_record, allow_destroy: true
end
model for User
class User < ActiveRecord::Base
validates :username, presence: true
validates :username, :password, format: {with: /\A[a-zA-Z0-9 ñÑ]+\Z/, message: "Special characters aren't allowed"}
validates :username, uniqueness: {message: "username is already taken"}
has_secure_password
belongs_to :person
end
model for Addresses
class Address < ActiveRecord::Base
validates :street, :barangay, :city, :province,
presence: {message: "Fill in all necessary fields"}
validates :zipcode,
numericality: {message: "Zipcodes should only contain numbers"},
length: {minimum: 4, maximum: 4, message: "Invalid zipcode length"}
belongs_to :person
end
also snippet for the login
def login
#user = User.find_by_username(params[:receptions][:username])
if #user && #user.authenticate(params[:receptions][:password])
session[:user_id] = #user.person_id
flash[:notice] = "You have succfully logged in"
redirect_to '/dashboards'
else
flash[:notice] = "Invalid username or password"
redirect_to '/login'
end
I have a problem with devise_invitable 1.4.0 and strong parameters when I add additional custom parameters and I really hope somebody can guide me in the right direction. I am able to send invitations, but when an invited user accepts an invitation and enters a desired username, maiden name, password and confirmed password, the following error is shown:
Processing by Users::InvitationsController#update as HTML
Unpermitted parameters: username, name
The user is created as expected, but the 'username' and 'name' columns in the database are empty.
I have tried all the suggestions I could find for related issues, but none of the worked. I have noticed that if I change the app/controllers/users/invitations_controller.rb file in any way (eg inserting a blank space on an empty line) without restarting the webserver (Thin) the problem disappears - but the problem reappears when the webserver is restarted.
The various relevant files look like this:
routes.rb:
Rails.application.routes.draw do
root to: 'visitors#index'
#Tell rails to use the Devise controllers that were generated with this command:
# > rails generate devise:controllers users
#Using these generated controllers allows us to overwrite anything in the deault controllers.
devise_for :users, :path_names => {:sign_in => 'login', :sign_out => 'logout'}, controllers: {confirmations: "users/confirmations", passwords: "users/passwords", registrations: "users/registrations", sessions: "users/sessions", unlocks: "users/unlocks", :invitations => 'users/invitations'}
resources :users
end
config/initializers/devise.rb
Devise.setup do |config|
...
...
config.scoped_views = true
config.authentication_keys = [ :username ]
...
...
end
app/controllers/users/invitations_controller.rb:
class Users::InvitationsController < Devise::InvitationsController
private
# this is called when creating invitation
# should return an instance of resource class
def invite_resource
## skip sending emails on invite
resource_class.invite!(invite_params, current_inviter) do |u|
u.tenant = current_inviter.tenant
u.role = :user
end
end
def after_invite_path_for(resource)
users_path
end
def resource_params
params.permit(user: [:name, :email,:invitation_token, :username])[:user]
end
end
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
around_filter :scope_current_tenant
before_filter :configure_permitted_parameters, if: :devise_controller?
if Rails.env.development?
# https://github.com/RailsApps/rails-devise-pundit/issues/10
include Pundit
# https://github.com/elabs/pundit#ensuring-policies-are-used
# after_action :verify_authorized, except: :index
# after_action :verify_policy_scoped, only: :index
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
end
#############################################################################
private
#############################################################################
if Rails.env.development?
def user_not_authorized
flash[:alert] = "Access denied." # TODO: make sure this isn't hard coded English.
redirect_to (request.referrer || root_path) # Send them back to them page they came from, or to the root page.
end
end
def current_tenant
#current_tenant ||= current_user.tenant unless current_user.nil?
end
helper_method :current_tenant
def scope_current_tenant(&block)
if current_tenant.nil?
scope_visitor_schema
yield
else
current_tenant.scope_schema("public", &block)
end
end
def scope_visitor_schema()
original_search_path = ActiveRecord::Base.connection.schema_search_path
ActiveRecord::Base.connection.schema_search_path = 'public'
ensure
ActiveRecord::Base.connection.schema_search_path = original_search_path
end
#############################################################################
protected
#############################################################################
def configure_permitted_parameters
# Only add some parameters
devise_parameter_sanitizer.for(:account_update).concat [:name, :email]
# Override accepted parameters
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:name, :username, :password, :password_confirmation,
:invitation_token)
end
end
end
app/models/user.rb:
class User < ActiveRecord::Base
enum role: [:user, :admin]
after_initialize :create_tenant, :if => :new_record?
belongs_to :tenant
# has_many :invitations, :class_name => self.to_s, :as => :invited_by
scope :unconfirmed, -> { where(confirmed_at: nil) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
# validate :username, presence: true, uniqueness: true, format: { with: /[a-zA-Z0-9]{4,20}/ }
def displayed_username
username.nil? ? "N/A" : username
end
def displayed_name
name.nil? ? "N/A" : name.titleize
end
def create_tenant
#The create_tenant method will also be called when looking up a user,
#so the following ensures a tenant is only created if it does not already
#exist - and the user has not been invited and assigned to an existing tenant:
if self.tenant.nil?
#Set role to 'admin' if a tenant is about to be created:
self.role = :admin #if self.tenant.nil?
self.tenant = Tenant.new
end
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
end
I finally found a fix, which was to place the parameter sanitizer directly in users/invitations_controller.rb instead of the application_controller.rb.
class Users::InvitationsController < Devise::InvitationsController
before_filter :configure_permitted_parameters, if: :devise_controller?
private
def configure_permitted_parameters
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:username, :name, :email, :password, :password_confirmation, :invitation_token)
end
end
end
Pretty simple, I am using factory girl to do the following:
FactoryGirl.define do
sequence :user_email do |n|
"user#{n}#example.com"
end
# Allows for multiple user names
sequence :user_name do |n|
"user#{n}"
end
factory :user, class: Xaaron::User do
first_name 'Adam'
last_name 'Something'
user_name {generate :user_name}
email {generate :user_email}
password 'somePasswordThat_Is$ecure10!'
end
end
And from there we pass this information into the user modal:
require 'bcrypt'
module Xaaron
class User < ActiveRecord::Base
attr_accessor :password
before_save :encrypt_password
validates :first_name, presence: true
validates :user_name, uniqueness: true, presence: true, length: {minimum: 5}
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
validates_uniqueness_of :user_name
validates_uniqueness_of :email
validates :password, presence: true, confirmation: true, length: { minimum: 10 }, if: :new_record?
def self.authenticate_user(user_name, password)
user = Xaaron::User.find_by_user_name(user_name)
if(user && user.password == BCrypt::Engine.hash_secret(password, user.salt))
user
else
nil
end
end
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.password = BCrypt::Engine.hash_secret(password, salt)
end
end
end
end
From there, any test that tests the user password validation or seeing if the password we generate is the same one we store in the database fails because factory girl is passing nil to the database.
Failed Test Output
5) Xaaron::User Validation checks should validate a user based on login credentials
Failure/Error: Xaaron::User.authenticate_user(#user.user_name, #user.password).should == #user
expected: #<Xaaron::User id: 5, first_name: "Adam", last_name: "Something", user_name: "user9", email: "user8#example.com", password: nil, salt: "$2a$10$Y1m4YK.4znWVz2icp0ENtO", created_at: "2014-04-06 15:20:53", updated_at: "2014-04-06 15:20:53">
got: nil (using ==)
# ./spec/models/xaaron/user_spec.rb:33:in `block (3 levels) in <top (required)>'
You can see in the above that: password: nil which it shouldn't be ...
The Test that Generated the fail
it "should validate a user based on login credentials" do
#user = FactoryGirl.create(:user)
Xaaron::User.authenticate_user(#user.user_name, #user.password).should == #user
end
As requested - Schema
ActiveRecord::Schema.define(version: 20140323000123) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "xaaron_users", force: true do |t|
t.string "first_name"
t.string "last_name"
t.string "user_name"
t.string "email"
t.string "password"
t.string "salt"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Remove attr_accessor :password from the class User.
As ActiveRecord would treat it as a virtual attribute and it will not be saved in the database.
If you notice rspec failure message received:
expected: #<Xaaron::User id: 5, first_name: "Adam", last_name: "Something", user_name: "user9", email: "user8#example.com", password: nil, salt: "$2a$10$Y1m4YK.4znWVz2icp0ENtO", created_at: "2014-04-06 15:20:53", updated_at: "2014-04-06 15:20:53">
password is not saved in database. Make sure to remove the attr_accessor from the User model so password is treated as field and would be saved in database.
Next, you need to update the example as below:
it "should validate a user based on login credentials" do
#user = FactoryGirl.create(:user)
#login_user = FactoryGirl.build(:user)
Xaaron::User.authenticate_user(#user.user_name, #login_user.password).should == #user
end
#user contains the actual record created in database, so #user.password has the encrypted password already. Your example fails because you are sending the encrypted password to authenticate_user method and re-encrypting the already encrypted password with:
BCrypt::Engine.hash_secret(password, user.salt)
In reality, for your example to pass what you need to pass is the actual password without any encryption. That is why I added #login_user in your example.
Total Rails novice here, just coming from PHP.
I'm running a Rails4+Active Admin setup with (device/cancan (disregarding those now).
Implementing strong_parameters. Please note, the code is totally broken. I've tried it in a bunch of ways and this is as close as I've gotten. Hopefully it gives a view into what I'm trying to do.
Been following a tutorial on implementing an API key setup, and trying to modify it to be able to generate an APIKey for a user in the admin.
I've added a column to AdminUser to generate an APIKey
It is correctly linked to
#app/admin/admin_users.rb
ActiveAdmin.register AdminUser do
index do
column :email
column :current_sign_in_at
column :last_sign_in_at
column :sign_in_count
default_actions
column('API', :sortable => :id) { |resource| link_to "(Re)Generate Key", new_admin_admin_user_api_key_path(resource.id) }
end
end
Which gives me a (correct) link to
/admin/admin_users/:admin_user_id/api_keys/new(.:format)
# app/admin/api_key.rb
ActiveAdmin.register APIKey do
belongs_to :admin_user
controller do
def create_for admin_user
key = APIKey.new
key.assign_params_from_controller(admin_user)
APIKey.create(:admin_user_id => params[:admin_user_id]);
end
def permitted_params
params.permit create_for [:admin_user_id]
end
end
end
#app/models/api_key.rb
class APIKey < ActiveRecord::Base
before_create :generate_access_token
belongs_to :admin_user, :foreign_key => "id", :inverse_of => :api_key
attr_accessible :admin_user_id
def assign_params_from_controller(params)
#params = params
end
private
def generate_access_token
begin
self.admin_user_id = params.admin_user_id
self.access_token = SecureRandom.hex
end while self.class.exists?(admin_user_id: admin_user_id, acces_token: access_token)
end
end
This code gives me:
NameError in Admin::APIKeysController#new
undefined local variable or method `params' for #<APIKey:0x000000078d6470>
def generate_access_token
begin
self.admin_user_id = params.admin_user_id
self.access_token = SecureRandom.hex
end while self.class.exists?(admin_user_id: admin_user_id, acces_token: access_token)
end
UPDATE, ANSWER:
Problem solved.
Ended up being on the right track. But was straying off because of type conversion errors.
My admin_user_id ended up being a 'string' in the database. Nice copy-paste job there.
Wrote a migration first
class AlterTableAPIKeys < ActiveRecord::Migration
def up
execute "DELETE FROM `api_keys` WHERE 1"
change_column :api_keys, :access_token, :string, { null: false }
change_column :api_keys, :admin_user_id, :integer, { null: false }
add_column :api_keys, :active, :boolean, {null: false, default: true }
remove_column :api_keys, :role
add_index :api_keys, ["admin_user_id"], name: "index_api_keys_on_admin_user_id", unique: false
add_index :api_keys, ["access_token"], name: "index_api_keys_on_access_token", unique: true
end
end
I didn't pass the access_token to the create.
I ended up with this.
# app/admin/api_key.rb
ActiveAdmin.register APIKey do
belongs_to :admin_user
controller do
def new
key = APIKey.create(:admin_user_id => params[:admin_user_id])
{:access_token => key.access_token}
redirect_to admin_admin_users_path, :notice => "API Key #{key.access_token} created! "
end
def permitted_params
params.permit api_key: [:admin_user_id]
end
end
end
# app/models/api_key.rb
class APIKey < ActiveRecord::Base
attr_accessible :access_token, :expires_at, :admin_user_id, :active, :application
before_create :generate_access_token
before_create :set_expiration
belongs_to :admin_user
def expired?
DateTime.now >= self.expires_at
end
private
def generate_access_token
begin
self.access_token = SecureRandom.hex
end while self.class.exists?(access_token: access_token)
end
def set_expiration
self.expires_at = DateTime.now+30
end
end
Obviously this does not account for access, might solve that with roles (i.e. uberadmin can regenerate other admins API, admins only their own).