Rails - Show changes after PATCH - ruby-on-rails

I would like to show the users which fields have been modified following his PUT/PATCH request
For example, I have a big "project" form, with several fields, but my user decided to only update the deadline and the project name. After he clicks the "save" button, I would like to show some message saying "You have successfully updated : name, deadline"
If it's possible, I would like some generic code that would detect the update action and infer the variable name. By generic I mean, I want to implement this in my ApplicationController, so I don't have to add code in every controller#update action
Let's look at this sample code from controllers/entreprise_controller.rb
def update
if #entreprise.update_attributes(entreprise_params)
redirect_to #entreprise, notice: "Entreprise éditée"
else
render 'edit'
end
end
Here's an idea of steps to reach my goal. Could you help me with each of these ? Or suggest a better approach ?
Detect that we are doing a CRUD update action, for example from the action name in the code, that should always be update (how can I read, from the code, the name of the action being executed ?)
Guess the variable name : here #entreprise, it can be inferred from the file for example (or maybe calling self.class and doing some regex ?)
Save the list of variables that are going to be updated (maybe some tricks involving before_action and after_action and dirty_tracking ? See my edit.)
Provide this list as a GET argument for the redirect to #entreprise (should be pretty straightforward)
Show this list to the user (this part is OK for me)
EDIT concerning Dirty Tracking
Mongoid already implements this. However the main problem is getting the intermediate variable before it is saved. Each controller instanciates the variable like #entreprise during a before_action callback. And if I add a before_action in my ApplicationController, it will fire before, so no variable is available yet. And as regards a possible after_action in ApplicationController, the doc says "Any persistence operation clears the changes." so it's dead already. I probably cannot get away without rewriting every controller ?
Dirty Tracking & controller in a nutshell :
prepend_before_action
before_action of ApplicationController
before_action of EntrepriseController (which includes set_entreprise, where the variable #entreprise is defined so as to proceed with update)
If we can get a callback to HERE, it would let us inspect the object for dirty tracking information, as the object exist in memory, we can use #entreprise.attributes=entreprise_params and look at the dirty info (where entreprise_params is the strong parameters for #entreprise)
action : on success, it will store the info in the DB, and we lose dirty tracking info
after_action

Related

How does cancancan set the model instance, and how can I inspect it?

I noticed users could access an action they shouldn't be able to access.
I debugged in the rails console with something like
user = User.first
physician = Physician.first
ability = Ability.new(user)
ability.can?(:send_message, physician)
# => false
The above says that user can't access the send_message action for that physician, which is the desired behaviour, yet I know they can in the app!
I think this narrows down the cause to a problem with cancancan loading the wrong model instance for some reason. And that's hinted to in the cancan docs too:
Note: this assumes that the model instance is being loaded properly.
But the problem is I'm not sure how to diagnose the problem from here, since the console says it should be working. I don't know how to view the model instance that cancancan has set, and I don't know what else to try.
Any ideas?
Update
I did manage to work around this by using authorize! :send_message, physician in the controller, but since I only stumbled upon this behaviour by chance, I think it's much more important to figure out why the wrong model instance was being loaded (especially so I can see if that was happening elsewhere too).
I figured out why it was probably happening (but I still don't know how to disagnose it)
I think this was happening because I had many custom actions, and some had #physician = Physician.find(current_user.physician.id) (i.e they're the current user), whereas others were more like #physician = Physician.find_by_id(physician_params[:id]). I'm not sure how cancan sets the instance model, but I do know it's not psychic, so it wouldn't know whether to set it to the current user's physician instance, or the physician instance for the physician id passed in.
What remains?
How does cancancan set the model instance for custom methods (I presume it tries something, and if that doesn't work, tries something else, etc etc)?
Small notes that help:
load_and_authorize_resource does attempt to load the model instance for non RESTful actions
Some useful info in the docs
This may have something to do with what I experienced:
When I returned slug it breaks this behaviour and I can edit all pokemons.
Leaving my notes here in case they are helpful to anyone else.
TL;DR, there are a lot of nuanced assumptions cancancan makes, which you won't know about from the outset. I discovered many of them by thoroughly reading the comments in the cancancan readme, code, and defining abilities docs
So here goes..
How cancancan works
if you call authorize! in the controller action itself, cancancan will look for an instance variable in each controller action.
if you instead simply add load_and_authorize_resource at the start of your controller, that will do two things:
Load an instance variable that cancancan thinks should be loaded, and
Checks for authorization on that model instance
Note that for custom actions, load_and_authorize_resource will still try to load a model instance, but how does it know what to load? It doesn't, it guesses, which, for me, I do not like, so be aware of that.
For me, I prefer to do the work of load_and_authorize_resource myself in two separate steps, so I know exactly what's going on.
Ensure #article is generated via a before action for each controller action (or #articles for index action)
Simply have a line at the top of the controller saying load_and_authorize_resource after the before action that sets the model instance
Note that the only difference is now the developer is responsible for loading the right model instance, and cancancan is not trying to guess it. I prefer this approach because it only takes one mistake to accidentally allow access where it shouldn't be granted.
Also remember that load_and_authorize_resource should always go after any before actions that set the model instance variable
Random notes that may also help
The name of the instance variable depends on the action. If we have an articles controller, then:
For the index action, authorize looks for #articles
For all other actions, authorize looks for #article
It then checks to see if the user is allowed access to that resource.
load_and_authorize_resource checks to see if the model instance exists, and if not, creates one. So if you have a before action that creates #article/#articles, then load_and_authorize_resource won't do it for you (i.e. it won't overwrite it), but if you didn't set one, cancan will try to set one. See here for more on that.
An ability rule will override a previous one. (see here for an example)
Just one last thing, never use current_user in ability.rb, it will error silently (!!), so be sure to use user instead :)
Here's what is happening: https://github.com/CanCanCommunity/cancancan/blob/585e5ea54c900c6afd536f143cde962ccdf68607/lib/cancan/controller_additions.rb#L342-L355
# Creates and returns the current user's ability and caches it. If you
# want to override how the Ability is defined then this is the place.
# Just define the method in the controller to change behavior.
#
# def current_ability
# # instead of Ability.new(current_user)
# #current_ability ||= UserAbility.new(current_account)
# end
#
# Notice it is important to cache the ability object so it is not
# recreated every time.
def current_ability
#current_ability ||= ::Ability.new(current_user)
end

Passing rails requests through a series of controllers?

The question is generalized but I want to ask about a specific case which I want to solve.
I'm working with a really really smelly code base of e-commerce app and I want to refactor it. I thought I should start with the User authentication.
Problem
Before every action in any controller, we check if the user is of a particular type: Guest, Signed-In or Admin and also, if the user is allowed to access this action based on the type. If all the conditions are met, then the action is executed. And this happens in majority of the actions in majority of the controllers.
My thinking
I know this code is smelly because checking if the user is of a particular type and (s)he has access to an action is not the action's responsibility.
My solution which may or may not be possible
We can make a SessionsController (or some other name) and let it handle the authentication and authorization part. But I want the SessionsController to do its job automatically before every request. i.e. Every request should go through the SessionsController and then this controller will decide whether or not to forward the request to the appropriate controller.
I search Google for this but didn't find anything. So my logical conclusion is that passing a request through a series of controllers might not be possible. But I'm not sure. So if it is possible, guide me how to do it. And if it is not possible, then suggest any other way to do it.
This sounds like a perfect example in which one or multiple before_action can be used. You can place a before_action in your ApplicationController:
# in app/controllers/application_controller.rb
private
def authorize_admin
render status: 401 unless current_user? && current_user.admin?
end
Then you can declare in any controller in which you want to run this method before running any action.
# in any controller - even the ApplicationController
before_action :authenticate
You can configure before_action to only run on certain conditions or with certain actions. Just a have a look at the how to use Filters in the Rails Guides.

Disable logged_in filter for precise URLs

I am quite new to Rails, so please let me know if I don't use the correct terms.
I am trying to create a website that lets people create their own elections. The latest version of the website is available here.
People creating the elections have to register. The elections are only viewable and editable by logged in members (You don't want anyone to be able to see your election).
For this reason, I have this at the beginning of my election controller :
class ElectionsController < ApplicationController
before_filter :logged_in?
Logged in users access the elections via this kind of url : http://spo2tu.be:8001/elections/:id
The person creating the election defines a list of voters that will have to vote for a candidate.
What happens is that for each voter, a unique url is generated and sent by mail. The URL are of form : http://spo2tu.be:8001/elections/:id/votes/:long_generated_id
Example : http://spo2tu.be:8001/elections/1/votes/Y2_8TDL8Hkpz2MzzV_bpgw
My issue is that due to the heritage of the before_filter, non logged_in users see an error when reaching this URL .
The error is the following :
undefined method `elections' for nil:NilClass
#election = current_user.elections.find(params[:election_id])
What I think happens is that since the election has a logged_in before filter, it is impossible to get a reference to it in a deeper URL.
Since those URLs are unique, temporary and generated, I would like to let the persons having access to them also access the elections; hence bypassing the before_filter.
What would be the proper way to achieve this?
One possible solution I see would be to avoid nesting and change the URL to be of kind http://spo2tu.be:8001/votes/Y2_8TDL8Hkpz2MzzV_bpgw directly, but that wouldn't solve the issue since voters still need to have access to the election to see the description.
Thanks for the help!
You should specify for with method you want to check if the user is logged in.
so change before_filter :logged_in? to before_filter :logged_in?, only: [:edit, :new, :destroy]
And you are probably using this method of logging in.
And this depends on a session being present for current_user method to work.
So I suggest you don't use this to look it up but you use a params in url to specify which user is trying to access the election.
Hope it helps. :)
So, after the suggestion from Ramon Gebben, the problem comes indeed from this line :
#election = current_user.elections.find(params[:election_id])
Since users are not logged_in, current_user is not defined, which lead to the error.
Changing this line to
#election = Election.find(params[:election_id])
solve the issue.

What's the absolute path to current request object in Rails environment?

I'm searching for the current request of class Rack::Request to find the params. Suppose I've spawned a debugger in my model, I don't want to send a new request, but still find my params.
I couldn't find any class attributes, that would store current request, which is reasonable.
I don't know how to find any instances of ApplicationController or Rack::Server, which might contain the info.
Also, peaking into the log is considered too much effort, so I'd like the effort to be concentrated on finding the request object, not telling me to grep/search through log.
In hopes of being able to be lazy,
Love Dzhon.
It's possible I'm misunderstanding your question, but from within a controller you can simply access a request object to get its details, and params to get the params.
ItemController
def show
#page_variable = request.inspect + params.inspect
end
end
If you want to make the request object available to your models you can create a class accessor and store it at the beginning of any action (via a before_filter in the application controller) for example. More details why here.

Trash implementation in rails app

In my app I'm trying to implement trash for some objects, i.e. there will be column "trashed" and it'll set to date, when the object was trashed. Trash also has an index page, where users can restore objects - set "trashed" to nil.
I found examples of models with method trash!, that set trashed to date and it was implemented with Concerns. But I don't really understand how to implement controllers with action to_trash? Is there any way to use Concerns with controllers too or every controller should have it's own action and route for calling it?
Now I implemented it with controller Trash, that have action move_to_trash and every controller use this action, but I have to add get params trashable_id and trashable_type to do this. Is it a good way to do things?
I think the simplest implementation could be to add to your routes.rb file the following:
match ':controller/:id/trash', :action => :trash
This will allow you to use the action trash on every controller. Have a look at the Routing Rails Guide for more examples.
A simple implementation is the following (taking the model Report as example). I don't use Concern here:
class ReportsController < ApplicationController
def trash
#report = Report.find(params[:id])
<Do the trashing of report here, you know that already.>
# Decide what to do after having called #trash
respond_to do |format|
format.html { redirect_to(reports_url) }
end
end
end
If you have only some controllers that should allow the action, it is perhaps easier to add special routes rules for each controller, instead of adding it to every one. And if you want to go a step beyond, you may implement the trash action in a mixin, and mix it in the controller you want to have it in (and no, I don't will implement that, too).

Resources