Undefined Wrapper Method - ruby-on-rails

I'm having a trouble understanding why I cannot successfully call my wrapper method in my User model. My problem lies with this line self.password_hash = hash_check(password, password_salt) in the encrypt_password method shown below. My hash_check method works correctly in the authenticate method so I'm a bit stumped. When I run my test I get the error undefined method 'hash_check' for #<User:0x007f94851a5f88>
class User < ActiveRecord::Base
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email
def encrypt_password
if password.present?
self.password_salt = generate_salt
self.password_hash = hash_check(password, password_salt)
end
end
def self.authenticate(email, password)
user = find_user(email)
user && user.password_hash == hash_check(password, user.password_salt) ? user : nil
end
def self.find_user(email)
user = find_by_email(email)
end
private
def self.hash_check(password, password_salt)
BCrypt::Engine.hash_secret(password, password_salt)
end
def generate_salt
BCrypt::Engine.generate_salt
end
end
require 'spec_helper'
describe 'User' do
let(:user) { User.create(email: 'user#gmail.com', password: "secure_password") }
it 'creates a user object' do
user.should be_kind_of User
end
describe '.find_user' do
let(:valid_user_search) { User.find_user('user#gmail.com') }
let(:invalid_user_search) { User.find_user('notregistered#gmail.com') }
it 'returns a user by their email' do
user.should == valid_user_search
end
it 'return nil if no user if found' do
invalid_user_search.should == nil
end
end
describe '.authenticate' do
let(:auth_user) { User.authenticate(user.email, user.password) }
let(:non_auth_user) { User.authenticate('notregistered#gmail.com', 'invalidpass') }
it 'returns an valid user' do
auth_user.should == user
end
it 'returns nil on an invalid user' do
non_auth_user.should == nil
end
end
describe '.encrypt_password' do
it 'returns a password salt'
it 'return a password hash'
end
end

