The different ways of using CanCan abilities - ruby-on-rails-3

I have four scenarios I want to get some feedback on so I can better understand how CanCan works with abilities.
1 - Is using the object from an iteration acceptable for passing the model to CanCan. It seems to work fine. It seems this will define a very specific ability from the object passed.
#users.each do |user|
link_to user.name, user_path(user) if can? :edit, user
2 - #user is not a variable that is set in the controller. This, I'm assuming, is the same as #1 since load_and_authorize_resource creates the #user instance.
#users.each do |user|
link_to user.name, user_path(user) if can? :edit, #user
But, what about when an instance variable exists in a view that is not related to the current model and is not deliberately set in the controller. For instance the following inside the show user page when the #account instance is NOT set in the show action. How does CanCan use #account when it shouldnt know what it even is?
link_to "view user account", account_path(1) if can? :read, #account
3 - What about defining the ability using the model? I assume this is a very broad "can edit users" ability...when would you ever use this?
#users.each do |user|
link_to user.name, user_path(user) if can? :edit, User
4 - Using an association. Doesn't break the page...but, Im not sure if it is working as expected.
#users.each do |user|
link_to "view user account", account_path(1) if can? :read, user.account

Oooh, a multi-part question.
Case #1 and #2
These two are nearly identical. The difference is that the #user variable is being set up in your controller before hand with the load_and_authorize_call or some other filter that runs before your actions.
This is useful if you want to check permissions on a single object, i.e. a user can only edit their own record.
For the end of case #2:
You will need to ensure that the variables are set within the controller before you can access them within your controller or view to do permission checking. Same as if you wanted to access them in other instances that have nothing to do with permission checking.
Case #3
Mainly used in cases where a user can edit all objects of a particular class.
This is useful if you want a user to edit all objects of a particular class, i.e. an admin can edit all user's details
Case #4
Using an association will grab that record out and pass it to CanCan, just like using if can? :read, user would. Not really any different to Case #1.

Related

How to properly scope User attributes in rails

How to achieve simple scoping in your views such as:
<% if #user.admin %>
where "admin" is the following scope in user.rb:
scope :admin, where(role: "admin")
there is a column Role which is a string in the Users table
I've done the same thing before with another Model (but not a devise user model) to which I could later call
<% if objective.completed %>
right after calling an each method in the objectives.
However
When I do the exact same thing to a user model I get an
undefined method `admin' for #<User:0x00000107e39038>
How could I make it work? I've been digging for hours.
For a no scope workaround try:
<% if #user.role == "admin" %>
You simply can't use scopes this way. Scopes are used as class methods, so if you run
User.admin
it returns list of users matching given condition. What you need is an instance method. Add it to your user.rb file:
def admin?
admin == 'admin'
end
and you will be able to use it in your view:
- if #user.admin?
anyways, you should definitely reconsider storing roles as string in users table. Try to create another table called roles.
Scopes are usually class level methods, having said that, how can you access it with an instance. I think, that is why the error says undefined method.

Rails Tutorial - Not understanding implementation of follow/unfollow users in Chap 11

My main difficulty comes from understanding the relationship that the _follow and _unfollow partials have with the create and destroy methods defined in the RelationshipsController from Chapter 11.2.3. I'll just focus on the act of unfollowing a user for now (since the act of following is mostly analogous).
Hartl defines the partial for unfollow as such:
<%= form_for(current_user.relationships.find_by_followed_id(#user), html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn btn-large" %>
<% end %>
and the corresponding destroy action as such:
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow!(#user)
redirect_to #user
end
What I am having trouble understanding is:
The #user variable in the first line of the unfollow partial .. is this a) defined in the show action that currently displays the page, or b) defined in the destroy action? It appears that the form_for helper already finds the #user to be destroyed, so why does the destroy action needs to find the #user all over again to be destroyed in the controller?
In the destroy method, the #user is found by first finding the Relationship id. I don't see how the Relationship id is passed into the URI in the first place (since seeing a particular user to unfollow shows up as /users/2), much less how it is used to find the #user to destroy. I understand that each Relationship table has an id, a followed_id, and a follower_id, but do not see how the id element itself comes into play here.
Thanks, for reading, and for answering my questions!!
1.) If the partial is rendered within the show action, the variable #user must be defined in that action. Rails won't execute the destroy method so the variable definition in there will never be executed.
Since HTTP is a stateless protocol, the server needs to create the necessary state on every request. That's why #user must be defined in every action.
2.) Where did you check that the url is "/users/2"? As I don't see the whole code I can only make guesses but the following line current_user.relationships.find_by_followed_id(#user) should return a Relationship object. It will be translated into something like "/relationships/8", where 8 is the ID of the Relationship. Because you specify `method: :delete, the destroy action will be invoked.
I think the url "/users/2" is after the destroy action performed the deletion was performed. There could be a redirect in the destroy action. (eg. redirect_to current_user). You can see all the invoked actions in the log file. Try scrolling through the log and see if you can find RelationshipsController#destroy. You will see the invoked url there. Also you could inspect the HTML to see where the <form> tag's "action" attribute points to.

Rails 3 How to access user data from user_id column in belongs_to :user association

