I have Account model with attribute role. Roles wrote with enum role: [:user, :admin]. I want that if left one account with role admin he can't update his role to user. I think need to write something like this: #account = Account.where(role: params[: role])... and then I don't know.
account.rb
class Account < ApplicationRecord
enum role: [:user, :admin]
end
accounts_controller.rb
class AccountsController < ApplicationController
def index
#accounts = Account.all
end
def update
#account = Account.find(params[:id])
redirect_to accounts_path if account.update(role: params[:role])
end
end
schema.rb
create_table "accounts", force: :cascade do |t|
t.integer "role", default: 0
end
I think what you want is a model callback before_update that acts like a validation.
class Account < ApplicationRecord
enum role: [:user, :admin]
before_update :insure_admin, if: -> { role == :admin && role_changed? }
private
def insure_admin
errors.add(:role, "admin cannot switch to regular user")
end
end
This should prevent the account.update(role: params[:role]) from returning true but you'll probably want to handle the error in your controller, something like:
class AccountsController < ApplicationController
def index
#accounts = Account.all
end
def update
#account = Account.find(params[:id])
if account.update(role: params[:role])
redirect_to accounts_path
else
redirect_to :back, flash: account.errors.full_messages
end
end
end
You might also want to add front end form validation to not allow the form to change role if the account is already persisted.
Related
I need write validation for role parameter of accounts_controller. If records with role admin only one, this record cannot update attribute role to user.
This validation must close security bug. But I don't know how it write.
accounts_controller.rb
class AccountsController < ApplicationController
def index
#accounts = Account.all
end
def update
#account = Account.find(params[:id])
redirect_to accounts_path if account.update(role: params[:role])
end
end
account.rb
class Account < ApplicationRecord
enum role: [:user, :admin]
end
schema.rb
create_table "accounts", force: :cascade do |t|
t.integer "role", default: 0
end
I trying write something like this: validate :role, less_than_or_equal_to: 1 but it didn't works.
account.rb
validate :the_last_admin
protected
def the_last_admin
if Account.admin.count < 2
errors.add(:role, 'You are the last admin!')
end
end
I wrote validation which check if role admin only one, role doesn't update on user. It's work, but then I have only one admin I can't update another users to admin. Wrote validation exception.
I try write math operators, like <= and <=> but it not working. I need what I can update user to admin if I have only one admin.
account.rb
class Account < ApplicationRecord
enum role: %i[user admin]
validate :admin_role
private
def admin_role
errors.add(:role, 'Must be one admin') if Account.where(role: :admin).count == 1
end
end
accounts_controller
class AccountsController < ApplicationController
def index
#accounts = Account.all.order(:id)
end
def update
#account = Account.find(params[:id])
if #account.valid?
#account.update(role: params[:role])
else
flash[:danger] = #account.errors.full_messages
end
redirect_to accounts_path
end
end
schema.rb
create_table "accounts", force: :cascade do |t|
t.integer "role", default: 0
end
I clearly made the role for users (as you can see down below) but it says it doesn't exist. Help please? By the way, you can see how I'm hardcoding myself.
app/controllers/application-controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def require_user
redirect_to '/login' unless current_user
end
def require_admin
redirect_to '/' unless current_user.admin
end
end
User.create(first_name: "Johnny", last_name: "Appleseed", email: "j.appleseed#example", password: "MY AWESOME PASSWORD THAT NOBODY KNOWS", role: "admin")
db/migrate/20160109170743_create_users:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.string :password_digest
t.string :role, :default => "reader"
t.timestamps null: false
end
end
end
app/controllers/users-controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :role)
end
end
Without knowing the specific error message, I can only speculate that your error is here:
def require_admin
redirect_to '/' unless current_user.admin
end
Regardless of the attributes you have in your model / db, you'll only get instance methods you've defined. You don't have admin in your User object, thus making current_user.admin invalid.
You'd need to use the following:
#app/models/user.rb
class User < ActiveRecord::Base
def admin?
self.role == "admin"
end
end
current_user.admin? #-> true / false
Whilst the question mark isn't required, it denotes the evaluation of an object's properties (true / false).
As an aside, you may want to look at adding an enum to your User model:
#app/models/user.rb
class User < ActiveRecord::Base
enum role: [:reader, :admin]
end
This will give you a series of instance & class methods to better help your logic:
#user = User.find params[:id]
if #user.admin?
...
#admins = User.admin
#-> collection of "admin" users
To do it, you'll need to change your role column from string to integer
I would suggest that you use boolean for the column role.
User.rb
def admin?
self.role == true
end
so you can do
redirect_to '/' unless current_user.admin?
I have 1:N relationship between user and post model. I want to access user_id in post model. I tried it by accessing current_user but it's throwing cannot find current_user variable.
My userModel class:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :validatable
has_many :post
validates_format_of :email, with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
end
MyPostModel class:
class Post < ActiveRecord::Base
belongs_to :user
before_create :fill_data
validates_presence_of :name, :message => 'Name field cannot be empty..'
def fill_data
self.is_delete = false
self.user_id = current_user # here I am getting the error
end
end
MyPostController class
class PostController < ApplicationController
before_action :authenticate_user!
def index
#post = Post.all
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to action: 'index'
else
render 'new'
end
end
.....
private
def post_params
params.require(:post).permit(:name,:user_id,:is_delete)
end
end
I can access the before_action :authenticate_user! in Post controller but not current_user in post model or controller. What I am doing wrong here in Post.fill_data. self.user_id?
Rest of the code is working fine and I can see the new entry of :name and :is_delete in sqlite3 database (when I am commenting self.user_id line in Post class).
Edit-1
I already have migration class for post
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :name
t.boolean :is_delete
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
In Rails your models should not be aware of the apps current user or any other state. They only need to know about themselves and the objects they are directly related to.
The controller on the other hand is aware of the current user.
So the proper way to do this would be to remove the fill_data callback from Post. And do it in the controller:
class PostController < ApplicationController
before_action :authenticate_user!
def index
#post = Post.all
end
def new
#post = current_user.posts.build
end
def create
#post = current_user.posts.build(post_params)
if #post.save
redirect_to action: 'index'
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:name,:user_id,:is_delete)
end
end
You should also set the default for your is_delete column in the database instead, but if you want to rock it like a pro use an enum instead.
Create a migration rails g migration AddStateToUsers and fill it with:
class AddStateToUsers < ActiveRecord::Migration
def change
add_column :users, :state, :integer, default: 0
remove_column :users, :is_delete
add_index :users, :state
end
end
We then use the rails enum macro to map state to a list of symbols:
class Post
enum state: [:draft, :published, :trashed]
# ...
end
That lets you do Post.trashed to get all posts in the trash or post.trashed? to check if a specific post is trashed.
notice that I use trashed instead of deleted because ActiveRecord has build in deleted? methods that we don't want to mess with.
You are trying to add current_user.id in post model using before_create call back. but better to do is use this
In posts_controller.rb
def new
#post = current_user.posts.new
end
def create
#post = current_user.posts.create(posts_params)
end
This will create a post for the current user.
Your fill_data method would be
def fill_data
self.is_delete = false
end
I tried this:
class CompaniesController < ApplicationController
after_action :set_default_role, only: [:create]
private
def set_default_role
#users.role ||= 'admin'
end
end
But it's not assigning the role to use user (in a nested attributes form).
Update:
class User < ActiveRecord::Base
before_create :set_default_role
belongs_to :company
def set_default_role
if Company.user.first
#user.role ||= 'admin'
end
end
end
As per the chat discussion with OP, OP had a nested form, while creating a Company a single user was getting created. There was only one change required in CompaniesController#create action:
def create
params[:company][:users_attributes]["0"][:role] = "admin" ## Add this
#company = Company.new(company_params)
if #company.save
redirect_to #company, notice: 'Company was successfully created.'
else
render action: 'new'
end
end
I would do it in the following way:
class User < ActiveRecord::Base
before_create :set_default_role
belongs_to :company
def set_default_role
if User.where(:company_id => company.id).empty?
self.role ||= 'admin'
end
end
end