I have a CRUD users controller. When I open the "user edit" page in the browser, my log shows this:
Started GET "/users/1/edit" for 127.0.0.1 at 2011-06-21 20:09:37 +0200
Processing by UsersController#edit as HTML
Parameters: {"id"=>"1"}
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE
`users`.`id` = ? LIMIT 1 [["id", 1]]
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE
`users`.`id` = ? LIMIT 1 [["id", "1"]]
In the edit action, I simply call a private function user, which returns
@user ||= User.find(params[:id])
The view looks as follows:
<%= settings_title(@user.username) %>
<%= form_for @user, :html => { :multipart => true } do |f| %>
<%= render "form", :user => @user
<div class="action"><%= submit_tag t("users.edit.submit"), :class => "button" %></div>
<%= end %>
The route is defined as resources :users do ...
Any idea how to prevent the second db access would be greatly appreciated!
Update:
It seems like the second DB SELECT can be prevented by calling
@user ||= User.find(params[:id].to_i) # notice the .to_i
in the edit action. I now get:
User Load (0.1ms) SELECT `users`.* FROM `开发者_Python百科users` WHERE `users`.`id` = ? LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = ? LIMIT 1
but is this the proper way to do it? Do you see any other side-effects of this solution?
Your #to_i workaround notwithstanding, if current_user is an admin and can edit any user record, then it would seem this is the correct behavior. It's just a coincidence that in this case current_user == user_to_be_edited and you're getting two db hits for the same data. In all the other cases where the current_user is editing someone else's user data, you will have to hit the database twice by necessity.
However, if current_user only ever edits his/her own data, then in your controller instead of:
@user ||= User.find(params[:id])
you would use:
@user ||= current_user
...under the assumption that user authentication has already occurred prior to getting to the action. In this manner, you will only have the one hit on the database that happens in authentication.
As a final note, in the former case, where a current_user admin can edit any user, if you really want to get rid of that one coincidental edge case where the database gets hit twice, you can do this:
@user ||= current_user.id == params[:id].to_i ? current_user : User.find(params[:id])
In this manner, you'll avoid the extra db hit when a user is editing his/her own data.
After some more tests I found out the reason for the 2 SELECTS:
- for accessing user through
params[:id]
in the edit action - for accessing current_user (for authorization) through
session[:user_id]
The former is executed with a string, whereas the latter is executed with an integer. I understand that for the simple cache that comes with Rails as default, the queries have to be exactly the same, so this explains the dual SELECT.
However, following Rails logic, I would now have to read the current user from the session with session[:user_id].to_s
, which seems cumbersome to me.
As a result, I might have to switch to a more intelligent cache. Thanks for all your support and comments.
精彩评论