How can I customize 'CanCan' when using polymorphic? - ruby-on-rails

I'm using the gem called acts_as_commentable_with_threading
It automatically creates comments table, and in which there are these columns such as commentable_type, commentable_id, and etcs.
It is made to be polymorphic, and it's working fine.
I have these three models.
User
Community
Topic
They have comments under of each.
I'd like to enable User to delete his comment or somebody else's comment on his User page.
(He can delete all the comments on his User page)
I figured out to make it enable coding like this code below.
But I found out the same thing will happen to Community Page and Topic page.
Even if the user was not original creator of the comment, it will be possible for him to delete all the comments if the ID of the Community/Topic is the same as USER ID.
I only like it enable to USER model. How can I customize this?
models/ability.rb
can [:create, :destroy], Comment, {:user_id => user.id}
can [:destroy], Comment, {:commentable_id => user.id}

Since you're Comment class uses a polymorphic association, the delete action is currently allowed when the commentable_id matches the id of the user. Since you can have conflicting ids across the different tables, you need to match on the commentable_type as well. Try something like this:
can [:destroy], Comment, {:commentable_id => user.id, :commentable_type => user.class.name}

Related

Setting custom permissions within a "dashboard" area in rails

I'm putting together a side project for a teacher/student type of website where the student will share a dashboard with the teacher. The teacher and student can both upload files and leave comments in the dashboard.
The part that I'm stuck on is the permission. For student, I've set up the index controller to this method
def index
#Homework = Homework.where(:user_id = current_user)
end
With this, I'm able to have the student only see the work that they have, but I'm confused on how to get the teacher to see each individual student's work?
Suggestions? Thanks
Here's a simple solution if you only ever need to support a single class in your application:
def index
if current_user.teacher?
#homeworks = Homework.all
else
#homeworks = Homework.where(user_id: current_user)
end
end
Otherwise, your Homework schema does not seem to be correctly designed. See your query below:
Homework.where(:user_id = <student_id>)
This works to retrieve a student's homeworks, but it does not work to retrieve a teacher's students' homeworks. You may need a class_id for a teacher to see each individual student's work:
Homework.where(:class_id = <class_id>, :user_id = <student_id>)
A Class has_many Teachers and has_many Students. This design will allow you to support multiple classes.
Some more guiding questions:
Is teacher/student both kept in the same User model?
How do you differentiate between teacher/student in your current User model?
Is there a "user_type" column somewhere in User?
What happens if the current_user is of the "teacher" user_type?
For complex user permissions, use CanCanCan: https://github.com/CanCanCommunity/cancancan
Don't use uppercase instance variables. ex: #Homework should be #homework
Check out the gem CanCan. Install it (follow the instructions, you should have to put something in application controller), Then, put in your ability file:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Homework, user_id: user.id
end
end
Then at the top of your StudentController put
load_and_authorize_resource
And the index action should look like:
#homework = #student.homework
Now, you didn't post your whole controller so this is a much as I can help.
I believe you may have a bigger underlying issue. You have students and teachers has_many homework i read in your comment. Then in your example you use user_id. You are likely overriding your students and teacher ownership of homework. You would need a has_and_belongs_to_many relationship OR you would need a student_id and teacher_id columns on the homework table
Cancan automatically generate a number of instance variables which can make it feel like magic. Watch the free railscasts on cancan the same guy who made the video wrote the CanCan library.

Find what user edited what post in RoR

