I have a user model with a nested model for user's informations, and i have already a working country field with country_select gem, but it doesn't offer states and cities .
After some research i found this gem ruby geocoder and as it says in the documentation :
In Any Rack-Based Framework
Detect Location of HTTP Request
Get current user's city and country (using IP address). A location method is added to the standard Rack::Request which returns a Geocoder::Result object:
# Rails controller or Sinatra app
city = request.location.city
country = request.location.country_code
Basically i want to get off using country_select gem and use ruby geocoder
I have two models :
models/user.rb
class User < ApplicationRecord
extend FriendlyId
friendly_id :username, use: :slugged
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one_attached :avatar, :dependent => :destroy
# User Information
has_one :user_information, :dependent => :destroy
accepts_nested_attributes_for :user_information, :allow_destroy => true
def with_user_information
build_user_information if user_information.nil?
self
end
# Login with username or email
attr_accessor :login
validates :username, uniqueness: true, presence: true
def login
#login || self.username || self.email
end
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
elsif conditions.has_key?(:username) || conditions.has_key?(:email)
where(conditions.to_h).first
end
end
end
and a nested model :
models/user_information.rb
class UserInformation < ApplicationRecord
belongs_to :user
has_one :gender, :dependent => :destroy
accepts_nested_attributes_for :gender, :allow_destroy => true
has_one :relationship, :dependent => :destroy
accepts_nested_attributes_for :relationship, :allow_destroy => true
def age
now = Time.current
dob = self.born_in
now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
def country_name
country = ISO3166::Country[country_code]
country.translations[I18n.locale.to_s] || country.name
end
end
this my devise controller
controllers/accounts_controller.rb
class AccountsController < Devise::RegistrationsController
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
resource_updated = update_resource(resource, account_update_params)
yield resource if block_given?
if resource_updated
set_flash_message_for_update(resource, prev_unconfirmed_email)
bypass_sign_in resource, scope: resource_name if sign_in_after_change_password?
session[:return_to] ||= request.referer
redirect_to session.delete(:return_to)
else
clean_up_passwords resource
set_minimum_password_length
session[:return_to] ||= request.referer
redirect_to session.delete(:return_to), alert: resource.errors.full_messages[0]
end
end
def settings
#user = current_user
if #user
render "devise/accounts/settings"
else
render file: 'public/404', status: 404, formats: [:html]
end
end
def passwords
#user = current_user
if #user
render "devise/accounts/passwords"
else
render file: 'public/404', status: 404, formats: [:html]
end
end
def security
#user = current_user
if #user
render "devise/accounts/security"
else
render file: 'public/404', status: 404, formats: [:html]
end
end
protected
def update_resource(resource, params)
if params[:current_password].blank? && params[:password].blank? && params[:password_confirmation].blank? && params[:email].blank?
resource.update_without_password(params.except(:current_password, :password, :password_confirmation, :email))
else
resource.update_with_password(params)
end
end
end
If you want to replace country_select with geocoder, you just need to take this values in controller that you need.
class UsersController < ApplicationController
...
def create
#user = User.new(user_params)
#user.user_information.country = country_name(request.location.country_code)
#user.user_information.city = request.location.city
#user.save
end
end
If you're using Devise...
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
# put above logic here
end
def update
super
end
end
You need to implement helper/model methods needed.
Related
Don't know why this is happening here.
NoMethodError in PostsController#update
undefined method `user' for nil:NilClass
My user has admin : true and I can't update other users.posts.
I want to let all users see the content, but only registered users can create content. And if the user is admin or the creator of that content he can edit/update/destroy it as well.
post.rb
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
validates :title, presence: true, length: { minimum: 5 }
validates :body, presence: true, length: { minimum: 240 }
end
user.rb
class User < ApplicationRecord
include Encryptable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_attached_file :avatar
validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\z/
validates :level, numericality: { less_than_or_equal_to: 100, only_integer: true }, allow_blank: true
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable
before_validation :downcase_email #, :populate_iv_fields #if you need/want iv to change more often
before_create :create_encryption_key
after_create :save_encryption_key
after_create :build_user_consents
attr_encrypted :email, key: :encryption_key
has_many :user_consents, dependent: :destroy
#entry point for exporting user's personal information
def self.export_personal_information(user_id)
return nil unless User.exists?(user_id)
descendants = ApplicationRecord.descendants.reject{|model| !model.has_personal_information?}
result = Hash.new
descendants.each do |descendant|
result[descendant.name] = descendant.export_personal_information_from_model(user_id)
end
return result
end
#simplest example, we just export to json
def self.export_personal_information_from_model(user_id)
return User.find(user_id).to_json
end
#overwrite this to true for methods that you will want to be included in export_personal_information
def self.has_personal_information?
true
end
#helper method if you are creating a user from console and want them to have all consents set
def fill_consents
hash = Hash.new
ConsentCategory.all.map(&:id).each do |id|
hash[id]='on'
end
self.registration_consents=hash
end
#unfortunately not having an email field that you can just "write to" breaks
#Devise. Below some necessary workarounds
def email_changed?
encrypted_email_changed?
end
def downcase_email
self.email = self.email.downcase
end
def registration_consents=(consents)
#consents = consents
end
def registration_consents
#consents
end
validate :validate_consents_completeness
validates_presence_of :email, if: :email_required?
validates_uniqueness_of :username, allow_blank: false, if: :username_changed?
validates_length_of :username, within: 6..20, allow_blank: true
validate :validate_email_uniqueness #validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
validates_format_of :email, with: Devise.email_regexp, allow_blank: true, if: :email_changed?
validates_presence_of :password, if: :password_required?
validates_confirmation_of :password, if: :password_required?
validates_length_of :password, within: Devise.password_length, allow_blank: true
def password_required?
!persisted? || !password.nil? || !password_confirmation.nil?
end
#end original devise
def email_changed?
self.encrypted_email_changed?
end
def email_required?
true
end
def email_unique?
records = Array(self.class.find_by_email(self.email))
records.reject{|u| self.persisted? && u.id == self.id}.empty?
end
#unfortunately, this is an O(n) operation now that has to go through ALL the users to see if an email is unique. Sorry!
#if you need it to ne O(1) then consider adding email_hash field instead
def self.find_by_email(email)
users = User.all
users.each do |user|
return user if user.email.downcase == email.downcase
end
return nil
end
protected
def validate_email_uniqueness
errors.add(:email, :taken) unless email_unique?
end
def validate_consents_completeness
return if self.id #we assume that already created user has all consents
errors.add(:registration_consents, 'Sie müssen allen erforderlichen Bedingungen zustimmen um fortzufahren.') and return unless self.registration_consents
consents = ConsentCategory.where(mandatory: true).map(&:id)
ids = self.registration_consents.keys.map(&:to_i) #we are relying on a fact that checkboxes that are not checked are not sent to Rails back-end at all
consents.each do |consent_type|
errors.add(:registration_consents, 'Sie müssen allen erforderlichen Bedingungen zustimmen um fortzufahren.') and return unless ids.include?(consent_type)
end
end
def build_user_consents
ids = registration_consents.keys
categories = ConsentCategory.where(id: ids)
raise 'Die vom Benutzer eingereichte Zustimmungsliste enthält Objekte, die nicht in der Datenbank vorhanden sind!' if categories.count != ids.count
categories.each do |category|
consent = UserConsent.new
consent.consent_category = category
consent.user = self
consent.requires_revalidation = false
consent.agreed_at = self.created_at
consent.save!
end
end
end
post_policy.rb
class PostPolicy < ApplicationPolicy
attr_reader :user, :post
def initialize(user, post)
#user = user
#post = post
end
def index?
true
end
def create?
user.present?
end
def new?
user.present?
end
def update?
return true if post.user_id == user.id || user == user.admin?
end
def destroy?
return true if post.user_id == user.id || user == user.admin?
end
private
def post
record
end
end
application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
user.admin?
end
def edit?
user.admin?
end
def destroy?
user.admin?
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope.all
end
end
end
post_controller
class PostsController < ApplicationController
before_action :find_post, only: %i[destroy edit update comment_owner upvote downvote]
after_action :verify_authorized, except: [:index, :show]
layout '_app_nav'
def index
return redirect_to post_path(params[:post_id]) if params[:post_id]
return redirect_to user_path(params[:user_id]) if params[:user_id]
#post = Post.all.order('created_at DESC')
#posts = Post.all.order('created_at DESC')
#user = User.all
#posts = if params[:suche]
else
Post.all.order('created_at DESC')
end
#comments = Comment.all
end
def new
#post = Post.new
end
def create
#post = current_user.posts.build(post_params)
authorize #post
if #post.save!
redirect_to #post
else
render 'new'
end
end
def show
#post = Post.find(params[:id])
#user = #post.user
#comments = Comment.where(post_id: #post).order('created_at DESC').paginate(:page => params[:page], :per_page => 5)
end
def edit
authorize #post
#post = Post.find(params[:id])
end
def update
#user = User.find(params[:id])
#post = Post.find(params[:id])
authorize #post
#post.update(title: params[:title], body: params[:post_params])
redirect_to post_path(#post)
end
def destroy
#post = Post.find(params[:id])
#post.destroy
authorize #post
redirect_to posts_path
end
=begin
def upvote
#post.upvote_from current_user
redirect_to post_path(#post)
end
def downvote
#post.downvote_from current_user
redirect_to post_path(#post)
end
=end
private
def post_params
params.require(:post).permit(:title, :body, :user_id)
end
def find_post
#post = Post.find(params[:id])
end
end
application_controller
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery with: :exception
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit(:username, :email, :password, :password_confirmation, registration_consents: {})
end
end
private
def user_not_authorized
flash[:alert] = 'test'
redirect_to(root_path)
end
end
registrations_controller:
class RegistrationsController < Devise::RegistrationsController
private
def account_update_params
params.require(:user).permit(:email, :username, :avatar, :current_password, :password, :password_confirmation)
end
end
edit:
updating my post_policy.rb with #post such as return true if user.present? && user == #post.user || user == user.admin? resolves in -> Pundit::NotAuthorizedError in PostsController#update
not allowed to update?
In your PostsController, you need to include update method inside array where you specify before which methods should authenticate_user before action run:
before_action :authenticate_user!, only: [:create, :destroy, :new, :edit, :update]
If you're using devise you can use this callback, in your application_controller.rb to validate you have a user logged in.
before_action :authenticate_user!
With that you avoid doing
user.present?
Your post_policy.rb should look like this
class PostPolicy < ApplicationPolicy
attr_reader :user, :post
def initialize(user, post)
#user = user
#post = post
end
def index?
true
end
def create?
user.present?
end
def new?
user.present?
end
def update?
return true if record.user_id == user.id || user == user.admin?
end
def destroy?
return true if record.user_id == user.id || user == user.admin?
end
private
def post
record
end
end
Also, to avoid that users can enter the links in the browser, you can do an extra step on you controller, which is adding the following callback and method
class PostsController < ApplicationControl
before_action :authorization
private
def authorization
authorize(Post)
end
end
EDIT:
Make sure your ApplicationController looks like this one to prevent the error Pundit::NotAuthorizedError.
class ApplicationController < ActionController::Base
include Pundit
before_action :authenticate_user!
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = 'You are not authorized to perform this action.'
redirect_to(root_path)
end
end
I'm trying to allow users to create projects...and as soon as a user creates a project...they will automatically be following that project. (I have my app setup to allow a user to follow a project from a 'follow' button on the project profile). I would like the project creator to automatically be following the new project without having to click the 'follow' button. I rearranged my code as per Bilal's answer...but now clicking 'create project' simply refreshes the 'new' view (no project gets posted). I assumed this has to do with the Pundit authorizations but perhaps someone can clarify why the 'create' action is no longer working...
My Projects Model:
class Project < ActiveRecord::Base
belongs_to :owner, :foreign_key=>'user_id', :class_name=>'User'
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
validates :title, presence: true
validates :background, presence: true
validates :projectimage, presence: true
mount_uploader :projectimage, ProjectimageUploader
attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
after_update :crop_projectimage
def crop_projectimage
projectimage.recreate_versions! if crop_x.present?
end
def private?
self.is_private == true
end
def public?
self.is_private == false
end
end
Relationships Model:
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "Project"
validates :follower_id, presence: true
validates :followed_id, presence: true
enum role: [:admin, :collaborator, :visitor]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :visitor
end
end
My Projects Controller:
class ProjectsController < ApplicationController
before_filter :authenticate_user!, only: [:create, :new, :edit, :update, :delete, :followers]
# CREATES REDIRECT & ALERT MESSAGE WHEN PUNDIT SEES SOMEONE IS NOT AUTHORIZED (via :not_authorized_in_project below)
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def new
#project = Project.new
end
def show
#project = Project.find(params[:id])
authorize #project, :visit?
# #user = User.where(:id => #project.user_id).first
rescue Pundit::NotAuthorizedError
flash[:warning] = "You are not authorized to access this page."
redirect_to project_path || root_path
end
def index
#projects = policy_scope(Project).all
end
def create
#project = current_user.own_projects.build(project_params)
#project.followers << current_user
if #project.save
if params[:project][:projectimage].present?
render :crop
else
flash[:success] = "You've successfully created a Project..."
redirect_to #project
end
else
render 'new'
end
end
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
if params[:project][:projectimage].present?
render :crop
else
flash[:success] = "Project Created"
redirect_to #project
end
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "Project destroyed"
redirect_to users_path
end
def followers
#title = "Following this Project"
#project = Project.find(params[:id])
#project = #project.followers.paginate(page: params[:page])
render 'show_follow_project'
end
private
def project_params
params.require(:project).permit(:title, :background, :is_private, :projectimage, :user_id, :crop_x, :crop_y, :crop_w, :crop_h)
end
def user_not_authorized
flash[:warning] = "You are not authorized to access this page."
redirect_to project_path(#project) || root_path
end
end
My User Model:
class User < ActiveRecord::Base
has_many :own_projects, :class_name=>'Project'
has_many :projects
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_projects, through: :relationships, source: :followed
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def following?(some_project)
relationships.find_by_followed_id(some_project.id)
end
def follow!(some_project)
self.relationships.create!(followed_id: some_project.id)
end
def unfollow!(some_project)
relationships.find_by_followed_id(some_project.id).destroy
end
Pundit Project Policy:
class ProjectPolicy < Struct.new(:user, :project)
class Scope < Struct.new(:user, :scope)
# SCOPE & RESOLVE METHOD USED TO RESTRICT PROJECTS INDEX TO PUBLIC & THOSE YOU'RE AN ADMIN/COLLAB ON
def resolve
followed_project_ids = user.followed_projects.map(&:id)
public_project_ids = Project.where(:is_private=>false).map(&:id)
Project.where(:id=>followed_project_ids + public_project_ids)
end
end
def update?
user.project_admin? || user.project_collaborator?
end
# METHOD USED IN PROJECTS_CONTROLLER (SHOW) TO RESTRICT VISITING PRIVATE PROJECT PROFILES TO ADMINS & COLLABS
def visit?
user.project_admin?(project) || user.project_collaborator?(project)
end
end
It's never a good idea to use current_user in a model, see this for reference.
Any easy and efficient place to set this thing would be the controller itself. So, you can write the following code:
def create
#project = current_user.own_projects.build(project_params)
#project.followers << current_user
if #project.save
if params[:project][:projectimage].present?
render :crop
else
flash[:success] = "You've successfully created a Project..."
redirect_to #project
end
else
render 'new'
end
end
On Rails 4, I am using Devise login. On first sign_in user gets directed to new_profile_path(#user) or profile_path(#profile = #user.profile_id). I am building the Profile ActiveRecord during User Registration. I'd like to pass the profile.id that is created during registration back to the User ActiveRecord before_save.
I am able to test with success "after_create", the build is working fine. Then "before_save" I'm able to get it to work by hard-coding a profile_id (see working code below). However, I cannot figure out after an entire day on the proper method to pass profile_id. I obviously do not want a hard-coded value. Here is my model code.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_one :profile, :dependent => :destroy, autosave: true
accepts_nested_attributes_for :profile
after_create :build_profile
before_save :set_profile_id
def build_profile
Profile.create(user: self) # Associations must be defined correctly for this syntax, avoids using ID's directly.
end
def set_profile_id
self.profile_id = 30 # Hard-coding tested and works. Profile built with user_id = user.id and user.profile_id = 30
end
end
class Profile < ActiveRecord::Base
belongs_to :user
#validates :username, presence: true, length: {maximum: 255}, uniqueness: { case_sensitive: false }, format: { with: /\A[a-zA-Z0-9]*\z/, message: "deve contar apenas letras e números" }
#validates :firstname, presence: true, length: {maximum: 255}
#validates :lastname, presence: true, length: {maximum: 255}
#validates :gender, presence: true, inclusion: %w(m f)
#validates :birthday, presence: true
has_many :offers, dependent: :delete_all
before_save :set_address
geocoded_by :address # can also be an IP address
def address
address = "#{street_address}, #{city}, #{state}, #{zipcode}"
end
after_validation :geocode # auto-fetch coordinates
reverse_geocoded_by :latitude, :longitude
after_validation :reverse_geocode # auto-fetch address
has_attached_file :logo_image, :styles => { :medium => "250x250>", :small => "175x175>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
# Validate content type
validates_attachment_content_type :logo_image, :content_type => /\Aimage/
# Validate filename
validates_attachment_file_name :logo_image, :matches => [/png\Z/, /jpe?g\Z/, /JPG?\Z/]
validates_attachment_size :logo_image, :less_than => 2.megabytes
#validates_attachment_presence :avatar
# Explicitly do not validate
#do_not_validate_attachment_file_type :avatar
# This may not be necessary anymore. With 'def address' above. Keeping for now as reference
def set_address
self.address = "#{street_address}, #{city}, #{state}, #{zipcode}"
end
end
class RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
build_resource({})
#validatable = devise_mapping.validatable?
if #validatable
#minimum_password_length = resource_class.password_length.min
end
respond_with self.resource
end
# POST /resource
def create
build_resource(sign_up_params)
resource_saved = resource.save
yield resource if block_given?
if resource_saved
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
#validatable = devise_mapping.validatable?
if #validatable
#minimum_password_length = resource_class.password_length.min
end
respond_with resource
end
end
private
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, :user_id, :profile_id)
end
def account_update_params
params.require(:user).permit(:email, :password, :password_confirmation, :current_password, :user_id, :profile_id)
end
end
class ProfilesController < ApplicationController
before_action :set_profile, only: [:show, :edit, :update, :destroy]
def index
#profiles = Profile.all
#hash = Gmaps4rails.build_markers(#profiles) do |profile, marker|
marker.lat profile.latitude
marker.lng profile.longitude
marker.infowindow profile.address
end
end
def address
#profile = Profile.find(params[:address])
Geocode.search("#profile")
end
def show
#profiles = Profile.all
#offers = Offer.where(user_id: current_user.id)
#hash = Gmaps4rails.build_markers(#profiles) do |profile, marker|
marker.lat profile.latitude
marker.lng profile.longitude
marker.infowindow profile.address
end
end
def new
#user = current_user # added to solve for session re-direct on sign-in
#profile = Profile.new
end
def create
#profile = Profile.new(profile_params)
##user.profile_id = #profile.id # added to solve for session re-direct on sign-in
if #profile.save
flash[:notice] = "Profile has been created."
redirect_to #profile
else
flash[:alert] = "Profile has not been created."
render "new"
end
end
def edit
#hash = Gmaps4rails.build_markers(#profiles) do |profile, marker|
marker.lat profile.latitude
marker.lng profile.longitude
marker.infowindow profile.address
end
end
def update
if #profile.update(profile_params)
flash[:notice] = "Profile has been updated."
redirect_to #profile
else
flash[:alert] = "Profile has not been updated."
render "edit"
end
end
def destroy
#profile.destroy
flash[:notice] = "Profile has been destroyed."
redirect_to profiles_path
end
private
def profile_params
params.require(:profile).permit(:id, :firstname, :lastname, :company, :website, :street_address, :city, :state, :zipcode, :phone, :logo_image, :address, :latitude, :longitude, :user_id, :user_email_id)
end
def set_profile
#profile = Profile.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The profile you were looking for could not be found."
redirect_to profiles_path
end
end
I have a Ticket model, an Employee model, and a User model.
Users and Employees can create tickets, but employees also have tickets assigned to them. So user_id refers to the creator of the ticket, and employee_id refers to the assigned employee (I am not sure if this the best way or not).
Ticket.rb
class Ticket < ActiveRecord::Base
before_save :default_values
after_commit :close_solved
after_commit :close_canceled
before_create :assign_state
attr_accessible :description, :title, :employee_department_id, :user_id, :first_name, :last_name , :email, :state_id, :employee_id, :ticket_state, :assign_state
belongs_to :employee_department
belongs_to :user
belongs_to :state
belongs_to :employee
has_many :replies
def default_values
self.state_id = 3 if self.state_id.nil?
end
def to_label
ticket_state.to_s
end
def close_solved
if self.ticket_state == "solved"
self.update_column(:ticket_state, "closed (solved)")
self.save!
end
end
def close_canceled
if self.ticket_state == "canceled"
self.update_column(:ticket_state, "closed (canceled)")
self.save!
end
end
def assign_state
if self.employee_id.nil?
self.assign_state = "un-assigned"
else
self.assign_state = "assigned"
end
end
Ticket.all.each do |ticket|
if ticket.ticket_state.blank?
ticket.ticket_state = 'open'
end
ticket.save
end
end
Employee.rb
class Employee < ActiveRecord::Base
# attr_accessible :title, :body
after_create :add_to_users
attr_accessible :employee_number, :joining_date, :first_name, :middle_name, :last_name,
:gender, :job_title, :employee_department_id, :qualification, :experience_detail,
:experience_year, :experience_month, :status_description, :date_of_birth, :marital_status,
:children_count, :father_name, :mother_name, :husband_name, :blood_group, :nationality_id,
:home_address_line1, :home_address_line2, :home_city, :home_state, :home_pin_code,
:office_address_line1, :office_address_line2, :office_city, :office_state, :office_pin_code,
:office_phone1, :office_phone2, :mobile_phone, :home_phone, :email, :fax, :user_id, :school_id,
:employee_category_id, :employee_position_id, :reporting_manager_id, :employee_grade_id,
:office_country_id, :home_country_id
belongs_to :employee_department
belongs_to :employee_category
belongs_to :employee_position
belongs_to :employee_grade
belongs_to :nationality, class_name: 'Country'
belongs_to :reporting_manager, class_name: "Employee"
belongs_to :school
belongs_to :user
has_many :tickets
def add_to_users
new_user = User.new
new_user.user_name = self.first_name
new_user.first_name = self.first_name
new_user.last_name = self.last_name
new_user.email = self.email
new_user.password = "123456"
new_user.password_confirmation = "123456"
new_user.user_type_id = 2
new_user.save
t = Employee.find(self.id)
t.user_id = new_user.id
t.save
end
def to_label
full_name = first_name + " " + last_name
end
def full_name
full_name = first_name + " " + last_name
end
end
User.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :validatable,:confirmable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable,
:trackable, :lockable, :timeoutable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :user_name, :first_name, :last_name, :password, :password_confirmation, :remember_me,
:role_ids, :current_password, :user_type
attr_accessor :current_password
# attr_accessible :title, :body
has_many :assignments
has_many :roles, :through => :assignments
has_many :articles
has_many :comments
has_many :students
has_many :guardians
has_many :employees
has_many :tickets
has_many :permissions
accepts_nested_attributes_for :tickets
def has_role?(role_sym)
roles.any? { |r| r.role_name.underscore.to_sym == role_sym }
end
end
Ability.rb
class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new
if user.has_role? :administrator
can :manage, :all
end
if user.has_role? :admission_manager
can :manage, Student
end
if user.has_role? :news_manager
can :manage, Article
end
if user.has_role? :ticket_manager
can :manage, Ticket
end
if user.has_role? :student_viewer
can :read, Student
end
if user.has_role? :news_viewer
can :read, Article
end
if user.has_role? :ticket_viewer #he should be able to create tickets and see what he has created.
can :create, Ticket
can :read, Ticket
end
end
end
Ticket_controller.rb
class TicketsController < ApplicationController
load_and_authorize_resource
def index
#tickets = Ticket.all
#tickets_grid = initialize_grid(Ticket, :include => [{:user => :user_type}, :employee_department, :state])
end
def show
#ticket = Ticket.find(params[:id])
#reply = #ticket.replies.build # this for comments on ticket
#state = State.all # this for a model called State which describe the priority of the ticket (Emergency / High / Normal )
end
def new
#ticket = Ticket.new
end
def create
#ticket = Ticket.new(params[:ticket])
if #ticket.save
flash[:notice] = 'Support ticket request created.'
redirect_to #ticket
else
flash[:error] = 'An error occurred please try again!'
redirect_to '/dashboard'
end
end
def edit
#ticket = Ticket.find(params[:id])
end
def update
#ticket = Ticket.find(params[:id])
if #ticket.update_attributes(params[:ticket])
flash[:notice] = 'Successfuly updated.'
redirect_to tickets_path
else
flash[:error] = 'An error occurred please try again!'
render #ticket
end
end
end
I need to allow Employees to be able to manage their assigned tickets, and I need the creator of the ticket to see only the tickets he created.
How can I do this using CanCan? I'm open to other suggestions, if it cannot be done with CanCan.
For users to be able to read the tickets they've created, you just need to add a condition on the ability (see below). You can use the same condition on the :create ability and cancan will pre-fill those attributes for you when it builds a new object for the #new or #create actions.
# app/models/ticket.rb
class Ticket < ActiveRecord::Base
# <snip>
belongs_to :user
belongs_to :employee
# <snip>
end
# app/models/user.rb
class User < ActiveRecord::Base
has_one :employee
end
# app/models/ability.rb
class Ability
# <snip>
if user.has_role? :ticket_viewer
can :create, Ticket
can :read, Ticket, :user_id => user.id
end
if user.employee # && any other necessary conditions
can :create, Ticket
can :read, Ticket, :employee_id => user.employee.id
end
end
# app/controllers/tickets_controller.rb
controller TicketsController < ApplicationController
load_and_authorize_resource
def index
# #tickets = Ticket.accessible_by(current_ability) # cancan's
# load_and_authorize resource will take care of loading ticket(s) for
# all controller actions, so I've commented them out
#tickets_grid = initialize_grid(#tickets, :include => [{:user => :user_type}, :employee_department, :state])
end
def show
# #ticket = Ticket.find(params[:id])
#reply = #ticket.replies.build # this for comments on ticket
#state = State.all # this for a model called State which describe the priority of the ticket (Emergency / High / Normal )
end
def new
# #ticket = Ticket.new
end
def create
# #ticket = Ticket.new(params[:ticket])
if #ticket.save
flash[:notice] = 'Support ticket request created.'
redirect_to #ticket
else
flash[:error] = 'An error occurred please try again!'
redirect_to '/dashboard'
end
end
def edit
# #ticket = Ticket.find(params[:id])
end
def update
# #ticket = Ticket.find(params[:id])
if #ticket.update_attributes(params[:ticket])
flash[:notice] = 'Successfuly updated.'
redirect_to tickets_path
else
flash[:error] = 'An error occurred please try again!'
render #ticket
end
end
end
This is fairly simple to achieve using CanCan. Here's a quick example using a modified subsection of the ability file you included:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
# Full access if you're the assigned employee
if user.has_role? :support_manager
can :manage, Ticket do |ticket|
ticket.try(employee) == user
end
end
# Read access only if you created the ticket
can :read, Ticket do |ticket|
ticket.try(user) == user
end
end
end
you will see all the tickets because in index action inside your controller you call:
#tickets = Ticket.all
you can try this:
#tickets = Ticket.accessible_by(current_ability)
by using this method current_user will see all tickets that current_user has access to
Update
you can define specific access in the accessible_method
#tickets = Ticket.accessible_by(current_ability, :manage)
the rest is how you define the access
https://github.com/ryanb/cancan/wiki/Fetching-Records
Example on define the access:
if user.has_role? :ticket_manager
can :manage, Ticket, employee: {:user_id => user.id}
end
I am trying to setup a site wide search that searches for users, as well as items belonging to users.
Basically each user has many "cereals", and I want to be able to keep the cereal hidden from the search results, and show the owner of the cereal in its place.
Right now I have sunspot functional, but on my index page in users, it lists all the users before a search is performed. If now search is performed I want the results to be empty.
I am still a noob to rails, and am trying to figure this out. Any help would be greatly appreciated. Here is my source.
App/models/user.rb
class User < ActiveRecord::Base
include Amistad::FriendModel
mount_uploader :avatar, AvatarUploader
has_many :cereals, dependent: :destroy
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:login]
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :login, :avatar, :avatar_cache, :remove_avatar, :firstname, :lastname, :phone, :urlname
# attr_accessible :title, :body
attr_accessor :login
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
where(conditions).first
end
end
def update_with_password(params={})
current_password = params.delete(:current_password)
if params[:password].blank?
params.delete(:password)
params.delete(:password_confirmation) if params[:password_confirmation].blank?
end
result = if params[:password].blank? || valid_password?(current_password)
update_attributes(params)
else
self.attributes = params
self.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
end
clean_up_passwords
result
end
searchable do
text :firstname, :lastname, :username
end
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate_user!
def index
#search = User.search do
fulltext params[:search]
end
#users = #search.results
end
def show
if params[:id].nil? && current_user
#user = current_user
else
#user = User.find(params[:id])
end
#cereal = current_user.cereals.build if signed_in?
#cereals = #user.cereals.paginate(page: params[:page])
end
def first_time
if params[:id].nil? && current_user
#user = current_user
else
#user = User.find(params[:id])
end
end
def edit
if params[:id].nil? && current_user
#user = current_user
else
#user = User.find(params[:id])
end
end
def profile
#username = params[:id]
#friends = current_user.friends || []
#title = "User Profile for #{#username}"
#user = User.find_by_username(#username)
#users = User.all :conditions => ["id != ?", current_user.id]
end
end
views/users/index.html.haml
= form_tag #users_path, :method => :get do
%p
= text_field_tag :search, params[:search]
= submit_tag "Search", name: 'nil'
- #users.each do |user|
%p
= user.username