Nested polymorphic relationships CanCan - ruby-on-rails

I have a polymorphic relationship between Responses and Posts, Calls & Meetings:
class Response < ActiveRecord::Base
belongs_to :responseable, polymorphic: true
...
end
class Post < ActiveRecord::Base
has_many :responses, as: :responseable, dependent: :destroy
...
end
class Call < ActiveRecord::Base
has_many :responses, as: :responseable, dependent: :destroy
...
end
class Meeting < ActiveRecord::Base
has_many :responses, as: :responseable, dependent: :destroy
...
end
I am using CanCan to define my abilities use the Nested Resource feature to authourize the viewing of responses dependent on their parents ability:
class ResponsesController < ApplicationController
before_filter :authorize_parent
load_resource :post
load_resource :call
load_resource :meeting
load_and_authorize_resource :response, :through => [:post, :call, :meeting]
...
private
def authorize_parent
authorize! :read, (#post || #call || #meeting)
end
end
All the abilities are working as they should using the standard actions in the controller.
However, I have an action in my Responses controller that is used to poll for new responses by a JS script every 15 seconds:
def polling
current_user_id = params[:current_user_id]
responseable_type = params[:responseable_type]
klass = [Post, Call, Meeting].detect { |c| responseable_type == c.name }
#responseable = klass.find(params[:responseable_id])
undivided_millisecond_epoch_time_in_integer = params[:after]
undivided_millisecond_epoch_time_in_decimal = (undivided_millisecond_epoch_time_in_integer).to_d
divided_millisecond_epoch_time_in_decimal = (undivided_millisecond_epoch_time_in_decimal / 1000000).to_d
#responses = #responseable.responses.where("created_at > ? AND user_id <> ?", Time.at(divided_millisecond_epoch_time_in_decimal), current_user_id)
end
No matter what I try I can not get this to work. I.e. I just get the response "You are not authorized to access this page". I presume that I need to add something to get this custom action working, but I'm a bit lost as to where and what.
Any help greatly appreciated.
EDIT: Added the details in my abilities file:
if user.role == "client"
can :index, Post, :user_expert_private => false
can :index, Post, :user_expert_private => true, :user_id => user.id
can :show, Post, :user_expert_private => false, :countries => { :id => user.country_ids}
can :show, Post, :user_expert_private => true, :user_id => user.id
can :create, Post
can :edit, Post, :user_id => user.id
can :update, Post, :user_id => user.id
can :read, Response
can :create, Response
can :polling, Response
can :read, Call, :user_id => user.id
can :create, Call, :user_id => user.id
can :edit, Call, :user_id => user.id
can :update, Call, :user_id => user.id
can :read, Meeting, :user_id => user.id
can :create, Meeting, :user_id => user.id
can :edit, Meeting, :user_id => user.id
can :update, Meeting, :user_id => user.id
can :responses, :polling
can :posts, :autocomplete
end

According to this, at the end of your polling method, you should be able to place this at the end of your polling method:
authorize! :read, #responses
I believe the reason is that you have authorized use of the Response object only under the auspices of the parent objects.
edit based on abilities:
perhaps something like
can :polling, Response, :user_id => user.id
I am doing some work in another project, and this page is probably going to be of use. I will update this answer if/when it proves to be correct.

Related

Complex conditions in CanCanCan can method

If I have user,client and request models as follows:
#user.rb
#client.rb
has_one :user
has_many :requests
#request.rb
belongs_to :client
I use user model for CanCanCan authentication.
Inside ability class i want to specify ability for client. I want to user to allow read,update only for requests that belong to him.
Her is what i try:
def client
can [:read,:update], [Request], ['client_id = ?', user.client_id] do |client|
......something here
end
end
can [:read, :update], Request, :client_id => user.id
here is the simplest option:
can [:read, :update], Request, :client_id => user.id
if you have more complex abilities than this then you can do:
can [:read, :update], Request do |request|
request.client_id == user.id
end

rails cancan access is denied

I have combined custom authentication with CanCan and to my frustration it is not exactly working as expected. So, here is the snippet of code in my controller
class Profile < ActiveRecord::Base
belongs_to :user, :inverse_of => :profile
delegate :email, to: :user
validates_presence_of :user_id
end
class Api::V1::ProfileController < Api::V1::UserActivityController
load_and_authorize_resource :user
load_and_authorize_resource :profile, :through => :user, :class => "Profile"
def index
#profile = subject_user.profile
authorize! :index, #profile
end
end
Here is the Ability class
class Ability
include CanCan::Ability
def initialize(user)
alias_action :index, :show, :to => :read
can :read, Profile
end
end
If I try can :read, :all it works fine but with can :read, Profile it fails. I had the initial hypothesis that maybe the delagate in the Profile class causes the denial but that is not the case. any thoughts?
The page is supposed to render into JSON using rabl
object #profile
attributes :first_name, :last_name, :gender
-- Update --
I figured that namespacing and nesting causes the issues with my code. My controlled is in fact namespaced (I updated the controller code) and I am trying to access Profile throught he following routing:
namespace :user
namespace :profile
end
I tried the following code for load_and_authorize_resource in ProfileController (also updated the controller code above) but it is not working. Any thoughts on how it should be modified? Does anything need to be changed in the Ability class?
load_and_authorize_resource :user
load_and_authorize_resource :profile, :through => :user, :class => "Profile"

Deleting a 'friend' that added you using self-referential association

I can implement reverse relationships, so if UserA adds UserB, then it shows UserA in B's profile, and visa versa.
But I cannot figure out how to let UserB remove UserA as a friend, if UserA added UserB.
I've tried so many different ways, but everytime I change something it moves the problem elsewhere! I can't tell if the fundamental issue is:
a. how the FriendshipsController destroy method is defined
b. whether I need another controller specifically just to handle
InverseFriendships destroy
c. if I need to customize the routes
d. if all the above are ok, but the code I have in my views (specifically
the _suggested_connections partial) is calling the wrong controller
and/or route
e. or none of the above.
Code snippets below:
class FriendshipsController < ApplicationController
def destroy
#friendship = current_user.friendships.find(params[:id])
#friendship.destroy
flash[:notice] = "Removed friendship."
redirect_to current_user
end
In the view
<% #user.inverse_friends.each do |inverse_friendship| %>
<li>
<%= inverse_friendship.name %>
<%= link_to "remove", #user.inverse_friendships, :method => :delete, :class => "btn-small btn-danger" %><br />
<%= image_tag inverse_friendship.avatar(:thumb) %>
My models:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User'
attr_accessible :friend_id, :user_id
end
class User < ActiveRecord::Base
has_many :friendships, dependent: :destroy
has_many :friends, through: :friendships
has_many :inverse_friendships, dependent: :destroy, class_name: "Friendship", foreign_key: "friend_id"
has_many :inverse_friends, through: :inverse_friendships, source: :user
And routes:
resources :friendships
authenticated :user do
root :to => 'home#index'
end
root :to => "home#index"
devise_for :users, :controllers => { :registrations => :registrations }
resources :users
Your main problem is a:
a. how the FriendshipsController destroy method is defined
You're looking for the friendship in the current_user.friendships, but it's not there. It's in inverse_friendships.
You'd need to either check both associations, or let the controller know which one you're looking for. The latter is probably preferable since although they are the same class, they are different resources. Something like this maybe:
# In routes, route inverse friendships to the same controller, but with a
# different path (I'm routing everything here, you may not need that.)
resources :friendships
resources :inverse_friendships, :controller => 'friendships'
# Then in your friendships controller, use the path to determine which
# collection you're working with:
#
def destroy
#friendship = collection.find(params[:id])
# ...
end
# the other collection methods would use the same collection, if you needed them,
# for example:
def create
#friendship = collection.build(params[:friendship])
# ..
end
protected
# simple case statement here, but you get the idea
def collection
case request.path
when /\/inverse_friendships/ then current_user.inverse_friendships
else current_user.friendships
end
end
Finally in your view you'd route to an inverse friendship like:
<%= link_to "remove", inverse_friendship_path(friendship), :method => :delete %>
A normal friendship could use the shorter form, or the full named route:
<%= link_to "remove", friendship, :method => :delete %>
OR
<%= link_to "remove", friendship_path(friendship), :method => :delete %>
EDIT: Searching both associations.
Of course if you wanted to keep it simple, and had no other use for inverse_friends being a separate resource, you could always just...
def destroy
id, cid = params[:id], current_user.id
# search both associations (two queries)
#friendship = current_user.friendships.find_by_id(id) ||
current_user.inverse_friendships.find(id)
# or query friendship looking for both types
#friendship = Friendship.
where("user_id = ? OR friend_id = ?", cid, cid).find(id)
# ...
end

Not showing user during iteration if user already belongs to "group" relationship

I have this relationship where User can create a document(trip) and invite other users to a group that belongs to that document. My relationship indicates that "Group" has a user_id and trip_id column, so for every user I invite, a new Group record will be created in the database.
When I am inviting other users, I only want users who are NOT in the group to appear. Users who are already in the group should not show up, but my view still shows the users.
I've been playing around with <% if !friend.trips.include?(#trip)%>, but I can't seem to get the correct view. The record is being created in the database correctly.
Also, when I am viewing groups/new.html.erb, this is the url http://localhost:3000/groups/new?id=2, where the id is the trip_id.
My question:
Am I using restful convention? That is, should I be using the new method here (as is) or should I be using the index method instead?
How do I iterate through each friend's groups to make sure that none of the group's trip_id is equivalent to #trip.id?
Thanks!
view (/groups/new.html.erb)
<% if !#friends.blank? %>
<% #friends.each do |friend| %>
<% if !friend.trips.include?(#trip)%>
<%= link_to groups_path(:user_id => friend.id, :trip_id => #trip.id),
:method => :post, :action => 'create' do %>
<div id="addfriend_totrip_button_groupsnew">add friend to trip</div>
<% end %>
<% end %>
<% end %>
<% end %>
groups_controller.rb
class GroupsController < ApplicationController
before_filter :authenticate, :only => [:update, :create, :destroy]
def new
#trip = Trip.find(params[:id])
#user = User.find(current_user)
#group = Group.new
#friends = #user.friends.all
end
def create
#trip = Trip.find(params[:trip_id])
#user = User.find(params[:user_id])
#group = Group.create(:user_id => #user.id, :trip_id => #trip.id)
if #group.save
flash[:success] = "Friend added to group."
redirect_to groups_path(:id => #trip.id)
else
flash[:error] = "Could not add friend."
redirect_to root_path
end
end
end
user.rb
class User < ActiveRecord::Base
has_many :trips, :through => :groups
has_many :trips, :dependent => :destroy
has_many :groups
end
trip.rb
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :traveldeal
has_many :groups
has_many :users, :through => :groups
end
group.rb
class Group < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
First of all, you have has_many :trips called twice in your User model. I understand you have two different types of User-Trip relationships (one directly, and one through Group), but you can't give both the same name, otherwise one will hide the other. Try defining your User model like this:
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:class_name => "Trip"
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
Trip.joins(:groups).where({:user_id => self.id} | {:groups => {:user_id => self.id}})
end
end
There's also the problem that you're searching the friend's list of groups for a Trip object. Try changing that line to:
<% if !friend.all_trips.include?(#trip) %>
Or without the new method, something like this should work:
<% if !friend.groups.where(:trip_id => #trip.id).first %>
I don't see anything un-RESTful about your approach. RESTful in general means stateless. I.e. the only thing a response depends on is the HTTP method and the address. So as long as your not keeping state information in, say, the session, you should be following REST.

build_* not working despite being has_one

My intention here is just to create a corresponding contact when a user
signs up, but the said contact is never created, despite using build_*
with a has_one:
Contact model:
has_one :user
User model:
belongs_to :contact
Users Controller:
def signup
#user = User.new
end
def signup_success
#user = User.find params[:id]
contact = #user.build_contact
contact.contactable = School.first
contact.save
end
protected
routes:
map.resources :users,
:collection => {
:signup => :get
},
:member => {
:signup_success => :any
}
Any idea of what I'm doing wrong? Thanks for any suggestions.
Does it work if you pass the attributes to build?
contact = #user.build_contact(:contactable => School.first)
contact.save

Resources