Factory Girl Passing nil to user model - ruby-on-rails-4

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.

Related

RSpec, Devise - Could not find valid mapping error

I have a Devise User model and in my application I have different roles which I am specifying through an enum in my User model. When I am running the tests for the admin role, I am receiving the following error when running RSpec tests with Devise. I have tried some of the other answers to similar issues but nothing seems to be working. I hope you can point me in the right direction. Thanks!
RuntimeError:
Could not find a valid mapping for {:email=>"collin_cain#torpdoyle.info", :password=>"12345678", :password_confirmation=>"12345678", :role=>2}
Here is the User model:
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 :comments
enum role: [:member, :moderator, :admin]
before_save :set_default_role
def set_default_role
self.role ||= 0
end
end
The user factory:
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
password "12345678"
password_confirmation "12345678"
role 0
end
end
The categories controller spec
require 'rails_helper'
RSpec.describe Admin::CategoriesController, type: :controller do
it 'should redirect to sign in path for non signed users' do
get :index
expect(response).to redirect_to(new_user_session_path)
end
it 'should redirect to root path for non admin users' do
user = create(:user)
sign_in user
get :index
expect(response).to redirect_to(root_path)
end
describe 'GET #index' do
context 'when admin signed in' do
it 'renders the index template' do
admin = attributes_for(:user, role: 2)
sign_in admin
get :index
expect(response).to render_template(:index)
end
it 'assigns a list of categories' do
admin = attributes_for(:user, role: 2)
sign_in admin
category = create(:category)
expect(assigns(:categories)).to eq([category])
end
end
end
end
and the routes file
Rails.application.routes.draw do
devise_for :users
namespace :admin do
get '', to: 'dashboard#index', as: '/'
resources :categories
end
resources :topics do
resources :comments, only: :create
end
resources :categories do
resources :topics
end
root 'categories#index'
end
I am also adding the User schema
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.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "role"
t.string "image"
end
UPDATE:
I have updated the admin categories controller spec, specifically Devise's sign_in method from sign_in user to sign_in(:admin, user) as shown below.
describe 'GET #index' do
context 'when admin signed in' do
it 'renders the index template' do
user = create(:user)
user.role = 2
sign_in(:admin, user)
get :index
expect(response).to render_template(:index)
end
...
Now I am getting the following error
1) Admin::CategoriesController GET #index when admin signed in renders the index template
Failure/Error: expect(response).to render_template(:index)
expecting <"index"> but was a redirect to <http://test.host/users/sign_in>
For some reason the admin is not being signed in, I have included Devise Test Helpers in rails_helper.rb file, unfortunately the error continues. Any help will be greatly appreciated.
Have you declared the role in the migration like
t.integer :role
As this would need to be in there to be included in the migration created structure.
If not
Add the line, in your database drop the table and run your rake again
I was able to troubleshoot my own question and decided to post the answer in hope that it will help someone in the future.
Instead of setting the user role to admin in the the admin_categories_controller_spec file, instead I added a nested Factory inside the Users Factory.
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
password "12345678"
password_confirmation "12345678"
role 0
factory :admin do
role 2
end
end
end
and the test ends up like this:
describe 'GET #index' do
context 'when admin signed in' do
it 'renders the index template' do
admin = create(:admin)
sign_in admin
get :index
expect(response).to render_template(:index)
end
it 'assigns a list of categories' do
admin = create(:admin)
sign_in admin
category = create(:category)
get :index
expect(assigns(:categories)).to eq([category])
end
end
end

ActiveRecord::UnknownAttributeError in FavoritesController#create

I am getting unknown attribute 'post' for Favorite. This error normally occurs when I am missing a column in a table. The error is stating this portion is where the issue lies: favorite = current_user.favorites.build(post: post). But there shouldn't be another column called post. Do I have another nested set of params I am missing perhaps that is layered under something? Or is my referential integrity incorrect?
Screenshot of error
Favorites_controller.rb file
class FavoritesController < ApplicationController
def create
post = Post.find(params[:post_id])
favorite = current_user.favorites.build(post: post)
if favorite.save
flash[:notice] = "This post is now favorited."
redirect_to [post.topic, post]
else
flash[:error] = "There was an error favoriting you post. Please try again."
redirect_to [post.topic, post]
end
end
end
Favorites schema
create_table "favorites", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "post_id"
end
Post Controller
class Post < ActiveRecord::Base
has_many :comments, dependent: :destroy
has_many :votes
has_one :summary
belongs_to :user #means the post table has the user table's primary k ey in it
belongs_to :topic
has_many :favorites
#has_one :summary
mount_uploader :avatar, AvatarUploader
#default_scope {order('created_at DESC')}
default_scope {order('rank DESC')}
validates :title, length: {minimum: 5}, presence: true
validates :body, length: {minimum: 20}, presence: true
validates :topic, presence: true
validates :user, presence: true
def create_vote
# self == post
user.votes.create(value: 1, post: self)
end
def markdown_title
(render_as_markdown).render(self.title).html_safe
end
def markdown_body
(render_as_markdown).render(self.body).html_safe
end
def up_votes
votes.where(value: 1).count
end
def down_votes
votes.where(value: -1).count
end
def points
votes.pluck(:value).sum
end
def update_rank
age_in_days = (created_at - Time.new(1970,1,1)) / (60 * 60 * 24) #1 day in seconds
new_rank = points + age_in_days
update_attribute(:rank, new_rank)
end
private
def render_as_markdown
renderer = Redcarpet::Render::HTML.new
extensions = {fenced_code_blocks: true}
redcarpet = Redcarpet::Markdown.new(renderer, extensions)
return redcarpet
end
end
Post table schema
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.integer "topic_id"
t.string "avatar"
t.float "rank"
end
Favorites route
rake routes | grep -i favorites
post_favorites POST /posts/:post_id/favorites(.:format) favorites#create
post_favorite DELETE /posts/:post_id/favorites/:id(.:format) favorites#destroy
This line
favorite = current_user.favorites.build(post: post)
should be
favorite = current_user.favorites.build(post_id: post.id)
Because you have post_id in your favourites table not post.