self.hash_check is a class method (because you put self). It works in self.authenticate because it is also a class method, (as it doesn't rely on an instance). HOWEVER, it won't work on an instance method like encrypt_password because you are not invoking the class method at all.
So you are going to need to replace hash_check(password, password_salt) in your instance method with self.class.hash_check(password, password_salt) or User.hash_check(password, password_salt) to be able to use a class method
Read more about the nuances here

Related

rails rspec error expected string got nil?

I am new to testing in rails, I am trying to pass the test as below where it kept throwing back errors as
/scooties_coupon POST Coupon /scooties_coupons/:coupon_id with coupon coupon_id redeemed API_KEY returns redeemed id
Failure/Error: expect(json['coupon']).to eq(scooties_coupon.coupon)
expected: "6B2F5"
got: nil
(compared using ==)
Here is the code for the test I am running:
context 'with coupon coupon_id redeemed API_KEY' do
subject { post "/api/scooties_coupons/#{scooties_coupon.id}", headers: headers }
it "returns redeemed id" do
subject
expect(json['coupon']).to eq(scooties_coupon.coupon)
expect(json['redeemed']).to eq(scooties_coupon.redeemed)
end
end
my factory code:
FactoryBot.define do
factory :scooties_coupon do
coupon { "43MDA" }
redeemed { false }
email { "email#host.com" }
trait :redeemed do
redeemed { true }
end
end
end
here's the model:
class ScootiesCoupon < ApplicationRecord
has_paper_trail
before_create :set_coupon_code
after_update :send_activecamp_email_scootiescoupon
validates :email, presence: true, format: { with: /\A[^#\s]+#([^#.\s]+\.)+[^#.\s]+\z/ }
def update_redeemed(redeemed = true, email = nil)
email == self.email if email.present? || self.email.present?
self.created_at + self.expires_in.seconds < DateTime.now if self.expires_in.present?
self.update_attribute(:redeemed, redeemed)
end
private
def set_coupon_code
loop do
self.coupon = gen_coupon
break unless coupon_exists?
end
end
def coupon_exists?
self.class.exists?(coupon: coupon)
end
def gen_coupon
return SecureRandom.hex[0..4].upcase
end
end
I've tried to get rid of the validation on email but it doesn't seem to b e a major problem here, Could you give me a little idea of any other way to solve this problem?

Check if Form input was empty

I have the following parameters
def note_params
params.require(:note).permit(
:content
)
end
Now i am trying to check of the content was empty for :content i am passing this to a service object
def add_note_to_plan
unless #note_params.content.empty?
puts "======================================================"
note = Note.new(
note_params.merge(
plan: #plan,
user: #current_user
)
)
note.save
end
puts "=================== outside ==================================="
end
Service Object
class PlanCreator
def initialize(current_user, venue_params, plan_params, note_params)
#current_user = current_user
#venue_params = venue_params
#plan_params = plan_params
#note_params = note_params
end
attr_reader :venue, :plan
def create
#venue = new_or_existing_venue
#plan = new_or_existing_plan
save_venue && save_plan && add_current_user_to_plan && add_note_to_plan
end
def errors
{
venue: venue_errors,
plan: plan_errors,
note: note_errors
}
end
private
attr_reader :current_user, :venue_params, :plan_params, :note_params
..... Removed all the unnecessary methods
def add_note_to_plan
unless #note_params.content.empty?
puts "======================================================"
note = Note.new(
note_params.merge(
plan: #plan,
user: #current_user
)
)
note.save
end
puts "=================== outside ==================================="
end
end
Error:
NoMethodError - undefined method `content' for
{"content"=>""}:ActionController::Parameters:
Change this:
unless #note_params.content.empty?
To this:
unless #note_params[:content].empty?
ActionController's params returns a hash of parameters.

Pundit Multiple Roles Index Spec

I have the following spec, policy and controller. For some reason, my permissions' index spec for business user is not passing, but it works on the front-end. My spec might be wrong or I might be overlooking something. Many thanks in advance for help.
App Policy
class AppPolicy
attr_reader :user, :model
def initialize(user, model)
#user = user
#app = model
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.super_user? || user.admin?
scope.all
elsif user.business_user? || user.user?
scope.where(user_id: user.id)
else
scope.none
end
end
end
def index?
#user.admin? || #user.super_user?|| #user.apps || #user == #apps.user
end
** App Controller**
class AppController < ApplicationController
def index
#apps = policy_scope(App).order(:name).page(params[:page]).per(25)
authorize #apps
#user = current_user
end
** App Policy Spec**
describe AppPolicy do
subject { AppPolicy }
let (:other_user) { FactoryGirl.build_stubbed :user }
let (:business_user) { FactoryGirl.build_stubbed :user, :business_user }
let (:admin) { FactoryGirl.build_stubbed :user, :admin }
let (:super_user) { FactoryGirl.build_stubbed :user, :super_user }
let (:app) { FactoryGirl.build_stubbed :app }
permissions :index? do
it "allows access for a regular user" do
expect(AppPolicy).to permit(app.user, app)
end
#failing spec below
it "denies access for a business user" do
expect(AppPolicy).not_to permit(business_user, app)
end
it "allows access for an admin" do
expect(AppPolicy).to permit(admin, app)
end
it "allows access for an super user" do
expect(AppPolicy).to permit(super_user, app)
end
end
** Update **
I was testing the wrong things. Will post answer on my solution.

Which behavior of this method I should test?

I would like to test save method:
class Note
def initialize(password)
#password = password
end
def save
encryption = Note::Encryption.new(#password)
encrypted = encryption.encrypt(serialized)
storage = Note::Storage.new
storage.write(encrypted)
end
private
def serialized
{some_data: true}
end
# .....
end
Method just delegates work to the other classes mainly, and that's the only responsibility. My first bet to testing it is just checking:
describe '#save' do
let(:encryption){ '12345' }
it 'calls encryption' do
expect_any_instance_of(Note::Encryption).to receive(:encrypt)
subject.save
end
it 'saves the file with data' do
expect_any_instance_of(Note::Storage).to receive(:write)
subject.save
end
end
I've problem with this approach as I'm little bit afraid that these tests don't test too much... Moreover test now is high coupled with the implementation not behavior. Do anyone know how to approach to this kind of methods. Worth to mention that this class would be at the top of system as wraps some resource.
I would test a bit more, because I would like to be sure about the arguments passed to that methods:
class Note
def initialize(password)
#password = password
end
def save
encrypted = Note::Encryption.new(#password).encrypt(serialized)
Note::Storage.new.write(encrypted)
end
private
def serialized
{some_data: true}
end
end
# the test
describe '#save' do
let(:password) { 'secret password' }
let(:encryted) { 'encrytped string' }
let(:storage) { double(:write => true) }
let(:note_encryption) { double(:encrypt => encrypted) }
subject(:note) { Note.new(password) }
before do
allow(Note::Encryption).to receive(:new).with(password).and_return(note_encryption)
Note::Storage.stub(:new => storage)
end
it 'encrypted the password' do
note.save
expect(Note::Encryption).to have_received(:new).with(password)
expect(note_encryption).to have_received(:encrypt).with(serialized) # mock serialized?
end
it 'stores the encryted string' do
note.save
expect(storage).to have_received(:write).with(encrypted)
end
end
If you look at the method, there are a few discrete things that it is doing:
def save
encryption = Note::Encryption.new(#password)
encrypted = encryption.encrypt(serialized) # first action
storage = Note::Storage.new
storage.write(encrypted) # second action
end
Those are the things you should test, along with validations - what happens if #password is an empty string?
Tests could look something like this:
let(:password) { 'my_pass' }
let(:note) { Note.new(password) }
let(:result) { note.save }
describe 'save' do
describe 'with a blank password' do
let(:password) { '' }
it 'fails' do
# assert_raises...
end
end
it 'encrypts the password' do
refute_same password, result #of course, this depends on what Storage.write returns...
end
it 'saves the password' do
# assert that the password is saved
end
end
Also, if I can suggest a refactor -
class Note
def initialize(password)
#password = password
end
def save
encrypted = build_encrypted_object
storage.write(encrypted)
end
private
def build_encrypted_object
encryption.encrypt(serialized)
end
def serialized
{some_data: true}
end
def encryption
Note::Encryption.new(#password)
end
def storage
Note::Storage.new
end
# .....
end

How am I supposed to use Devise and Omniaauth in Database Mongodb?

I did look Ryan Bates episodes to use devise with omniauth. Problem is that I am able to sign up with linkdin. My code
In my user.rb
field :provider, :type => String
field :uid, :type => String
field :name, :type => String
#has_many :authentications
def self.from_omniauth(auth)
where(auth.slice("provider", "uid")).first || create_from_omniauth(auth)
end
def self.create_from_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["nickname"]
end
end
I add this and in my create controller for authentication I did
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url, notice: "Signed in!"
I am succeded to put the value of linkdin in my user database as
{ "_id" : ObjectId("50b2f4e66d7ab88ac7000003"), "email" : "", "encrypted_password" : "", "sign_in_count" : 0, "provider" : "linkedin", "uid" : "wuBFLcbDyB", "name" : null, "updated_at" : ISODate("2012-11-26T04:49:42.549Z"), "created_at" : ISODate("2012-11-26T04:49:42.549Z") }
But as I login from linkdin it does not signup through linkdin else it redirects to
http://localhost:3000/users/sign_in
How can I login through that linkdin?
If you have something like this in your user model
validates :username, presence: true
Then you must know that linked in does not provide you any username. Since that, to complete your authentication / registration, your user has to add explicitly his username.
Make sure that your registrations_contreoller.rb looks like this
class RegistrationsController < Devise::RegistrationsController
def create
super
end
private
def build_resource(*args)
super
if session[:omniauth]
#user.apply_omniauth(session[:omniauth])
#user.valid?
end
end
end

Resources