Pundit Usage When Creating/Deleting Objects - ruby-on-rails-4

I am creating and updating objects, my controller has:
def create
#mymodel = MyModel.create mymodel_params
authorize #mymodel
end
I need to authorize create so I have added authorize #mymodel but surely this should come first? The problem is what parameter do I give authorize?
I could do
authorize :mymodel
but it seems that this is not the way Pundit is supposed to be used inside controllers that have associated policies. What is the correct way to authorize here? Apologies if I missed it in the docs.

Wouldn't you be able to do:
def create
#mymodel = MyModel.new
authorize #mymodel
#mymodel.update_attributes(mymodel_params)
end

For pundit, you can call the model name in it without it being a instance variable or symbol.
ex. Posts
class PostPolicy < ApplicationPolicy
def create?
user.admin?
end
end
class PostsController < ApplicationController
expose(:post)
def create
authorize post
post.save
respond_with(post)
end
end
The pundit section on this application will show it in action.

The correct way to do this is like this:
def create
#mymodel = MyModel.new(mymodel_params)
authorize #mymodel
#mymodel.save
end
This way, you can use the properties set in your #mymodel instance, for example:
class MyModelPolicy
def create?
#record.user == #user
end
end
So your data is not persisted before you authorize the record and you can authorize the record based on what the data will be.

Related

How do I know what functions are run in Generic Views?

I am new to Django and scratching my head with the following question
how do I know which function is run in specific processes?
for example in below view, retrieve function is executed. I flipped the documentation, however, could not find anything named retrieve to overwrite
class ProfileRetrieveAPIView(RetrieveAPIView):
serializer_class = ProfileSerializer
def retrieve(self, request, username, *args, **kwargs):
print("hey yo i am running 01")
try:
profile = Profile.objects.select_related('user').get(
user__username=username
)
except Profile.DoesNotExist:
raise
serializer = self.serializer_class(profile)
return Response(serializer.data)
In general the generic views in Django Rest Framework are made by inheriting from various mixins. It is also documented which mixin a view is inheriting from. For example the documentation for RetrieveAPIView which you use is as follows:
Used for read-only endpoints to represent a single model instance.
Provides a get method handler.
Extends:
GenericAPIView,
RetrieveModelMixin
So you can override get here if needed (it actually calls the retrieve method here). If you further follow the link for RetrieveModelMixin you will see that it's documentation has this line:
Provides a .retrieve(request, *args, **kwargs) method, that implements
returning an existing model instance in a response.
Here we can see it mentions that it provides a retrieve method. So you just need to check the documentation for the super classes for a respective class if you want to know what method is used by it.
Note: If all else fails when searching the documentation consider simply looking at it's the source code on GitHub or in your own IDE.
;)
According to DRF documentation, there is a default mapping the views and the router:
list is for GET on a list of objects, like /users/
retrieve is for GET on a single object, like /users/3/
create is for POST on a non-detailed endpoint, like /users/
update is for PUT on a detailed endpoint, like /users/3/
partial_update is for PATCH on a detailed endpoint, like /users/3/
destroy is for DELETE on a detailed endpoint, like /users/3/
Those are the expected/standard mappings within DRF views and viewsets
You can view some examples here: https://www.django-rest-framework.org/api-guide/generic-views/#mixins

Location for user_parameter_sanitizer.rb

Recommendation from GitHub devise gem:
If you have multiple Devise models, you may want to set up a
different parameter sanitizer per model. In this case, we recommend
inheriting from Devise::ParameterSanitizer and adding your own
logic:
class User::ParameterSanitizer < Devise::ParameterSanitizer
def initialize(*)
super
permit(:sign_up, keys: [:username, :email])
end
end
And then configure your controllers to use it:
class ApplicationController < ActionController::Base
protected
def devise_parameter_sanitizer
if resource_class == User
User::ParameterSanitizer.new(User, :user, params)
else
super # Use the default one
end
end
end"
Where should I create the file user_parameter_sanitizer.rb to host the code?
The info given by the devise GitHub page is excellent, but I am new to Rails and coding in general.

Returning related fields of a model instance

I am creating an app with a rest API that should return values for instances of objects based on the url given. Right now I have the API working using ModelViewSets of my objects for the API.
For example I have three objects, user, transactions, and goals.
As it stands I can go to /mysite/api/users and return a list of all users
I can also go to /mysite/api/users/1 to return just the user with the id '1'.
I can do something similar with transactions and goals.
What I'm looking to do is go to url /mysite/api/users/1/transaction/1/goal
to find the goal associated with the transaction for that user.
I've been scouring tutorials and am not sure what the right question is to ask in order to find something useful to learn how to do this. What is the correct way to go about setting up my rest api like this?
If I understand correctly, you want to create nested ressources.
If you are using Viewsets, then the ExtendedRouter class of the drf-extensions package will allow you to achieve this.
Drf-extensions documentation about this feature: https://chibisov.github.io/drf-extensions/docs/#nested-routes
There is also this module, who also offer the same features.
You can either use url params or query params to solve your issue. I will explain the URL params solution here,
serializers.py
#Write a Goal Serializer
urls.py
#change the URL according to your environment
url(r'^users/(?P<uid>[0-9]+)/transaction/(?P<tid>[0-9]+)/goal/$', GoalViewSet.as_view({'get': 'user_transaction_goal',}), name='user-transaction-goal'),
views.py
class GoalViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
queryset = Goal.objects.all()
def user_transaction_goal(self, request, uid, tid):
#assuming user is FK in transaction and transaction is a FK in goal
#modify the filter rule according to your model design
goals = Goal.objects.filter(transaction=tid, transaction__user=uid)
serializer = GoalSerializer(goals, many=False)
return Response(serializer.data)
As #clement mentioned you can also use plugins to handle this situation.

Django Rest Framework - best way to an endpoint?

I'm learning Django + DRF + React and I reached the phase where it's time to protect some endpoints. Some are easier to protect, just a permission so only the user which created a particular object (and the admins) are able to see it. There is one endpoint which is tricky for me. It's a GET call and returns something like:
{book: "Who am I and how come",
id: "whatever31",
reading: ["user1", "user2"]}
I want to protect this endpoint based on the user making the request (Session auth) so only calls coming from user1 and user2 can access this object (also, not exposing reading field, but that's probably a different discussion). Should I use a custom permission on DRF view? Should I use a filter instead in queryset method? Maybe both?
Custom permission just like creating an decorator, both of them match what you need:
class InReader(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj):
return request.user in obj.reading

Is there a better way of overriding devise controller in rails

I have to populate a field in my db when a new user signs-up.But this field is not to be filled by the user, instead will be populated by the application in the db. Here is what i tried:
Added the extra field(investorId) in the migration file.
Overridden the devise controller:
def create
super
if #user.save
#user.investorId = #user.id + X---> some number
#user.save
end
end
Though it is working fine, but I want to know if there are better ways of doing it since I am doing it for the first time.
Thanks,
Sachin
If you need to generate a value before you create a record, you can use the before_create or after_create callbacks on the model.
User < ActiveRecord::Base
after_create :generate_investor_id
private
def generate_investor_id
reload
investor_id = "#{self.id}something"
update_attribute(:investor_id, investor_id)
end
end
Don't override your devise controller, there is no benefit of doing this. Simply put the below logic in your after_create callback -
User < ActiveRecord::Base
after_create :create_investor_id
private
def create_investor_id
self.update_column(:investor_id, id+some_number)
end
end