Rails 3 -- After Failed Sign In, Errors Do No Populate - ruby-on-rails

Edit: Hmm this is interested. I just noticed, my signup route is /signup. But, once I submit the information and the form returns invalid, I'm in the route /users .
I'm building a simple app to learn rails, and I've learned to set up an authentication system.
Today, I added a new plans table, to make different subscriptions for users. The Plan model has_many users, and users belong to plans. After implementing this, I see that if I enter invalid information, error messages do not show up in the view anymore.
I have the following code in the application.html.erb file for it show up -- >
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
It works fine on other notices, but it isn't showing the error messages.
One thing to also note, is that if I enter an email incorrectly for example, Rails shows me that it was the problem by highlighting it in red(edited CSS previously to do that). But, the error messages themeselves are nowhere to be found :P
Here's my Users controller :
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:show]
before_filter :correct_user, only: [:show]
def show
#user = User.find(params[:id])
end
def new
plan = Plan.find(params[:plan_id])
#user = plan.users.build
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def index
if current_user
redirect_to(user_path(current_user))
else
redirect_to(root_path)
end
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to login_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
end
I did some research, and people have been saying it might because of redirects, but I'm not sure If I'm doing that.
When first visiting the signup though, it's in the form of /signup?plan_id=1, to populate a hidden field with the plan_id in the signup form. When it shows the error screen, the plan_id is no longer there, which I assumed is okay since it already POSTed it. Does that have anything to do with it?

I think the problem is that the error messages you're expecting to see are errors on the User object, not stored in the flash. Try this in your view:
<% #user.errors.full_messages.each do |error_message| %>
<div class="alert"><%= error_message %></div>
<% end %>
See the docs for ActiveModel::Errors for more info.

Related

Login/signup function not working, #current_user not working, sessions not working

I'm new to Rails, and am working on a practice app that involves a simple login function. I've been following a tutorial from CodeAcademy by the books, however the code is not working in quite a few ways. First of all, the sessions do not set, even though Rails is executing the rest of the code inside the "if" block shared with the session declaration (btw, no errors are returned).
The session controller:
class SessionsController < ApplicationController
def new
end
def create
#user = User.find_by_username(params[:session][:name])
if #user && #user.authenticate(params[:session][:password])
session[:user_id] = #user.id
redirect_to '/posts'
else
session[:user_id] = nil
flash[:warning] = "Failed login- try again"
redirect_to '/login'
end
end
def destroy
session[:session_id] = nil
redirect_to login_path
end
end
Extrapolating from that issue, my "current_user" function is not working, which is most likely because the session is not being set.
The application controller:
class ApplicationController < ActionController::Base
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
def require_user
redirect_to '/login' unless current_user
end
end
Any help is much appreciated. Let me know if you need to see anything else.
NOTE: I know I should use Devise, and I am planning to in my future, more serious projects. However, like I said, this is a practice/test app to help develop my coding skills, and before using a "magical" gem like Devise I want to get hands-on experience with making a login system myself.
I think the error is that session_controller is not able to find the current_user.
Write the following code in application_controller:
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
def require_user
redirect_to '/login' unless current_user
end
end
Letme know if it works
There are a few possible problems.
First, #current_user is not set until the current_user method is called. And as #Neha pointed out, you'll need to add a helper method to your ApplicationController so that all your views will have access to the current_user method. Add this line to your ApplicationController:
helper_method :current_user
Now, to diagnose the problem, let's set something up that lets you get some visibility into your session and current_user.
First, in views/layouts/application.html.erb, just after the line that says <= yield %>, add this:
<%= render 'layouts/footer' %>
Then add a new file views/layouts/_footer.html.erb:
<hr/>
Session:<br/>
<% session.keys.each do |key| %>
<%= "#{key}: #{session[key]}" %><br/>
<% end %>
<br/>
User:<br/>
<%= current_user&.username || '[None]' %>
Now at the bottom of every view you can see the details of your session.
In your sessions#create action, you have a potential problem with finding your User. You are using params[:session][:name] where you probably should be using params[:session][:username].
Also, tangentially, the proper way to destroy a session is not by setting session[:id] to nil, but instead to use reset_session. So your SessionsController should look like this:
class SessionsController < ApplicationController
def new
end
def create
#user = User.find_by_username(params[:session][:username])
if #user && #user.authenticate(params[:session][:password])
session[:user_id] = #user.id
redirect_to '/posts'
else
session[:user_id] = nil
flash[:warning] = "Failed login- try again"
redirect_to '/login'
end
end
def destroy
reset_session
redirect_to login_path
end
end

