Ruby, before_action confusion - ruby-on-rails

In ruby, I'm trying to prevent users from adding someone as a friend if either one of them has the other on their ignore list. In the friendships controller, I'm trying to define
class FriendshipsController < ApplicationController
before_action :ignored_user, only: :create
And
def ignored_user
if current_user.foes.include?(#user)
flash[:notice] = "Unable to send friend request."
redirect_to :back
end
end
In this context, current_user is the user who is logged in, #user is the user whose profile they're viewing, and foes means "either someone they're choosing to ignore, or someone who's ignoring them." All of that seems to work except the "if current_user blah blah" line. If I replace it with "if current_user.admin?" then it works exactly as you'd expect. If that user is an admin, they can't send anyone friend requests. If I replace it with "if current_user.id == 2", it also works as you'd expect. I've tried replacing the faulty line with many, many variations of the above code, including things like:
if Disagreement.where(foe_id: [#user, params[:id]], user_id: [current_user, params[:id]]).first
which works elsewhere, just not in the friendships controller.
As for error messages: there are none. My attempts always either end with the friend being added when they shouldn't be, or all friend requests being blocked, including friend requests to users who aren't ignored. Any help with this would be greatly appreciated.

A much smarter person than myself gave me the solution. I completely removed the "before_action :ignored_user, only: :create" part, as well as the "def ignored_user" section. All of that has been replaced with this:
class FriendshipsController < ApplicationController
def create
#user = User.find(params[:friend_id])
if current_user.foes.include?(#user)
flash[:notice] = "Unable to send friend request. You are ignoring this user or vice versa."
redirect_to :back
puts "#user is: #{#user}" #This line only existed for the sake of figuring out the problem
puts "#user.id is: #{#user.id}" #Same
puts "current_user.foes are: #{current_user.foes}" #Same
else
#friendship = current_user.friendships.build(friend_id: params[:friend_id])
if #friendship.save
flash[:notice] = "Friend request sent!"
redirect_to :back
end
end
end
The problem was that the friendships controller couldn't recognize #user in any of the ways it's normally defined, due to the params[:id] part. Because that's rewritten for use in the friendships controller, the controller assumes that by "id," you mean the id of some friendship, not a user's id. The person who gave me the solution realized that you could just replace ":id" with ":friend_id" since, in the context of this action, those two IDs will always be synonymous, since you'll always be adding people from their own profile page. So I never figured out how to directly reference a user's ID from within a different controller, but I've learned the next best thing.

Related

Rails 4 - Couldn't find User without an ID

I'm new to rails, so any explanation & advise would much appreciated.
i have a webpage in which i would like any user to view that page not just the current_user, but i am unsure how to correctly define the instance variable #user in my controller
in my static_pages_controller.rb i have the below action recruiterpg
static_pages_controller.rb
def recruiterpg
#user = User.find(params[:user_id])
#adverts = #user.adverts
#applications = #user.forms
end
in my controller, i have defined user as #user = User.find(params[:user_id]) but this breaks my code in the views; views/static_pages/recruiterpg.html.erb
when i define user as #user = current_user my code in the views works perfectly fine
what am trying to do is: for my views, the recruiterpg.html.erb, i would like
any user to be able to view the page not only the current_user to
view the page. Could one kindly advise me and explain to me how to
define #user correctly in my status_pages_controller.rb. i also
tried #user = User.find(params[:id]) but my code still breaks in the
views - i get the error message
Couldn't find User without an ID
You need to make sure you are passing a user_id to the recruiterpg action. For example, try this url in your browser (set user_id to a known id in the users table):
http://localhost:3000/dashboard?user_id=1
A suggested modification to your action:
def recruiterpg
#user = User.find params.require(:user_id)
#adverts = #user.adverts
#applications = #user.forms
end
If params[:user_id] isn't defined, you want to find a way to make visible what is being defined.
If you throw the following statements into your controller...
def recruiterpg
...
puts params
...
end
...you should see something like the following get spit out in your console when you load the page...
{"controller"=>"static_pages", "action"=>"recruiterpg", "id"=>"49"}
Take a look at the Rails guide for parameters. They can get defined in one of three ways.
One: As a query string similar to Sean's answer above.
Two: Routing parameters. See section 4.3 in the Rails guide. In your case, that would mean you should have something like the following in routes.rb:
get '/dashboard/:user_id' => 'staticpages#recruiterpg'
Note that there's nothing magic about :user_id in that string.
Three: From a form which it doesn't seem like applies here, since a user isn't submitting data.
Since you're new, here is some information for you:
User Story
Firstly, the best way to resolve errors is to identify your user story.
A "user story" is a software principle in which you put the "user's perspective" first -- explaining how the software should work in conditions defined from how the user engages with it.
One of the main issues you have with your question is your user story is very weak; it's hard to decifer what you're trying to achieve.
Next time you ask a question, you should try your hardest to describe how the user should see your app, before providing code snippets :)
Controller
Your main issue is an antipattern.
An antipattern is basically a "hack" which will likely break another part of your app in future. Kind of like duct tape for software):
#app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def recruiterpg
#user = User.find(params[:user_id])
#adverts = #user.adverts
#applications = #user.forms
end
end
So you're showing a static page but yet you want to populate it with data?
Hmm...
What you should be doing is something like the following:
#config/routes.rb
resources :users do
resources :recruiters, only: :index #-> url.com/users/:user_id/recruiters
end
#app/controllers/recruiters_controller.rb
class RecruitersController < ApplicationController
def index
#user = User.find params[:user_id]
#adverts = #user.adverts
#applications = #user.forms
end
end
This will allow you to populate the following view:
#app/views/recruiters/index.html.erb
<%= #adverts %>
--
It's important to note the structure of the controller / routes here.
The issue you have is that you're calling a "static page" and expecting to have params available to find a User. This can only happen if you have params available...
Params
Rails is not magic, and as such if you want to look up a user, you have to provide the parameters to do so.
This is why you're able to look up current_user -- the params are already set for this user.
As such, you'll need to use something called nested routes in order to attain a user ID other than that of current_user:
#config/routes.rb
resources :users do
resources :recruiters #-> url.com/users/:user_id/recruiters
end

Rails / Devise: Gracefully ask user to sign in on signed-out create

I'm continuing to tweak the Rails Getting Started project to get the authentication behavior I want.
I want to control what is allowed and not allowed at the level of specific actions rather than for a whole controller. For example, you don't need to be signed in to view posts (index / show), but you must be signed in to access the form to submit a new post (new) and to get a submitted post processed (create).
Since I would like people to be redirected to sign-in if they're not signed-in, and I'll be using that snippet over and over again in a million places, I put this in the application controller:
def authcheck
unless user_signed_in?
redirect_to new_user_session_path
end
end
For new posts, this seems to work:
def new
authcheck #see application controller
#post = Post.new
end
But for the case where I have two tabs open and I have the first one on the new post form, but I log out on the second one, then try to submit the post on the first form, I get an error about the user being null even though it seems to me like it should have been redirected:
def create
authcheck
#when not signed in, causes error "undefined method 'posts' for nil:NilClass"
#post = current_user.posts.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
Actually, I was getting an exception page originally but I changed exception to null_session in the application controller's protect_from_forgery with: :exception line.
Basically: Why isn't it redirecting to the sign-in page like it was when it was just about showing the form for a new post? And, from there, what might you suggest I should do about it?
I think I was labouring under a false impression. I thought that a redirect_to was the end of execution. But it doesn't seem to work that way - after a redirect line I could, for instance, tie up the server in an attempt to square the circle, even if it's not tied to spitting out a webpage.
I modified my "authcheck" to return true or false, then put everything in if blocks. The first example didn't fail the old way because it was just instantiating a new post but it didn't matter if it was actually saved or not.
Change to application controller routine:
def authcheck
unless user_signed_in?
redirect_to new_user_session_path
return false
end
return true
end
Changes to Posts controller routines:
def new
if authcheck #see application controller
#post = Post.new
end
end
def create
if authcheck
#post = current_user.posts.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
end
I suspect I'm not really doing things the accepted way, so I'll hold out a few days before accepting my own answer. :-)

how to prevent a DELETE HTTP request from succeeding in this situation?

Rails beginner here..
I have a users resource where I implemented a callback that's supposed to prevent an admin user from deleting herself.
before_filter :admin_no_delete, only: :destroy
def admin_no_delete
admin_id = current_user.id if current_user.admin?
redirect_to root_path if params[:id] == admin_id
end
If this looks familiar to some, it's from Michael Hartl's rails tutorial, exercise #10 here but I tried to do it differently, not as he suggested.
My (lame) test for this fails
describe "deleting herself should not be permitted" do
before do
delete user_path(admin)
end
it { should_not redirect_to(users_path) }
end
But exposing a delete link for the admin user just to test and clicking on that link, it seems like the callback actually succeeds in executing (redirecting to root_path).
I was able to invoke the destroy action using jQuery to delete the record being protected by the callback (using Web Inspector's javascript console):
$.ajax({url: 'http://localhost:3000/users/104', type: 'DELETE', success: function(result){alert(result)} })
Looking for ideas on how to prevent a DELETE HTTP request from succeeding in this situation.. also any ideas on how to properly test for this kind of situation?
Thanks.
Simple: params[:id] is a string, while admin_id is a Fixnum. You can just change it as follows and it should work:
redirect_to root_path if params[:id].to_i == admin_id
The logic you're using seems a little odd to me, though. Why use a before filter if it's just for one action, and why change the redirect? I think the logic should be directly in the destroy action and look something like this:
def destroy
unless current_user.admin? && current_user.id == params[:id].to_i
User.find(params[:id]).destroy
flash[:success] = "User destroyed."
end
redirect_to users_path
end
You're comparing admin_id, an integer with params[:id]. Values in params are always strings (or arrays/hashes containing more strings) so the comparison will always fail.

Post Redirect Get pattern in Rails

How can I implement PRG in Rails?
I used PRG in Rails, but I am not totally convinced it's right. I was wondering is there any better way to handle it in Rails?
I don't know how popular PRG pattern is and why one has to religiously stick to the "redirect" on failure aspect of it (actually, one good reason is sometimes you dont want to deal with the "setup" complexity at create failure and keep things dry).
What you basically need is to transfer the params for :user to new. I think #Hitesh's solution above is quite close.
class UsersController < ApplicationController
def new
if flash[:user_params]
#user = User.new(flash[:user_params])
#user.valid?
else
#user = User.new
end
end
def create
#user = User.new(params[:user])
if #user.save
# clears previously stored user if there is any
flash[:notice] = "User created."
redirect_to '/'
else
flash[:error] = "Error saving User"
flash[:user_params] = params[:user]
redirect_to :action => :new
end
end
end
Use the session, Luke
The way you implemented it in your blog post is quite fine, however you may want to use session instead of flash to store your #user and optionally use the ActiveRecord session store to keep cookies from getting bloated.
From ActionController::Base documentation
ActiveRecord::SessionStore - Sessions are stored in your database, which works better than PStore with multiple app servers and, unlike CookieStore, hides your session contents from the user. To use ActiveRecord::SessionStore, set
config.action_controller.session_store = :active_record_store
in your config/environment.rb and run rake db:sessions:create.
So you should…
class UsersController < ApplicationController
def new
#user = session[:user] || User.new
end
def create
#user = User.new(params[:user])
if #user.save
# clears previously stored user if there is any
session[:user] = nil
redirect_to '/'
else
session[:user] = #user
redirect_to :action => :new
end
end
end
I'm no expert in these matters, but this looks good. From what I understand flash is a part of the session. So the answers telling you to switch to session seem a bit misguided. In this case you want the data to be cleared after the redirect. Other than shoving it in the session, I'm not sure where you would put it.
As far as your cookie size increasing, well, the default session provider for Rails is a cookie in Rails 3. You could swap the session provider out if you wanted to keep the data server side. It is encrypted though, so you are probably okay with the data in the cookie, unless size is an issue.
use below code
class UsersController < ApplicationController
def new
#user = User.new(session[:user_param])
session[:user_param]=nil
end
def create
#user = User.new(params[:user])
if #user.save
# clears previously stored user if there is any
flash.discard(:user)
redirect_to '/'
else
session[:user_param] = #user
redirect_to :action => :new
end
end
end
It is true, though, that you should not do redirect_to '/'. You should define root in your routes file and then do redirect_to root_path.
Edit: Oops, that was supposed to be a comment to SpyrosP's answer.
Also: Here is some excellence guidance on flash. Particularly this may ease your mind:
The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc.
The interesting things there is that, yes it is a part of the session, so answers to "use the session instead of flash" are misguided, as Justin Etheredge's answer already put it. The other thing is that it says it is useful for storing messages instead of only for storing messages. With the added "etc" it would lead me to believe that it is within the intended usage to store user information in there as well.
One last thing, I would agree with Aditya Sanghi that you should just store the user parameters and not an entire user object in the flash.
I didn't read the question properly.
The validation failure you have necessitates going to a different page where a different process will occur. You tried to update a domain object, it doesn't exist. The usual response to a validation failure is to re-render the page, but you need to go to the create page.
The flash hash seems wrong for this. I'd agree with the idea of stuffing your entered data into the session and redirecting.

How do you prevent flash[:notice] from being rendered twice when redirecting from ApplicationController

I have a simple method in the ApplicationController that, when called, may set a 'flash[:notice]' then redirect to the root_url.
The problem is that even though that method is only called once, the root URL renders that flash[:notice] TWICE.
Here's a the method (which is a before_filter used in other controllers, and is defined in the ApplicationController) :
def authenticate
if params[:id].try(:size) == 40
company = Company.find_by_hash_identifier(params[:id])
if company
session[:editable_companies] ||= []
session[:editable_companies] << company.id
session[:editable_companies].compact!.uniq!
end
end
unless session[:editable_companies].try('&', [company.try(:id), params[:id]])
flash[:notice]= "You are not permitted to edit this company.<br />Please check the URL from the email we sent you, and try again."
flash.keep[:notice]
redirect_to root_url and return
end
end
In the root_url view, I get two flashes like so:
You are not permitted to edit this company.You are not permitted to edit this company.
Get rid of the flash.keep(:notice) line.
You don't (at least shouldn't) need to call flash.keep(:notice) to store the flash across a redirect. A value in the flash hash only gets auto-deleted on a render.
Turns out it was a problem in the view. :-(
I actually had flash[:notice] twice.

Resources