How to let Users edit a resource they create but not others when resource doesn't belong to User? - ruby

In my application using CanCan I have permissions where users can view and create stores but I also want them to only be able to edit the ones they've created. Users can create as many stores as they like, which all should be editable by them. A store doesn't have users so how could I do this when theirs no user_id apart of the Store table?
CanCan:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role == "default"
can :read, Store
can :create, Store
end
end
end

Since a user will be able to create as many stores as they like, a store will belong to a user.
You MUST create this relationship.
So, in the User model.
class User < ActiveRecord::Base
has_many :stores
end
And in the Store model.
class Store < ActiveRecord::Base
belongs_to :user
end
And in the ability.rb file, just put something like:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role == 'default'
can :manage, Store , :user_id => user.id
end
end
end

I would add the following to the store model:
has_one :created_by, :class => User
Then add a migration to add a created_by_id to your Store class.
You should then be able to add a CanCan::Ability:
can :edit, Store, :created_by => user

I agree with a previous poster, that you must set up a relationship between User and Store. The relationship can be one-to-many (as Kleber S. showed), or many-to-many, if a store can have multiple users.
Then, the best way to handle access control is in the controller, by using the association. For the show, edit, update, destroy methods, you'll need to find the store, given a logged in user, so do something like this:
class StoresController < ApplicationController
before_filter :find_store, only: [:show, :edit, :update, :destroy]
def show
end
def edit
end
def update
if #store.update_attributes(params[:store])
# redirect to show
else
# re-render edit, now with errors
end
end
# ...
private
def find_store
#store = current_user.stores.find(params[:id])
end
end
This way, the association takes care of limiting the lookup to only those stores that are connected to the current_user by foreign key. This is the standard way for RESTful Rails resources to perform access control of associated resources.

Related

Factory Girl building and creating 3 related models, association is lost after build stage

I have the following models
class Company
has_many :admins, class_name: 'Profile::CompanyAdmin'
validates :must_have_at_least_one_admin_validation
def must_have_at_least_one_admin_validation
errors.add(:admins, :not_enough) if admins.size.zero?
end
end
class Profile::CompanyAdmin
belongs_to :company
belongs_to :user, inverse_of: :company_admin_profiles
end
class User
has_many :company_admin_profiles, inverse_of: :user
end
I am trying to set up factories so I can easily build coherent data. Especially, I want to be able to create(:company, *traits) and it creates an Admin profile with a user account
factory :company do
transient do
# need one admin to pass validation
admins_count 1 # Admins with a user account
invited_admins_count 0 # Admins without user account
end
after(:build) do |comp, evaluator|
# Creating a company admin with a user
comp.admins += build_list(:company_admin_user,
evaluator.admins_count,
company: comp
).map { |u| u.company_admin_profiles.first }
comp.admins += build_list(:company_admin_profile,
evaluator.invited_admins_count,
company: comp
)
comp.entities = build_list(:entity,
evaluator.entity_count,
company: comp
)
# If I debug here, I have
# comp.admins.first.user # => Exists !
end
after(:create) do |comp, evaluator|
# If I debug here
# comp.admins.first.user # => Gone 😱
# First save users of admin profiles (we need the user ID for the admin profile user foreign key)
comp.admins.map(&:user).each(&:save!)
# Then save admins themselves
comp.admins.each(&:save!)
end
In the example above, when I debug at the end of the company after_build stage, I have successfully built admin profiles with thier users, however after the beginning of the after_create stage, I have lost the associated user in the admin profiles (cf comments)
What's wrong ?
For the reference here are the other factories for Profile/User
factory(:company_admin_user) do
transient do
company { build(:company, admins_count: 0) }
end
after(:build) do |user, evaluator|
user.company_admin_profiles << build(:company_admin_profile,
company: evaluator.company,
user: user,
)
end
after(:create) do |user, evaluator|
user.rh_profiles.each(&:save!)
end
end
factory :company_admin_profile, class: Profile::CompanyAdmin do
company
user nil # By default creating a CompanyAdmin profile does not create associated user
end
EDIT :
A simpler way to see the problem
company = FactoryGirl.build(:company)
company.admins.first.user # => Exists !
company.save # => true
company.admins.first.user # => Nil !
It would seem saving the company model first loses the 2-level deep nested user association. SO instead of
after(:create) do |comp, evaluator|
# First save the users
comp.admins.map(&:user).compact.each(&:save!)
# Then save admins themselves
comp.admins.each(&:save!)
The following does work (still not quite sure why though)
before(:create) do |comp, evaluator|
# Need to save newly created 2-level deep nested users first
comp.admins.map(&:user).compact.each(&:save!)
end
after(:create) do |comp, evaluator|
# Then save admins themselves
comp.admins.each(&:save!)
end

Many to many and one to many association between same models

I am creating a simple Sinatra app, using Sequel for my ORM.
Most of the data revolves around users and events where:
An event can have many users, one of which is the "owner".
Users can have many events, one or many of which they "own".
Here is a simplified version of my schema/model definitions:
class User < Sequel::Model(:users)
many_to_many :events
one_to_one :event
end
class Event < Sequel::Model(:events)
many_to_many :users
many_to_one :user
end
# provides a link between users and events
# e.g. event.users or user.events
# I am unsure how necessary this is :)
db.create_table :events_users do
primay_key :id
foreign_key :event_id, :events
foreign_key :user_id, :users
end
This allows me to get the users attached to an event, or the events that a user is attached to, but I am struggling to express the "ownership" of an event. It seems like the following pseudocode would work:
my_user = User.all.first
owned_events = Event.where(user_id = my_user.user_id)
That leads to two questions:
Does the current way i'm using assocations make sense?
How do I express ownership of an event in terms of Sequel's association model?
Maybe something like this:
class Event
many_to_one :owner, :class=>:User
many_to_many :users
end
class User
one_to_many :owned_events, :class=>:Event, :key=>:owner_id
many_to_many :events
end
You'll need to add owned_id field in events table.
Usage:
user = User.all.first
event = Event.new(:title => 'New Event')
events.add_owner(user)
event.save
another_user = User.create(:name => 'Jack')
event.add_user(another_user)