I am trying to create an activity feed with the most recent activities from my TrainingSession model.
class User
has_many :training_sessions
end
class TrainingSession
belongs_to :user
end
The problem is that I am trying to access a user's data in the view page (mainly the user's name) by instantiating an object from the TrainingSessions database table, as shown below:
<% #training_sessions.each do |training_session| %>
<%= training_session.user_id %>
The problem is that, although I successfully get the user's id, I cannot call, for example:
training_session.user_id.name
... otherwise I get the NoMethodError shown below:
undefined method `first_name' for 2:Fixnum
so my question is ... how can I access the user's data from the TrainingSession's object?
any help would be much appreciated. Pretty stumped on this one.
The reason that you get a "undefined method `name' for nil:NilClass"-error is that some training sessions do not belong to a user. The solution is to cleanup your database:
DELETE FROM training_sessions WHERE user_id IS NULL
If it is expected behavior to have training sessions that don't belong to a user, you have to check that the user is not nil in your loop:
<% #training_sessions.each do |training_session| %>
<% unless training_session.user.nil? %>
<%= training_session.user.name %>
<% end %>
<% end %>
First of all, you need to rename your model name (TreningSessions) into singular name (TreningSession). That's the convention rails uses. Rename only model, leave has_many without change.
Now the user association,you should call it via user object. user_id is just a attribute that represents field in database and it's value, while user is an association object. Try this:
training_session.user.name
More on ActiveRecord relations
Here is what I ended up doing, creating a local user variable containing the user_id and using that variable with the find method on the user model to instantiate an instance variable #training_session_user in my controller, like the following:
#training_sessions.each do |training_session|
user = training_session.user_id
#training_session_user = User.find(user)
end
then I call this in my view:
#training_session_user.first_name
and it retrieves the name with no errors.
If anyone has a better solution please feel free, but I will mark this as correct for now.

Rails 3.1 associations and counting?

I have two models in my Rails application, Users and Calls.
The model associations are set as follows:
user model
has_many :calls
call model
belongs_to :user
I am trying to call, within the application.html.erb layout, the number of calls that the current user has.
Currently, I am using the following string:
<%= Call.count %>
Which works but it's obviously counting all calls, not just the calls that the current user has.
So I swapped that for the following:
<%= current_user.Call.calls.count %>
I am confused as to how to do this. I need to be able to call the count from anywhere so I can then start working on counting based on the last 30 days etc.
Do this:
current_user.calls.count
You can do the same thing with any User object:
user = User.find(1)
user.calls.count
You then can chain more conditions to do the date-based counts:
user.calls.where("calls.created_at > ?", 30.days.ago).count
You don't really want to be putting database calls within views, you really want to put that into a controller. You'd be looking for something like User.find(current_user.id).calls.count as this will then use the association of which you've set up, or if you want to disregard the model relation you could do Call.where(:user_id => current_user.id).count
So put the below into the relevant controller (in the correct action), and likewise for the view.
Controller
#count = Call.where(:user_id => current_user.id).count
View
<%= #count %>

Accept terms of use rails

What is the best way to add a check for accepting terms of use in a rails app?
I can't seem to get validates_acceptance_of working quite right. I added a bool to my user model (was that necessary?). And then have a checkbox that returns either true/false.
I feel like I'm just making a silly little mistake. Any ideas?
In your model,
validates_acceptance_of :terms
If you're using attr_accessible in your model then make sure you also add,
attr_accessible :terms
In your view,
<%= form_for #user do |f| %>
...
<%= f.check_box :terms %>
...
<% end %>
There is no need for an extra column in the users table unless you plan on denying access to users who have not accepted the terms of service, which won't exist since they can't complete registration in the first place.
This is a working Rails 4 solution:
Terms of service doesn't need to be a column in the database
Form
= f.check_box :terms_of_service
models/user.rb
validates :terms_of_service, acceptance: true
And most important, devise will sanitize your parameters and terms of service will be removed from the submitted params. So:
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:full_name,
:email, :password, :password_confirmation, :terms_of_service)
end
end
end
This is a working solution for Rails-6.1 (I18n) + Devise-4.8.0 + SimpleForm. No need to add a column in the "users" table in DB.
View
<% label_str = t('read_html', mylink: link_to(t('terms'), '/a/b/c')) %>
<%= f.input :terms_of_service, label: label_str, as: :boolean, checked: false %>
Or, if you use Indo-European languages only, you can make it a little more simple, like:
label_str = (t('agree_html')+link_to(t('terms'), '/a/b/c')+'.').html_safe
/app/models/user.rb
attr_accessor :terms_of_service
validates_acceptance_of :terms_of_service, on: :create
validates_presence_of :terms_of_service, on: :create
/app/controllers/application_controller.rb
Devise::ParameterSanitizer::DEFAULT_PERMITTED_ATTRIBUTES[:sign_up] << :terms_of_service
# see /vendor/bundle/ruby/*/gems/devise-*/lib/devise/parameter_sanitizer.rb
Explanation
In the User model, on: create guarantees it is read only in creation. If you need to reevaluate the condition in updating, too, specify it accordingly, like on: %i(create update).
In the User model, I add validates_presence_of to play safe. The reason is, validates_acceptance_of will not be executed when the parameter terms_of_service is nil, in which case validates_presence_of will catch it and set an error. Admittedly, if the data are always submitted via the web-interface you have built AND your implementation is working perfectly, the value should be always either true or false and never be nil. So, validates_presence_of should not be necessary in this sense. It does no harm, though (except you'd need to be a little careful in manual user creation, bypassing the web-interface, such as from the Console).
The last one is neccesary for use with Devise for the same reason as in the answer by #vladCovaliov; that is, to prevent Devise from sanitizing your custom parameter, which is not a column in the database table. The one-liner in the example above can be stated in any files as long as you are sure it is read at the run-time and after Devise Ruby code. application_controller.rb is one of the sure places (though I guess there is a better-fitting place). Make sure the sentence is put out of the class ApplicationController block.