Right now I have a simple blog website setup with devise which allows users to edit posts. I also have activeadmin installed on the backend. What I want is when a user signs in and they edit a post I want that users email to be tied to that post. Then I could go into active admin and setup the column to view the user later. Trouble im having is that im not sure how to automatically tag a users email to a specific post when they edit it, also my user and post model are on different tables in the database.
Thanks for any help.
http://guides.rubyonrails.org/association_basics.html#the-has_and_belongs_to_many-association
Just set up a join table for a has_and_belongs_to_many association on each model. This is a standard active record association which should be well-documented; see the link above for a start.
Then in your update method for the PostsController you can add a line like:
#post.users << current_user
(obviously the specific code will vary depending on the names of your variables & associations -- i'd probably rename the association to "editors" or something like that)
I don't know anything about active admin, so I can't tell you how to make these associations viewable there. But it shouldn't be too hard once the association is set up properly.
Two approaches I use to create user_stamps.
paper_trail gem that records all modifications in Version table.
Works great with Active Admin.
Adding updated_by_id and created_by_id columns to all tables (paper_trail needed)
# In each Model.
belongs_to :updated_by, :class_name => "AdminUser", :foreign_key => "updated_by_id"
belongs_to :created_by, :class_name => "AdminUser", :foreign_key => "created_by_id"
after_create { |i| i.update_column(:created_by_id, PaperTrail.whodunnit) }
after_save { |i| i.update_column(:updated_by_id, PaperTrail.whodunnit) }
These columns will be redundant but a great compliment to Version table and is faster and better for many reports and scopes.

Forem gem: how to link a forum to other models

I have groups (Group model) in my app, which represent groups of people.
I want each group to have its own forum.
Should I just have the forum id in the groups table? It doesn't feel right. If I did it myself, the forum would have a polymorphic association to a "forumable" element (groups in this case, but I have other models that would need a forum).
Any opinions on what I should do? Modify the gem to fit my needs, or just have the forum_id in my models that need a forum? Or another solution maybe?
I'm the guy who started Forem (its the volunteers who did most of the hard work, though!), I think I can answer this question.
If you want only certain groups to have access to one and only one forum then you can put the forum_id field on the groups table and do it that way. What you can do then is override the can_read_forem_forum? method in your User model to perform a permission check for that user:
def can_read_forem_forum?(forum)
groups.where(:forum_id => forum.id).any?
end
This is used in Forem's ability model to determine whether or not a person can access a forum. What this method is going to do is that it will only return groups for that user that have link that specific forum. If there are any, then it's known that the user can access that forum.
Now if you're going the other route where a group may have access to many forums, well then you'd define a joins table between groups and forem_forums (called forum_groups) and define it as an association in your Group model like this:
has_many :forum_groups
has_many :forums, :through => :forum_groups, :class_name => "Forem::Forum"
You would need to also define a new model inside your application for this forum_groups association, it would be called ForumGroup and go a little like this:
class ForumGroup < ActiveRecord::Base
belongs_to :forum, :class_name => "Forem::Forum"
belongs_to :group
end
We're doing it this way so you have an easy way to manage the associations between forums and groups. If you did has_and_belongs_to_many, it generally only provides a gigantic pain in the ass when you want to delete one specific record from that join table.
Now, with that all nicely set up, the method you want to define in your User model is this one:
def can_read_forem_forum?(forum)
groups.joins(:forums).where("forem_forums.id = ?", forum.id).any?
end
Same thing, except this time we find all the groups that are linked to a specific forum through that association we set up earlier. This will do an INNER JOIN on the forum_groups table, and then another on the forem_forums table, getting the data required.
I hope this helps you, and thanks for using Forem!

How should I authenticate access to an Active Record object?

This seems like a straightforward task, but I'm having trouble figuring out the "rails" way to do it.
I'm using Authlogic to authenticate Users, and each user has_many :blogs, :pictures, etc. What I'm not sure how to do is restrict access (show/update/edit/delete) of User A's blog just to User A.
It seems I have three options.
Add the condition (user_id == current_user.id) to all of my Find statements
Use a before_filter to accomplish the above
Use the default_scope to append this authentication condition (But I think this will be a pain when I go to write tests).
What have others found to be the best approach to this problem?
As a bonus question, suppose it's the case that a User has_many :blogs, a Blog has_many :sections, and a Section has_many :images. In this case, all of the elements belonging to a blog need to be restricted to User A, but only the blog has the user_id column. What's the most efficient/elegant way of appending the same authentication condition to the Sections and Images?
Thanks,
Mike
Another option could be to use the cancan gem. https://github.com/ryanb/cancan
It's really straightforward and you can solve your bonus problem as well.
For example:
can :manage, Blog, :user_id=>user.id
can :manage, Section, :blog=>{:user_id=>user.id}
can :manage, Image, :section=>{:blog=>{:user_id=>user.id}}
This setup is good if you have
Blog belongs_to User
Section belongs_to Blog
Image belongs_to Section
For how to set up the rest please see the Wiki.
Oh, and I forgot there is also a screencast about cancan
Use before_filter with function which checks whether user is authenticated or not.

Rails: How to treat some fields of model info independently? Eg. Account vs. Profile information

I have a User model with the usual information (login, email, name, location, etc). However, when users decide to edit their information, I'd like to separate the fields to be edited according to the appropriate concerns.
For example, I'd like to have Name, Bio and Location to be edited on a Profile page or tab, and login, email and password to be edited on an Account page or tab.
What are the best practices, and the safest way, to accomplish that? Should I have two separate model/resources: User and UserProfile? Or can I just create something like a profile method in the UserController, with a custom form with only the specific profile fields, and link to it in the user page? I'm really confused on how to go about this.
Thanks in advance for any ideas you might have.
I think it depends on the rest of your Application. It sounds like in your case it's good to be modular. If you want users to be able to see the profile of other users, it's an advantage to have a separate model for the Profile and like it with a has_one relationship. I'd just call the class Profile so it can be accessed through user.profile in your controllers and views.
Models:
class User < ActiveRecord::Base
has_one :profile, :dependent => :destroy
end
class Profile < ActiveRecord::Base
belongs_to :user
end
If all your users have both profile and account fields then I wouldn't put it in seperate models. It will only add unnecesary complexiety to your forms and add may add some sql queries.
I don't see here any security problems. In case of editing, both actions (edit account and profile) should be protected the same way - so only owner user should be able to edit both of them. If he want to "hack" it and edit also his login when he edits his first name then it is his problem. It won't cause any problem since he is allowed to edit both fields.
According to views that are viewable for other people: just don't display there any fields that belongs to account part.
How to seperate it? For me the cleanest way is to add this kind of routes:
map.resources :accounts
map.resources :profiles
And use paths like /accounts/34/edit to edit account part and /profiles/34/edit to edit profile part.
In this case you will need a seperate controller for both routes: accounts_controller.rb and profiles_controller.rb. If they share a lot of similar methods and behavior, you can add it in users_controller.rb and in profiles and accounts controllers inherit from it.
You can also do it with one controller:
map.resources :accounts, :controller => 'users'
map.resources :profiles, :controller => 'users'
But I don't know how to pass some additional value with routes created with resources. When you use connect you can pass it with :defaults => {:foo => 'bar'} and then it will be availble in controller as params[:foo] but it doesn't seem to work with resources. So how can you distinguish between accounts and profiles? You can read it from current url (here is example). And then in controller you can render different views according to requested resource.
I would be inclined to cut along the grain here. It's going to be easier to map a model to a form. So if you have information that is accessed within different forms in your UI, I would create a model for each.
That said, at a certain point in my systems I tend to pull the profile information out of the base User class, so the User becomes solely for authentication.

Resources