Hide Products from list based on authorization using cancan in rails_admin

I have a Product model.
Ability class is as follows:
class Ability
include CanCan::Ability
def initialize(user)
if user.has_role? :super_admin
can :manage, :all
else
can :read, Product, Product.all.limit(10) if user.has_role? :brand_manager
can :access, :rails_admin # grant access to rails_admin
can :dashboard # grant access to the dashboard
end
end
end
Here is the code for rails_admin:
RailsAdmin.config do |config|
config.authorize_with :cancan
end
I want to hide all the products from list which don't have particular name? Some how rails_admin does not support it. Can anyone help me to resolve this issue?
You can do something like this.
1. Create a new field in model like admin_user_id
2. While creating/updating product from save current admin user details in product data
and use it ability class
class Ability
include CanCan::Ability
def initialize(user)
if user.has_role? :super_admin
can :manage, :all
elsif user.has_role? :brand_manager
can :manage,Product, :admin_user_id=> user.id
can :access, :rails_admin # grant access to rails_admin
can :dashboard # grant access to the dashboard
end
end
end
What do you mean with hide all the producs from the list ?
If you want that users can't read it, you can try:
can :read, Product do |product|
[name1, name2].include?( product.name )
end
If you want don't return these products, you can use in your controller:
Product.where( :name.in => [name1, name2] )
I hope I've helped

Blocking Users From Specific Pages using Ruby on Rails and cancan

I am learning Ruby on Rails and was looking into utilizing cancan to help restrict users access to actions that they shouldn't have and to pages depending on who they are. I currently understand how to restrict actions, but I was curious if someone could help with actually restricting certain pages and unique pages.
One example is I have a home page for admin users and one for regular users, how would I restrict the admin page from the normal user?
Thanks, and any pointers on if I am doing something wrong is greatly appreciated.
If you want to use cancan :
Admit you add in your user controller a method admin_home :
def admin_home
#user = current_user
authorize! :admin_home
end
You need to specify in ability.rb file you want to restrict access to admin_home for standard users :
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
#Authorize all actions
can :manage, User
else
#authorize only self modifications and restrict access to admin_home
can :manage, User, :id => user.id
cannot :admin_home, User
end
end
end
You can find great resources about cancan in official wiki like
https://github.com/ryanb/cancan/wiki/Defining-Abilities and
https://github.com/ryanb/cancan/wiki/Authorizing-controller-actions
Hope this help
Note: I am just giving you an example, you are not supposed to use it as it is, but you can have an Idea that how you will be able to put your logic.
class AdminsController < ApplicationController
before_filter :check_admin, :only => [:index, :show]
def index
#admins = //whatever your query for this action
end
def show
#admin = //whatever your query for this action
end
protected
def check_admin
if(my_condition to check if user type is admin)
{
return true // or anything u want for ur admin user
}
else
{
//anything here when user is not admin
1. you can redirect to users home page using redirect_to
2. you can redirect to a specific page which shows "You are not authorized to see this web page"
}
end
end
end

Rails Active Record: Calling Build Method Should Not Save To Database Before Calling Save Method

I have a simple user model
class User < ActiveRecord::Base
has_one :user_profile
end
And a simple user_profile model
class UserProfile < ActiveRecord::Base
belongs_to :user
end
The issue is when I call the following build method, without calling the save method, I end up with a new record in the database (if it passes validation)
class UserProfilesController < ApplicationController
def create
#current_user = login_from_session
#user_profile = current_user.build_user_profile(params[:user_profile])
##user_profile.save (even with this line commented out, it still save a new db record)
redirect_to new_user_profile_path
end
Anyyyyyy one have anyyy idea what's going on.
The definition of this method says the following but it's still saving for me
build_association(attributes = {})
Returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key, but has not yet been saved.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one
Ok, I'm sure experienced vets already know this, but as a rookie I had to figure it out the long way...let me see if I can explain this without screwing it up
Although I was not directly saving the user_profile object, I noticed in my logs that something was updating the user model's last_activity_time (and the user_profile model) each time I submitted the form (the user model's last_activity date was also updated when the logged in user did various other things too - I later realized this was set in the Sorcery gem configuration).
Per http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
AutosaveAssociation is a module that takes care of automatically saving associated records when their parent is saved. In my case, the user mode is the parent and the scenario they provide below mirrors my experience.
class Post
has_one :author, :autosave => true
end
post = Post.find(1)
post.title # => "The current global position of migrating ducks"
post.author.name # => "alloy"
post.title = "On the migration of ducks"
post.author.name = "Eloy Duran"
post.save
post.reload
post.title # => "On the migration of ducks"
post.author.name # => "Eloy Duran"
The following resolutions resolved my problem
1. Stopping Sorcery (config setting) from updating the users last_activity_time (for every action)
or
2. Passing an ':autosave => false' option when I set the association in the user model as follows
class User < ActiveRecord::Base
has_one :user_profile, :autosave => false
end

Resources