Relation randomly raises ActiveRecord::RecordNotFound - ruby-on-rails

Once in a while when trying to fetch an order record for a particular user, a ActiveRecord::RecordNotFound is raised.
Some things to note here.
The error is raised when visiting /orders/:id, but not for all users. We track completed orders (meaning that you end up on a orders page) and around 50% gets a 404. Note that we're talking about 50% of the users, not the requests. If it displays 404 once for an order for a particular user, it will always display a 404.
The record exists as it can be accessed via the console using the same data that's being logged in the controller.
The problem disappears when re-deploying the application.
What could the problem be?
I'm running rails 4.2.0.
class OrdersController < ApplicationController
#
# GET /orders/:id
#
def show
Rails.logger.info "current_user=#{current_user.id}, params[:id]=#{params[:id]}"
#order = current_user.orders.find(params[:id])
end
end
class ApplicationController < ActionController::Base
def current_user
#_current_user ||= User.find_by_id(cookies.signed[:uid])
end
end
class User < ActiveRecord::Base
has_many :subscriptions
has_many :orders, through: :subscriptions
end
class Order < ActiveRecord::Base
has_one :user, through: :subscription
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
Here's the log output
[User#id=2454266] Parameters: {"id"=>"1553"}
[User#id=2454266] current_user=2454266, params[:id]=1553 <==== Rails.logger.info
[User#id=2454266] Completed 404 Not Found in 240ms
[User#id=2454266]
ActiveRecord::RecordNotFound (Couldn't find Order with 'id'=1553):
app/controllers/orders_controller.rb:6:in `show'
Running User.find(2454266).orders.find(1553) in the console works.
Also note that it's possible to skip the relation and go directly to the order model, like this
class OrdersController < ApplicationController
#
# GET /orders/:id
#
def show
#order = Order.find(params[:id])
end
end

The way we arrived at the conclusion can be found by going through the comments list.
Our summary of findings are:
Either orders are cached, or current_user is outdated (along with cached associations)
Going straight to order works (i.e. Order.find / User.find(current_user.id).orders...
Solution that we arrived at is:
current_user.reload!
Before performing
current_user.orders.find(params[:id])

class ApplicationController < ActionController::Base
def current_user
#_current_user ||= User.find_by_id(cookies.signed[:uid])
end
def user_signed_in?
#_current_user.present?
end
end
class OrdersController < ApplicationController
def show
if user_signed_in?
#order = current_user.orders.find(params[:id])
else
return redirect_to <404 page or any>, alert: 'Order not found'
end
end
end
I think you have to check the presence of the user first, then you can trace the bug
I usually use this method when I got some issue
def show
if user_signed_in?
begin
#order = current_user.orders.find(params[:id])
rescue => e
binding.pry
end
else
return redirect_to <404 page or any>, alert: 'Order not found'
end
end
but you need 'rails-pry' gem, the advantage is rails will fire up rails console when there is exception

Related

How to respond with 403 (forbidden) for index action on nested resource?

Suppose we have the following setup in a ruby-on-rails (API) application:
class User < ActiveRecord::Base
has_many :posts
has_many :friends, class_name: User # Via a joins table....
end
class Post
belongs_to :user
end
When visiting /users/:id/posts, I want the logged-in user to only be able to view this data, if they are friends.
The standard implementation for this in Pundit is to use a policy scope:
class PostsController < ApplicationController
before_action :authenticate
def index
posts = policy_scope(Post.where(user_id: params[:user_id]))
render posts
end
end
class PostsPolicy < ApplicationPolicy
class Scope < ApplicationPolicy::Scope
def resolve
scope.where(user: user.friends)
end
end
end
This will prevent a user from seeing non-friends' posts. However, it produces an API response of 200 Success (with an empty response body), not 403 Forbidden - which would be preferable for the FrontEnd to receive, and display an appropriate error message.
Here's one solution that does not work:
class PostsController < ApplicationController
before_action :authenticate
def index
posts = policy_scope(Post.where(user_id: params[:user_id]))
authorize posts # <- !!!!
render posts
end
end
class PostsPolicy
def index?
record.all? { |post| user.friends_with?(post.user) }
end
end
Not only is this very inefficient, but if the user doesn't have any posts, then you'll always get a 200 Success response - which is still not ideal.
Similarly, it's not ideal to "return 403 if the response is empty" - because then you'd get error messages when viewing friends' posts, if they don't have any!
Here's a possible solution, but it feels wrong...
class PostsController < ApplicationController
before_action :authenticate
def index
user = user.find(params[:user_id])
authorize(user, :index_posts?) # <-- !!!!
posts = policy_scope(Post.where(user: user))
render posts
end
end
class UsersPolicy
def index_posts?
user.friends_with?(record)
end
end
(You could also use a more generic method name like UserPolicy#friends?, to the same affect.)
This works, but it feels like a mis-use of Pundit to be applying a UserPolicy method when authorising a Post resource!
Pundit does not allow passing additional arguments to policies. This has been a highly requested feature over the years. In particular, see this highly-relevant PR/discussion. In other words, what I'd like to be able to do is this:
class PostsController < ApplicationController
before_action :authenticate
def index
user = User.find(params[:user_id])
posts = policy_scope(Post.where(user: user))
authorize(posts, user) # <- !!!! (not valid in Pundit)
render posts
end
end
class PostsPolicy
def index?(other_user) # <- !!!! (not valid in Pundit)
user.friends_with?(other_user)
end
end
However, the feature was eventually conclusively rejected by the project maintainer in favour of using "name-spaced policies" and "form objects".
Hopefully this question is not too "opinionated", but what would you suggest? Is there a clean way to use the Pundit library whilst responding with appropriate 200 vs 403 appropriately?
Or, is there a good patch/fork/alternative I could use (preferably easy to migrate to, for a large project) that will better support my desired behaviour?

Rails accepts_nested_attributes_for always creates the nested models, but does not update them

Given the following:
class WebsitesController < ApplicationController
# POST /websites/save
# POST /websites/save.json
def save
Website.exists?(name: params[:website][:name]) ? update : create
end
# POST /websites
# POST /websites.json
def create
#server = Server.find_or_create_by_name(params[:server_id])
#website = #server.websites.new(params[:website])
#etc... #website.save
end
# PUT /websites/1
# PUT /websites/1.json
def update
#website = Website.find_by_name(params[:website][:name])
#etc... #website.update_attributes
end
end
The client does not have any IDs of these models
The request that gets sent only has the names, but not the ids.
And the following models
class Website < ActiveRecord::Base
serialize :website_errors
attr_accessible :plugins_attributes
has_many :plugins
accepts_nested_attributes_for :plugins
end
class Plugin < ActiveRecord::Base
belongs_to :website
end
When I make a POST request to /websites/save.json, the Website gets updated correctly if it exists, but the Plugins that belong to it always get recreated causing duplicate content in the Database. Why does this happen? I redirect to the update action which calls update_attributes so how can it be that it does not update it? I take it that it's because no ID is given with the request.
Can I make the Controller listen to plugin_name instead of plugin_id?
Modify your controller to have this:
def update
#website = Website.find_by_name(params[:website][:name])
if #website.update(params)
redirect_to website_path(#website)
else
render :edit
end
end
Also, if you're using strong_parameters, you'll need this at the bottom of your controller:
params.require(:website).
permit(
:name,
...,
plugins_attributes: [
:name,
...,
]
)
end

Why it happen error that could not find user happen when I destroy user from grouping table

all I'm new be to rails.
I want to destroy user that have group_id in grouping table.
My source code is here.
GroupsController
def leave
#user = current_user
#group = Group.find(params[:id])
#user.groupings.find_by_group_id(#group).destroy
redirect_to :back , notice: "Destroy!"
end
lists.html.haml
%td= link_to("Destroy",leave_group_path(:id => group),:confirm => "Are you sure",:method => :delete)
When I click "Destroy" button , then it happen error that Couldn't find User with id=1
I want to destroy only grouping table, but why can't find current_user.I don't remove current_user.
Error logs is that
app/controllers/application_controller.rb:6:in `current_user'
And current_user is provided helper_method of OmniAuth Plugin.
current_user is implemented following this ..
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
Please some help.
Thanks in advance.
If there is an error that says could not find user with id=1 then there is not user with id=1.
Ofte we delete/destroy the user.
In rails when you destroy the record with id = 1 and create another record then the newly created records id will not be 1. This could have happened the case with you.
Also, your association in models is this, i am guessing ::
User Model
class User < ActiveRecord::Base
has_many :groups # not groupings
end
Group model #as mentioned in the question.
class Group < ActiveRecord::Base
belongs_to :user
end
This means ::
#user.groupings.find_by_group_id(#group).destroy could not possible be correct.
Instead try this ::
#user.groups.find_by_group_id(#group).destroy
Since, a user has_many groups and not groupings.
Read More about naming conventions

Rails 3 Checking a relationship count

I have a User & Profile Models. A user has_one profile IE.
class User < ActiveRecord::Base
has_secure_password
# Relationships
has_one :profile
class Profile < ActiveRecord::Base
# Relationships
belongs_to :user
I am then trying to test to see if the user has a profile. If not redirect them to the profile controller ie.
class User::BaseController < ApplicationController
# Filters
before_filter :check_for_profile
# Layout
layout "backend"
# Helpers
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def check_for_profile
if current_user.profile.empty?
redirect_to new_user_profile_path, :notice => "Please create a profile first."
end
end
end
No matter what I try I'm getting the error.
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.empty?
I'm pretty sure my relationships are right. Can anyone shed some light on what I'm doing wrong ?
Thank you in advance.
Lee
try profile.blank? instead. empty? is not defined for nil.
Check out the "blank?" method at the following link. The "present?" method should also be considered - they're basically the same.
http://api.rubyonrails.org/classes/Object.html#method-i-blank-3F

Access current_user in model

I have 3 tables
items (columns are: name , type)
history(columns are: date, username, item_id)
user(username, password)
When a user say "ABC" logs in and creates a new item, a history record gets created with the following after_create filter.
How to assign this username ‘ABC’ to the username field in history table through this filter.
class Item < ActiveRecord::Base
has_many :histories
after_create :update_history
def update_history
histories.create(:date=>Time.now, username=> ?)
end
end
My login method in session_controller
def login
if request.post?
user=User.authenticate(params[:username])
if user
session[:user_id] =user.id
redirect_to( :action=>'home')
flash[:message] = "Successfully logged in "
else
flash[:notice] = "Incorrect user/password combination"
redirect_to(:action=>"login")
end
end
end
I am not using any authentication plugin. I would appreciate if someone could tell me how to achieve this without using plugin(like userstamp etc.) if possible.
Rails 5
Declare a module
module Current
thread_mattr_accessor :user
end
Assign the current user
class ApplicationController < ActionController::Base
around_action :set_current_user
def set_current_user
Current.user = current_user
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.user = nil
end
end
Now you can refer to the current user as Current.user
Documentation about thread_mattr_accessor
Rails 3,4
It is not a common practice to access the current_user within a model. That being said, here is a solution:
class User < ActiveRecord::Base
def self.current
Thread.current[:current_user]
end
def self.current=(usr)
Thread.current[:current_user] = usr
end
end
Set the current_user attribute in a around_filter of ApplicationController.
class ApplicationController < ActionController::Base
around_filter :set_current_user
def set_current_user
User.current = User.find_by_id(session[:user_id])
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
User.current = nil
end
end
Set the current_user after successful authentication:
def login
if User.current=User.authenticate(params[:username], params[:password])
session[:user_id] = User.current.id
flash[:message] = "Successfully logged in "
redirect_to( :action=>'home')
else
flash[:notice] = "Incorrect user/password combination"
redirect_to(:action=>"login")
end
end
Finally, refer to the current_user in update_history of Item.
class Item < ActiveRecord::Base
has_many :histories
after_create :update_history
def update_history
histories.create(:date=>Time.now, :username=> User.current.username)
end
end
The Controller should tell the model instance
Working with the database is the model's job. Handling web requests, including knowing the user for the current request, is the controller's job.
Therefore, if a model instance needs to know the current user, a controller should tell it.
def create
#item = Item.new
#item.current_user = current_user # or whatever your controller method is
...
end
This assumes that Item has an attr_accessor for current_user.
The Rails 5.2 approach for having global access to the user and other attributes is CurrentAttributes.
If the user creates an item, shouldn't the item have a belongs_to :user clause? This would allow you in your after_update to do
History.create :username => self.user.username
You could write an around_filter in ApplicationController
around_filter :apply_scope
def apply_scope
Document.where(:user_id => current_user.id).scoping do
yield
end
This can be done easily in few steps by implementing Thread.
Step 1:
class User < ApplicationRecord
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
end
Step 2:
class ApplicationController < ActionController::Base
before_filter :set_current_user
def set_current_user
User.current = current_user
end
end
Now you can easily get current user as User.current
The Thread trick isn't threadsafe, ironically.
My solution was to walk the stack backwards looking for a frame that responds to current_user. If none is found it returns nil. Example:
def find_current_user
(1..Kernel.caller.length).each do |n|
RubyVM::DebugInspector.open do |i|
current_user = eval "current_user rescue nil", i.frame_binding(n)
return current_user unless current_user.nil?
end
end
return nil
end
It could be made more robust by confirming the expected return type, and possibly by confirming owner of the frame is a type of controller...

Resources