Model Constraint - User Can Only Vote Once Per Post - ruby-on-rails

I want to restrict users to voting up or down on posts and each User can only cast one vote for each Post. I have to check if the User voted on the post before I create a new relation.
I am already using CanCan for other restrictions like "the User can only edit its own Post", but this case is something different, so I want the best practice. After adding an uniqueness-validation to the model should I implement the rule above ("one vote per post and user") via CanCan or just in the controller?

To me it sounds like neither, this is a validation that should be performed in the model layer IMO.

To me, this is not a CanCan job. On a cursory review of the issue, my sense is that I would create a predicate method in the model and call that from my view.
def votable?
if blahblahblag
true
else
false
end
end
In the (haml) view:
- if model_name.votable?
= cool_markup_tag
Something like that ... just a rough sketch but, to me, this keeps the controller clean and the check in the model where I think it belongs (and is along Logan's comment/answer).

Related

Different update / edit methods available to different users

I have a model Post, which is submitted and graded by different Users. The submitter and grader are identified by submitter_id and grader_id in Post model. Note that an user is both a submitter himself and a grader to others.
I want to make sure that the submitter can only edit the content of the Post but not the grade. Likewise, the grader can only edit the grade but not the content.
Is multiple edit methods the way to go? How should I accomplish this otherwise?
You can have a role column in your users table, and the role can be either submitter or grader. Not sure what you are using for authentication, but in case you are using devise, you can access the currently logged in user with current_user helper (in case you are using something else, figure this part out, or add a new helper).
Now in your update method, you can do something like this:
# Controller
# scope post to current user, so that a user cannot edit someone else's post. A crude way to achieve this is post = Post.find(params[:id])
post = current_user.posts.find(params[:id])
post.content = params[:content] if post.submitter?(current_user.id)
post.grade = params[:grade] if post.grader?(current_user.id)
post.save!
# Model - Post.rb
def submitter?(user_id)
self.submitter_id == user_id
end
def grader?(user_id)
self.grader_id == user_id
end
The advantage of keeping those methods in the model is that in case you permission logic changes (who is submitter, or a grader), you need to change it at a single location. DRY.
You can modify the above approach to show error messages, and do other similar stuff. In case you are looking for more granular authorization control, you can look into cancan gem:
https://github.com/ryanb/cancan
Your post model should only be concerned with persisting data. Better to use plain old ruby objects to encapsulate the higher order behavior of grading and submitting. Consider using service objects or form objects.
Each service or form object can then include ActiveModel::Model(rails > v4) to get its own validations.
See more about service and form objects here: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
If you only have one submit action and one grade action, its probably ok to keep in one controller. But if you start having multiple actions that are related to submitted, and multiple actions that are related to grading, this sounds like they would make great resources controllers on their own.

Determining if an association between two models has been created within one model?

I've been researching this topic for a day now, and I haven't seen a solution that could adequately allow this. I would have even give up and said that it's not possible, but I see large companies achieving this in their apps!
I need to know if the current user is following another user. I need to know this many times (for the current_user) without polling the DB again
The solution needs to be friendly for reuse. A solution (that's not friendly for reuse) I had come up with is as follows:
module UsersHelper
def is_following?(user)
return false if current_user == user
user.is_following = Relationship.find_by(followed_id: user.id, follower_id: current_user.id)
end
end
is_following?(#user) can now be called in any controller
Notice that I'm able to access current_user because this helper method will be called in a controller, not a model.
This implementation is cool for one model, maybe two... except I need to do this in almost every many-many relationship I have in the app. So it has to be scalable.
I'm referencing exactly what Twitter does with their following.

How to nest a Rails scriptlets within each other?

I have got a few models that are different types of user account - i.e. a User model, an Admin model, a Guest model.
I'm trying to create a forum as part of an application I am making. When a post is created, the user_type attribute is set to the type of user that created the post. What I want to do seems to be something pretty simple, but I can't seem to work it out. Basically, I want to get the username of the person that created the post. I can't just use something like post.user.username, as I have a few different models, and I don't want to have to do something ugly with if statements, so is there a better way around? This might show what I'm trying to achieve (although this doesn't work):
created by:<%= post.#{user_type}.username %>
which would then output to the relevant code:
post.user.username
or post.admin.username
or post.guest.username
Thanks!
Maybe your best course of action
class Post < ActiveRecord::Base
...
def username
[user,admin,guest].find(&:present?).try(:username)
end
end
You can stick this in your model, or even better, a decorator.
Now you can just do this in your view:
created by:<%= post.username %>
find retrieves the first thing that responds to present?. In other words, it finds the first thing that is not nil. Then, we try to call username on whatever that method returns. If there is no user, admin, or guest for this post, the method will return nil.

Authorization for actions (not models!) with roles in rails

If I've got a simple rails user model that has an array of roles, is it sufficient enough to control access to actions by simply checking the model's role attribute for that role and blocking/proceeding accordingly?
Is there an advanced system that I ought to leverage due to unforeseen complexity?
Important: I'm not looking to authorize users/roles to models (I am already aware of CanCan). I'm looking to do security at the controller level so that I can break out the functionality in finer detail.
Even more important: Seriously, I'm not necessarily asking about CanCan, please read the question carefully and pay attention! :)
Question 1: YES, Question 2: NO.
I just keep this simple
If you check the models attribute in the controller, the controller will restrict all users that do not have this attribute set.
ex:
def create
#user.find(params[:user_id])
if #user.admin?
#post.new(params[:post])
#post.create!
end
end
make a method in the user model
def admin?
role == "Admin"
end
You should make better code than this. To much logic in the controller, but this will keep all, except admins out.

Is it safe to put reference to current user in User model in Rails?

You know, I think I have to check current user in the model callbacks (like before_update). Rather than rely solely on adding where ('something.user_id = ?', 'current_user.id') in the controllers. I need something like Thread.CurrentPrincipal in .NET
Is it safe to put reference to current user in User model? I'm sorry I don't really understand how it works under the hood yet.
Or how you do it The Rails way?
Sorry if this a silly question.
Added on 3/27
Oops
To get the right answer you have to ask the right question. And that's not an easy task in itself. How can it be that other people's questions are so obscure and they get their answers and your own question is so clear-cut but nobody understands it? :)
I do not understand where to put security check. That the user will get access only to his own stuff. On the controller level? And then test every action? "should not /view|create|edit|destroy/ other user's stuff"? I thought may be I can put it in the model and have one place to /write|refactor|test/. That's why I asked about how I can get a reference to the current user.
Actually I'm surprised I didn't find anything relevant in Rails Guides or Blogs. Some questions were asked but no authoritative "best practices" besides "don't do it".
After giving some thought I decided to just create in the controller before_filter the scope scoped to the current user and just rely on my own convention (promised to myself that I won't access the model directly). And just test it once per controller. That's not a banking application anyway.
I'm not sure I get your situation. If you want to check if some other model's instance belongs to current user - use associations (I inferred that from "something.user_id = ?").
Else - in ActiveRecord before_update method is used on a per-instance basis. I.e. you pass current instance to that callback as an argument. Hence:
def before_update(current_user_instance)
current_user_instance.do_something
end
Will yield any user instance as current_user_instance in the callback. So you can do following:
>> user_1 = User.find(1)
>> user_1.update_attribute(:some_attribute, 'some value')
>> user_2 = User.find(2)
>> user_2.update_attribute(:some_attribute, 'some other value')
This will call do_something method on separate instances (user_1 and user_2)
I don't really understand Thread.CurrentPrincipal, but current_user generally means the logged in user and that is completely a controller context. It is not available for use inside a model. So a hacky solution would be:
class UseCase < ActiveRecord::Base
after_save :my_callback_method
attr_accessor :current_user
def my_callback_method
# Some operations based on current_user
end
# ...
end
And then in your controller:
#...
use_case = UseCase.find(use_case_id) # Just an example, can be anything
use_case.current_user = current_user
# then this
use_case.save
# or basically
use_case.method_that_triggers_after_save_callback
#...
Warning: I am sure this is bad practise (I have never used it myself). But this will work. Ruby gurus / MVC gurus out there, please comment.

Resources