NameError when bypassing mass assignment while updating a user? - ruby-on-rails

I followed the Railscast tutorial for bypassing mass assignment to edit my role attribute of my User model as the "admin". This is how I defined my roles:
class User < ActiveRecord::Base
attr_accessible :email, :password, :remember_me
attr_accessor :accessible
devise :database_authenticatable, ....etc
before_create :setup_default_role_for_new_users
ROLES = %w[admin default banned]
private
def setup_default_role_for_new_users
if self.role.blank?
self.role = "default"
end
end
def mass_assignment_authorizer
super + (accessible || [])
end
end
And then I created a new UsersController only to have issues with my update method:
def update
#user = User.find(params[:id])
#user.accessible = [:role] if user.role == "admin"
if #user.update_attributes(params[:user])
redirect_to #user, :notice => "Successfully updated user."
else
render :action => 'edit'
end
end
I can't do this though becuase this line: if user.role == "admin", is causing issue, giving me the error:
NameError (undefined local variable or method `user' for UsersController
What am I missing here?
Thanks in advance.

With the user part in user.role == "admin" you're trying to use a local variable, which hasn't been defined in your update method. If user isn't declared as a helper method that's accessible in your controllers then ruby won't find it.
From your code I'm assuming that only an admin user can update the role of another user? Thus you're not using #user.role == "admin" but user.role == "admin"?
If so you have to provide a user object whether it's through a helper method (i.e. in your ApplicationHelper class) or fetch it before you try to use it in your update method, or with a before_* callback in your controller.
I hope it's clear what I meant.

Related

Restrict view for various roles in pundit

I am following up from a problem that I had before. I was able to get the code to work for three roles, but I need to include 4 roles in the mix.
The problem: I have 4 roles (user, business user, super user, and admin). Admins have access to everything (user index). Super users can only see both users and business users (user index).
The error: I have a functioning app that allows admins to have access to everything, but my super users can only see users (and not business users). I tried switching in the User Policy resolve method, for the super user to role: 'business_user' to see if that even worked. Well, it does not work and it only shows me users (not business_users). It's probably a simple ruby issue that I'm overlooking.
User Policy
class UserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#user = model
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else user.super_user?
scope.where(role: 'user')
end
end
end
def index?
#current_user.admin? or #current_user.super_user?
end
end
User Controller
class UsersController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized
def index
#users = policy_scope(User)
authorize #users
end
[rest of the controller]
User Model
class User < ActiveRecord::Base
enum role: [:user, :business_user, :super_user, :admin]
[rest of model]
end
Can you try to change this method :
def resolve
if user.admin?
scope.all
else user.super_user?
scope.where(role: 'user')
end
end
By this :
def resolve
if user.admin?
scope.all
else user.super_user?
scope.where('role == "user" or role == "business_user"').all
end
end
You have to change your query to have both roles.
I figured out what I had to do. It was a two step process. First, I had to change the role to the numerical value that pundit stores it as instead of the string, so the role would be 0 & 1. Second, I used an array to feed them into the param so it would accept multiple options.
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
elsif user.super_user?
scope.where(role: [1,0])
else
scope.none
end
end
end

devise: is it possible to sign in user in the model?

My app creates a user in a callback of a model and I can't figure out how to seamlessly sign him in after that, as sign_in helper is available only in the controller.
after_validation do
return unless errors.empty?
if create_account == "1"
begin
self.user ||= User.create!(...)
rescue => e
errors.add(:create_account, 'bla bla')
end
end
end
So how to sign in user after it has been created (in the model)?
You can not sign in a user through the model and you don't want to do that either.
Why are you using a validation callback here?
If you move part of this logic to your controller you can easily achieve what you want.
def MyController < ApplicationController
def my_action
my_instance.user ||= User.new(...)
if my_instance.user.save
sign_in my_instance.user
end
end
end

ActiveAdmin and Devise - skip_confirmation! on create action

I want to call user.skip_confirmation while his account is created by admin in admin panel. I want user to confirm his account in further steps of registration process, but not on create. The only idea I have is to override create in controller:
controller do
def create
user = User.new
user.skip_confirmation!
user.confirmed_at = nil
user.save!
end
end
The problem is, I have different attr_accessibles for standard user and admin, and it works, because ActiveAdmin uses InheritedResources:
attr_accessible :name, :surname
attr_accessible :name, :surname, invitation_token, :as => :admin
It doesn't work after I changed create (it worked before). How can I do what I want and still be able to use this :as => :admin feature?
I look at the answer and none is solving the issue at hand. I solve it the simplest way as shown below.
before_create do |user|
user.skip_confirmation!
end
controller do
def create
#user = User.new(params[:user].merge({:confirmed_at => nil}))
#user.skip_confirmation!
create! #or super
end
def role_given?
true
end
def as_role
# adapt this code if you need to
{ :as => current_user.role.to_sym }
end
end
something like that could work
EDIT: if you define role_given? to return true and as_role, InheritResources will use as_role to get the role information
also
controller do
with_role :admin
end
works, but this way you can't change the role given the user.
At your /app/models/user.rb
before_create :skip_confirmation
def skip_confirmation
self.skip_confirmation! if Rails.env.development?
end

Authentication from scratch: "undefined method `find_by_username`"

I'm trying to include a simple user authentication into my application, based on a filemaker database (using the ginjo-rfm gem). After getting some ideas from Ryan Bates' Authentication from Scratch, I've written a customized version of it, but running into some problems.
When I submit my login form, I'm presented with
undefined method `find_by_username' for User:Class
The find_by_username method should be based on a column in the database called 'username', is it not?
User.rb
class User < Rfm::Base
include ActiveModel::SecurePassword
include ActiveModel::MassAssignmentSecurity
include ActiveModel::SecurePassword
has_secure_password
attr_accessible :username, :password
config :layout => 'web__SupplierContacts'
def self.authenticate(username, password)
user = find_by_username(username)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.authenticate(params[:username], params[:password])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
flash.now.alert = "Email or password is invalid"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Logged out!"
end
end
I'm guessing this is a problem with my model inheriting from Rfm::Base, but I'm not sure. Any ideas?
Idea:
Is there any way to rephrase the Class.find_by_column statement? I'm not able to do User.where(:username => "username usernamerson", either (returns undefined method 'where' for User:Class).
If Rfm::Base does not extend ActiveRecord, then you won't be able to use the activerecord db query methods like find, where, etc. -- they are part of the ActiveRecord class and only available to classes which inherit from it.
If you want to include database wrapper methods in a class which extends another class (in this case Rfm::Base), you might have a look at DataMapper, which takes the form of a module (and thus can be included in any class). (DataMapper can be used as a replacement for ActiveRecord in Rails apps.)
Also, you've included ActiveModel::SecurePassword twice:
class User < Rfm::Base
include ActiveModel::SecurePassword
include ActiveModel::MassAssignmentSecurity
include ActiveModel::SecurePassword
I'd delete one of those.

create a count method for user logins with ruby on rails

what I want: A user logs in to his account he and automatically updates his own counter (#counter += 1).
I am new to Ruby and Rails and I am using Rails 3.2.12. I read the book "eloquent ruby", searched stackoverflow regarding this question and watched a video-ruby-course from pragmaticstudio.com. In that video-course they created a Class like this:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :counter
has_secure_password
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
def initialize(counter=0)
#counter = counter
end
def w00t
#counter += 15
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
Now in my App the User log-in is settled with a SessionsController and here come my problems because every method from the User model is "unknown" to the SessionsController.
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or templates_path
else
flash.now[:error] = 'something went wrong.'
render 'new'
end
end
def destroy
sign_out
redirect_to root_url
end
end
Here is what I already tried but didn't work for my solution: i added
user.w00t
in the SessionsController, 1 line above
sign_in user
the returned error was: "undefined methode 'w00t' for SessionsController".
I also tried to write a method in the Sessions Helper:
def woot(template)
template.counter += 1
end
then I re-ordered my SessionsController 'create' method like so:
def create
template = Template.find_by_bosskey(params[:bession][:bosskey])
if template
woot template #that is my new line !
tsign_in template
redirect_back_or template
else
flash.now[:error] = 'something went wrong.'
render 'new'
end
end
With this i did not get any errors BUT still the counter doesnt change. I am more confused then ever. Please tell me WHERE to put that method or how to fix this problem for my app I am lost.
Your counter isn't being incremented because it's not being persisted to the database. Your using an instance variable which is only valid for the current request. As soon as you redirect and reload the page, that object is lost to the garbage collector, along with your counter.
To make the counter persistent you need to create a new column on user to hold the counter, then you can use the increment methods that Rails provides.
# create the migration
rails g migration add_sign_in_count_to_users sign_in_count:integer
rake db:migrate
# Then increment
class User < ActiveRecord::Base
def w00t
increment! :sign_in_count
end
end
ActiveRecord::Persistence#increment!

Resources