Rails 4 - Admins editing another user's info

I want a simple system where users have three types (user, admin and medic) and the ones tagged Admin can "activate" other users' accounts so they can access certain privileges. I tried doing this with a simple button but I haven't found a way to do so.
The button for the code is:
<%= button_to "Change user Type to Medic", :method=> "activate_medic" %>
My activation method is as so:
def activate_medic
#user = User.find(params[:id])
#user.activated = true
if #user.save
flash[:info] = "Success"
end
end
And there's a post 'users/activate_medic' in my routes.rb file.
However, pressing the button brings up:
ActionController::RoutingError (No route matches [POST] "/users/1"):
If I'm trying to edit user 1.
Not exactly what I proposed in the original question, but:
I allowed the Admin users to completely edit a given user's info and by proxy their user type and permissions:
Users controller:
before_action :correct_user, only: [:edit, :update]
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user) || current_user.type == "Admin"
end
However, to avoid any user from just accessing their edit page and giving themselves admin rights, I edited the form to only allow Admins to see the field that edits their type:
<% if #user.type == "Admin" %>
<%= f.label :type, "User Type:" %>
<%= f.text_field :type, class: 'form-control' %>
<% end %>
The method option in link_to is meant to specify an HTTP verb, not the name of a custom method. Based on what you've provided, I would approach the problem like this:
button_to "Change user Type to Medic", activate_user_path(#user), method: :patch
And a dedicated controller:
class ActivateUserController
def update
#user = User.find(params[:id])
if #user.activate
flash[:info] = "Success"
end
redirect_to #user
end
end
And a route:
resources :users do
patch :activate, to: 'activate_user#update', as: :activate_user
end
And finally, move user behavior into the User model:
class User
def activate
self.update_attribute!(:active, true)
end
end

Storing Params in a Session with Nested Resource

I am using Rails 4.
I have subarticles nested into articles. I am storing all of the form data from subarticles in a session when a user needs to create an account before submission.
Here is what I am using (subarticles_controller):
def create
if current_user.nil?
session[:subarticle] = params
redirect_to new_user_session_path
end
Then after the user signs up, it creates the subarticle with the stored params using
if session[:subarticle].present?
#subarticle = current_user.subarticles.create(session[:subarticle]["subarticle"])
session[:subarticle] = nil
flash[:notice] = "Awesome, you are logged in and your answer is undergoing review."
edit_user_registration_path
end
I am having trouble, however, saving the article_id in which the subarticle is created under. Can someone point me in the right direction to doing this?
A better approach could be to save the (sub)articles created by guest users in the database.
class SubArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
# ...
def create
#subarticle = Article.new(article_params) do |a|
if current_user
a.user = current_user
else
a.token = SecureRandom.hex
end
end
if #subarticle.save
if #subarticle.user
redirect_to #subarticle
else
session[:after_sign_in_path] = edit_article_path(#subarticle, token: #subarticle.token)
redirect_to new_user_session_path, notice: 'Please sign in to finalize your article.'
end
else
render :new
end
end
def edit
if #subarticle.user.nil? && #subarticle.token != params[:token]
redirect_to root_path, alert: 'You are not authorized.'
end
flash[:notice] = 'Please press save again to publish your post.' unless #subarticle.user
render :edit
end
def update
# The #subarticle.token should be included in the edit form
unless #subarticle.user && #subarticle.token == params[:sub_article][:token]
# let the current user claim the article
#subarticle.user = current_user
end
if #subarticle.update(article_params)
redirect_to #subarticle
else
render :edit
end
end
private
def set_article
#subarticle = Article.find(params[:id])
end
def sub_article_params
params.require(:sub_article).permit(...)
end
end
So here we we instead give the user a link to the edit page for the article where he/she can "finish" the article after logging in.
Since a malicious user could potentially "steal" unclaimed articles by guessing the id and entering the edit url we add a random token which we store in the database with the article and add to the url. Not 100% foolproof but at least better.
To make this work you will also need to add a token field to your form:
<%= form_for(#subarticle) do |f| %>
...
<%= f.hidden_field :token %>
...
<% end %>
The reason you might want to consider this is because session storage is often memory based and if you have a large amount of traffic storing the entire params hash in the session will exhaust the server memory. Also you should reset the session before logging a user in or out to avoid session fixation.
We do have a few issues though - first we don't want to accumulate a bunch of "unclaimed" articles if the user (or a bot) never logs in. The easiest way to do this is to setup a cron job to delete articles over a certain age without an associated user.
You would also want to filter any articles without a user from you show/index action.

Rails app with Mongoid not saving on update

I'm making a Rails app that uses Mongoid and devise. Currently, the functionality's pretty basic, but it's not working like I expect it to. In particular, update isn't working at all.
I can create a user, but when I go back to update it, it doesn't raise any errors, but also doesn't save. This may be clearer with some code. I've got this in routes:
devise_for :users
resources :users
And this in the controller:
## WORKS PERFECTLY FOR SIGNING USERS UP, AND FLASHES CORRECTLY
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save!
flash[:success] = "Welcome!"
sign_in #user
redirect_to #user
else
render 'new'
end
end
## DOES NOT UPDATE THE USER RECORD, AND DOES NOT FLASH SUCCESS
## IT DOES, HOWEVER, REDIRECT TO SHOW, INSTEAD OF RENDERING EDIT
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.save!
flash[:success] = "Profile edited successfully!"
redirect_to #user
else
render 'edit'
end
end
So yeah. As the code suggests, going through my views to edit an existing user does NOT edit that user, nor does it give the flash saying the users was edited, but it DOES redirect correctly. I currently have NO user validations (though devise might have some) -- but changing all devise-relevant fields doesn't get around the issue, so I don't think it's a silent validation fail.
The form is of the basic
<%= form_for #user do |f| %>
<%= f.label: name %>
<%= f.text_field :name %>
...
<%= f.submit %>
<% end %>
sort.
Not sure what other code could be helpful here. Please let me know. I'm super stumped. Thanks!
you have to pass params you can use update_attributes or update_attributes!
#user = User.find(params[:id])
if #user.update_attributes!(params[:user])
...

How to set up acts_as_follower

I'm using the gem acts_as_follower in a rails app. I set it up and it works (In console), however I'm clueless as to how to set it up in a view.
I want to make a button correspond to the user.follow and user.stop_following methods.
The github doesn't explain this. Help please.
You can create controller actions that you link to. For example in an app I have the following two actions added to a user controller. Once the routes are also setup I use the url helpers to link to the actions from my view, and end up displaying the flash messages via javascript callbacks.
UsersController:
def follow
#user = User.find(params[:id])
if current_user
if current_user == #user
flash[:error] = "You cannot follow yourself."
else
current_user.follow(#user)
RecommenderMailer.new_follower(#user).deliver if #user.notify_new_follower
flash[:notice] = "You are now following #{#user.monniker}."
end
else
flash[:error] = "You must <a href='/users/sign_in'>login</a> to follow #{#user.monniker}.".html_safe
end
end
def unfollow
#user = User.find(params[:id])
if current_user
current_user.stop_following(#user)
flash[:notice] = "You are no longer following #{#user.monniker}."
else
flash[:error] = "You must <a href='/users/sign_in'>login</a> to unfollow #{#user.monniker}.".html_safe
end
end
config/route.rb:
resources :users do
member do
get :follow
get :unfollow
end
end
Then in your view you can use the url helper to link to the controller action:
<%= link_to "Unfollow", unfollow_user_path(#user) %>

Resources