Mysterious "Validation failed: Password is too short" in Rails4

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

Rails Tutorial: NameError in sessions_helper

I am developing a simple application which re-used some of the code of the Sample application of the famous Rail Tutorial of Michael Hartl. More specifically, I am re-using the User model but have re-named it as "Account". I think I have replaced all the references to the User model but somehow can't make my code work. Here is my code:
class Account < 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 }
has_secure_password
attr_accessor :remember_token
validates :password, length: { minimum: 6 }
# 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
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
Here is the Session helper:
module SessionsHelper
# Logs in the given user.
def log_in(account)
session[:account_id] = account.id
end
# Returns the current logged-in user (if any).
def current_account
#current_account ||= Аccount.find_by(id: session[:account_id])
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_account.nil?
end
end
Here is the header partial:
<header class="navbar navbar-default navbar-fixed-top">
<div class="container">
<a class="navbar-brand" href=<%= root_path %>><font color="red">Sample Application<sup>®</sup></font></a>
<nav>
<ul class="nav navbar-nav">
<% if logged_in? %>
<% else %>
<li class="active"><a href=<%= root_path %>>Home</a></li>
<li><a href=<%= demo_path %>>Try Demo</a></li>
<li><a href=<%= pricing_path %>>Pricing & Sign up</a></li>
<li>Login</li>
<% end %>
</ul>
</nav>
</div>
</header>
When I run the code I am getting
NameError in StaticPages#home
Showing /Users/nnikolo/Documents/private/rails_projects/appmate/app/views/layouts/_header.html.erb where line #6 raised:
undefined local variable or method `Аccount' for #<#<Class:0x007fbc1ede96f8>:0x007fbc1ede8b18>
app/helpers/sessions_helper.rb:10:in `current_account'
app/helpers/sessions_helper.rb:15:in `logged_in?'
app/views/layouts/_header.html.erb:6:in `_app_views_layouts__header_html_erb___3728230762564015047_70222988074560'
app/views/layouts/application.html.erb:20:in `_app_views_layouts_application_html_erb___3720454973504965845_70222923917160'
In other words, for some reason the Session helper cannot recognise the Account class. The same code in the Tutorial works when Account is replaced by User.
Interestingly, when I decided to include the Account model in the SessionsHelper (which I should not need do but I did it just as an experiment) I am getting
wrong argument type Class (expected Module)
You can find more details in this screenshot:
What's the problem? Why can't SessionsHelper see the Account model? In fact, it cannot see any of the models - I replaced "include Account" with "include Reminder" (another ActiveRecord model I have) and I get the same error message. All the models shall be visible to the helper - why is this not the case here?
P.S. I did run migration and I don't think the problem is there but here is the relevant section of the schema.rb:
create_table "accounts", force: true do |t|
t.string "password_digest", null: false
t.string "remember_digest"
t.string "activation_digest"
t.boolean "activated", default: false
t.datetime "activated_at"
t.string "title"
t.string "first_name"
t.string "last_name"
t.string "company"
t.string "email", limit: 100, null: false
t.integer "phone", limit: 8, null: false
t.integer "rem_notice_hrs", null: false
t.string "rem_text", limit: 140, null: false
t.datetime "start_day", null: false
t.datetime "end_day"
t.datetime "created_at"
t.datetime "updated_at"
end
From what it seems, either you havent changed the migration to reflect Account or you havent run the migration for that file. Please share the contents of your schema.rb file.
I ended up naming 'Account' as 'User'. Everything works now. Still don't know what was wrong. Seems like there is some sort of rails 'magic' associated with the 'User' name.
Account is a class.
SessionsHelper is a Module.
You can't include Account into SessionsHelper because a Class can't be mixed-in a Module. It's the other way round.
That's why you're getting that TypeError at StaticPages#home.

Rails 4 + ActiveAdmin passing params to another model and creating an object (with relations)

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