RSpec, Devise - Could not find valid mapping error - ruby-on-rails-4

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

Related

Trouble with Capybara and Admin User permissions (and Devise)

Devise for User registration and login.
Admins are set using the rails console (setting the admin boolean to true).
Rspec and FactoryGirl.
Unfortunately, I wrote my app before writing tests (the best lessons are learned the hard way). I am now learning rspec and writing a test suite for the app.
I have controller permissions and view permissions set up, for admins and non-admins, which DO work in practice (I know this through thorough browser manual testing).
In this case, I have an "Admin" link in the header which is displayed when a user is logged-in and is also an admin.
My StaticPagesController also has an effective before_action setup so no-one can access the admin page unless they are logged-in and are also an admin.
I wrote some tests for this, and thought I had it sorted, until I noticed that, when making changes to the specific /features spec file which contains these tests, guard runs only those tests and passes them. However, when I run the entire test suite, those same tests fail. I totally confused by this.
I think it may have something to do with Devise, but I just don't know.
spec/features/user_and_role_spec.rb
require 'rails_helper'
def manually_create_user
visit new_user_registration_path
fill_in('user_first_name', :with => 'Test')
fill_in('user_last_name', :with => 'User')
fill_in('user_email', :with => 'testuser#email.com')
fill_in('user_password', :with => 'testuser')
click_button('Sign up')
end
def create_user_and_login_as(type)
user = FactoryGirl.create(type)
visit(new_user_session_path)
fill_in('user_email', :with => user.email)
fill_in('user_password', :with => user.password)
click_button('Log in')
end
describe 'with users and roles' do
context "if user is not an admin" do
it "does not allow any user to visit the admin page if not logged-in" do
visit(admin_path)
expect(current_path).to eq(root_path)
end
it "does not allow a new user to visit the admin page" do
manually_create_user
visit(admin_path)
expect(current_path).to eq(root_path)
end
it "does not allow a student to visit the admin page" do
create_user_and_login_as(:student)
visit admin_path
expect(current_path).to eq(root_path)
end
it "does not allow a teacher to visit the admin page" do
create_user_and_login_as(:teacher)
visit admin_path
expect(current_path).to eq(root_path)
end
end
context "if user is an admin" do
it "allows an admin user to visit the admin page" do
create_user_and_login_as(:admin_user)
click_link 'Admin'
expect(current_path).to eq(admin_path)
end
it "allows a teacher_admin to visit the admin page" do
create_user_and_login_as(:teacher_admin_user)
click_link 'Admin'
expect(current_path).to eq(admin_path)
end
end
end
The tests in the context "if user is not an admin" all FAIL when running the full test suite. They all fail with the same error:
Failure/Error: expect(current_path).to eq(root_path)
expected: "/"
got: "/admin"
(compared using ==)
Which, to me, means the admin page was accessible, when it shouldn't have been. In my browser, the admin page link cannot be seen, neither can the page be accessed by manually typing in the url, unless the user is signed in and is an admin.
The tests in the context "if user is an admin" all PASS when running the full test suite.
spec/factories/users.rb:
require 'faker'
FactoryGirl.define do
factory :user do |f|
f.first_name { Faker::Name.first_name }
f.last_name { Faker::Name.last_name }
f.email { Faker::Internet.email }
f.password { Faker::Internet.password(8) }
f.admin false
trait :student do
type "Student"
end
trait :teacher do
type "Teacher"
end
trait :admin do
admin true
end
factory :admin_user, traits: [:admin]
factory :student, traits: [:student]
factory :teacher, traits: [:teacher]
factory :teacher_admin_user, traits: [:teacher, :admin]
end
end
static_pages_controller.rb:
class StaticPagesController < ApplicationController
before_action :admin?, only: [:admin]
def home
#testimonials = Testimonial.all
end
def admin
#groups = Group.all
#users = User.all
#students = Student.all
#teachers = Teacher.all
end
private
def admin?
unless signed_in? and current_user.admin == true
redirect_to root_path, notice: "You must be a signed-in admin to view this page"
end
end
end
static_pages_helper.rb:
module StaticPagesHelper
def allowed_to_see_admin_link?
signed_in? && current_user.admin
end
end
models/user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :first_name, presence: true
validates :last_name, presence: true
validates :admin, inclusion: { in: [true, false] }
scope :newest_first, -> { order("created_at DESC") }
scope :order_by_first_name, -> { order("first_name") }
def full_name
"#{first_name} #{last_name}"
end
def unassigned?
type != "Student" and type != "Teacher"
end
def can_view_materials?
admin || type == "Teacher" || type == "Student" && groups.any? # So only current students can view the Materials page.
end
def testimonial_owner?(testimonial)
id == testimonial.student_id
end
end
Relevant part of the _header.html.erb partial:
<ul class="nav navbar-nav navbar-right">
<% if allowed_to_see_admin_link? %>
<li><%= link_to "Admin", admin_path %></li>
<% end %>
</ul>
Gemfile:
gem 'rails', '4.2.0'
gem 'bootstrap-sass', '~> 3.3.3'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'jquery-rails'
gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'devise'
group :development, :test do
gem 'sqlite3'
gem 'byebug'
gem 'web-console', '~> 2.0'
gem 'spring'
gem 'better_errors'
gem 'binding_of_caller'
gem 'rspec-rails'
gem 'guard-rspec', require: false
gem 'factory_girl_rails'
end
group :test do
gem 'faker'
gem 'capybara'
gem 'launchy'
gem 'database_cleaner'
end
group :production do
gem 'pg'
gem 'rails_12factor'
end
users in the 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.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "admin", default: false
t.string "type"
t.string "first_name"
t.string "last_name"
end
routes.rb:
resources :materials
root 'static_pages#home'
devise_for :users, :controllers => { registrations: 'registrations' }
get 'admin' => 'static_pages#admin'
resources :groups
resources :users
resources :students
resources :teachers
resources :testimonials
post 'assign_to_group' => 'students#assign_to_group' # Could have been 'patch', but default in the controller method is 'post', so I left the method as default and changed this route to 'post'. Doesn't NEED to be patch.
post 'remove_from_group' => 'students#remove_from_group'
post 'unassign_teacher' => 'groups#unassign_teacher'
post 'assign_as_student' => 'teachers#assign_as_student'
post 'assign_as_teacher' => 'students#assign_as_teacher'
post 'add_student' => 'groups#add_student'
post 'remove_student_from_group' => 'groups#remove_student_from_group'
The reason your tests are failing are that you are checking the current_path before the redirect has finished. Basically you're calling visit(xxx), which sets the current_path to xxx, and then immediately reading back xxx, while the server returns a redirect back to / and then the browser changes current_path to /. As long as you're using Capybara 2.5+ you should be using the have_current_path matcher which will retry for a bit, thereby giving the redirect time to be processed
expect(page).to have_current_path(root_path)

Devise::InvitationsController reports Unpermitted parameters

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

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.

Factory Girl Passing nil to user model

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.

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