1 - My controller is this , but this not call the view whit this name is login.html.erb, im not understand beacause what happen this , is to show the form of the login.
class FinancesController < ApplicationController
# GET /finances
# GET /finances.json
def login
#user = User.find_by_name(params[:user])
if #user
session[:user_id] = #user.id
redirect_to :action => 'index'
else
redirect_to login_url
end
end
def index
#finances = Finance.all(:order => "created_at")
respond_to do |format|
format.html # index.html.erb
format.json { render json: #finances }
end
end
2 - Someone can talk to me to use the authenticate method to validate a sample login whit user and password forms some, can show to me how use this method , the find_by_name_and_password dont works.
I thanks by the answers, many.
Routes.rb
Controle::Application.routes.draw do
match 'login' => 'finances#login'
resources :finances
end
check for the #user object.
because, as of your code it will redirect to your login method if #user is not available.
To render login.html.erb from login method, there is no need for any manual redirections.
Related
I'm building an events platform with Devise gem & Rails 6.1.
I'm having trouble with a small RSVP functionality.
When a non-login user click "Attend" button, it redirect the user to sign-in and once this is done the user is redirect back to the previous page. HOWEVER user has to click again "Attend" button.
I want the user redirect back and ALSO complete the create action ( POST action )
I managed to do the redirect work fine, but the user would have to click again on the "Attend" button which I do not want. I've searched the entire internet but none of the solutions worked.
The closet solution I've found is from this blog https://blog.justinthiele.com/retaining-form-data-through-a-login-process-a/ which I tried to apply on my app but without success.
I would think that something is missing in the after_sign_in_path_for method.
Any help would be appreciated.
UPDATE!
After debugging, I changed a bit the way I'm overriding Devise.
I feel I'm very close to the solution but can't figure it out what I'm missing.
attendee_controller.rb
def create
if !current_user
# Store the data in the session so we can retrieve it after login
session[:attendee] = params
# Redirect the user to register/login
redirect_to user_session_path
else
#attendee = current_user.attendees.build({event_id: params[:id]})
respond_to do |format|
AttendeeMailer.with(event: #attendee).notify_event_creator.deliver_now
if #attendee.save
format.html { redirect_to #attendee.event, notice: "You have successfully registered for the event!" }
format.json { render :show, status: :created, location: #attendee }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #attendee.errors, status: :unprocessable_entity }
end
end
end
end
app\controllers\users\sessions_controller.rb (user_session_path)
protected
def after_sign_in_path_for(resource_or_scope)
if session[:attendee].present?
new_attributes = session[:attendee]
previous_url = session[:previous_url] #defined in the show method from the events controller.
#attendee = current_user.attendees.create({event_id: new_attributes[:id]})
#clear session
session[:attendee] = nil
session[:previous_url] = nil
previous_url
else
#if there is not temp attendee in the session proceed as normal
super
end
end
events_controller.rb
def show
session[:previous_url] = request.fullpath
end
routes.rb
devise_for :users, controllers: { sessions: "users/sessions" }
resources :events do
post :attendee, to: 'attendees#create', on: :member
post :unattend, to: 'attendees#destroy', on: :member
end
In Views, views\events\show.html.erb
<%= button_to 'Attend Now', attendee_event_path(#event), class:"btn btn-success btn-lg" %>
Found the solution !
There is actually a gem called repost, that helps you redirect to a POST action. I implemented it in my solution.
Here is the whole technique I applied. To redirect after sign-in and then complete the POST action when the login was successful:
First, I create a session session[:previous_url] for later on to be used to redirect back here.
def show
session[:previous_url] = request.fullpath #url for user to come back after signing in
...
attendee_controller.rb : we create session[:attendee]
def create
if !current_user
session[:attendee] = true
redirect_to user_session_path
else
...
app\controllers\users\sessions_controller.rb (user_session_path)
protected
def after_sign_in_path_for(resource_or_scope)
if session[:attendee]
previous_url = session[:previous_url]
session[:previous_url] = nil #clear session
previous_url #going back to event page
else
super
end
end
More info into after_sign_in_path_for method here in the Devise How-To Wiki.
previous_url will bring us back to show view from the events.
Here is where the gem will come save the day :
app\controllers\events_controller.rb
before_action :attendee?, only: :show
private
def attendee?
if session[:attendee]
session[:attendee] = nil
repost(attendee_event_path, options: {authenticity_token: :auto})
end
end
This is the gem template :
repost('url' , params: {}, options: {})
Here is the gem link: https://github.com/vergilet/repost
I get the following error, Couldn't find User with 'id'=
I have this in my Users_Controller,
def edit
#user = #signed_in_user
end
This is in my routes.rb,
root 'welcome#welcome'
get 'login' => 'sessions#login', :as => :login
get 'profile' => 'users#profile', :as => :profile
post 'logging/user' => 'sessions#create'
get 'logout' => 'sessions#destroy', :as => :logout
get 'about' => 'about'
resources :users
get 'register' => 'users#new', :as => :register
get 'edit' => 'users#edit', :as => :edit
This is in my application_controller.rb,
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :set_user
protected
def set_user
unless session[:user_id] == nil
#signed_in_user = User.find(session[:user_id])
end
end
end
This is in my Users_Controller
Here is my code from my User_Controller on creating the account
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
# GET /users.json
def index
#users = User.all
end
def profile
#user = User.find(session[:user_id]) unless session[:user_id] == ""
redirect_to login_path, notice: "You're not logged in" unless #user
end
# GET /users/1
# GET /users/1.json
def show
end
# GET /users/new
def new
#user = User.new
end
# GET /users/1/edit
def edit
#user = #signed_in_user
end
# POST /users
# POST /users.json
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /users/1
# PATCH/PUT /users/1.json
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:name, :password, :password_confirmation, :email, :age)
end
end
And this is the link that I use for my HTML,
<li role="presentation"><%= link_to 'Edit', edit_path %></li>
So, to start, a good practice when you get an 'Couldn't find' message is to check what instance variables are in your view.
So in a view, just type: <%= #user_id %> and see if anything shows up on your page, thus indicating if any user is even present! The other problem is that your instance variable might be <%= user.id %> but I am not sure as I can't see your code and how the user is stored in the database.
Second if you run rake routes, you generally find that the edit path will have a URI pattern like: "/edit(.:format)", meaning the route need "edit_path(#user.id)" rather than just "edit_path".
Let me know if this leads you anywhere or you have further questions and I hope I can answer them!
===========
Additional info:
Well without more code to look at, I would provide a few more suggestions...The goal is to have the <%= #user.id %> (or user_id) show up on the page somehow, thus telling you it is available.
The set_user method is an instance method, not a class method. To make it a class method, try def self.set_user. This invokes the method of the instance on the controller, thus making it a class method.
Make sure you have a session object to use. In the routes, it looks like post logging/user might be creating the session, but I am not sure.
Keep the edit_path(#user.id) or however the id is stored for the user as the route rather than just edit_path. I am pretty sure if you run 'rake routes', it will tell you that an additional variable needs to be passed for the link to work
Use the gem byebug Here is the link: https://github.com/deivid-rodriguez/byebug. You get this error while the edit page or where ever you are getting the error write in the action byebug. As you have mentioned in the console it shows a arrow pointing at a specific line in the application, the last line should appear as this (byebug), here write the variable in which you are getting the user id. If we take an example of your application controller in the set_user method:
def set_user
byebug
unless session[:user_id] == nil
#signed_in_user = User.find(session[:user_id])
end
end
In the console after (byebug) write session[:user_id] so this will give you the value of the session[:user_id]. So if this is null then you have a problem here or just follow the same procedure to check anywhere else.
Also there is one more thing you can do to learn is just create a new project or use the existing one and generate a scaffold which will give you options of show, edit, index. It will generate all the views, controller code, migration and everything. You can do that like this:
rails generate scaffold User email:string password:string
You can add more fields if you want. And then in your application just visit http://localhost:[port_no]/users which will by default take you to index page where you can add new users, edit existing ones. This will teach you about everything. It would be like a reference code for you. Read more at: http://guides.rubyonrails.org/command_line.html#rails-generate
And being more specific to users only there is a gem named Devise which will give you all the required things like sign_in, sign_up, session_management for users. Hope these things help you with your issue.
Edit:
Here is a very good tutorial link which will help you: https://www.railstutorial.org/book/updating_and_deleting_users#sec-updating_users
Preface: I'm using devise for authentication.
I'm trying to catch unauthorized users from being able to see, edit, or update another user's information. My biggest concern is a user modifying the form in the DOM to another user's ID, filling out the form, and clicking update. I've read specifically on SO that something like below should work, but it doesn't. A post on SO recommended moving the validate_current_user method into the public realm, but that didn't work either.
Is there something obvious I'm doing wrong? Or is there a better approach to what I'm trying to do, either using devise or something else?
My UsersController looks like this:
class UsersController < ApplicationController
before_filter :authenticate_admin!, :only => [:new, :create, :destroy]
before_filter :redirect_guests
def index
redirect_to current_user unless current_user.try(:admin?)
if params[:approved] == "false"
#users = User.find_all_by_approved(false)
else
#users = User.all
end
end
def show
#user = User.find(params[:id])
validate_current_user
#user
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
validate_current_user
#user
end
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to #user, :notice => 'User was successfully created.' }
else
format.html { render :action => "new" }
end
end
end
def update
#user = User.find(params[:id])
validate_current_user
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, :notice => 'User was successfully updated.' }
else
format.html { render :action => "edit" }
end
end
end
private
def redirect_guests
redirect_to new_user_session_path if current_user.nil?
end
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
return redirect_to(current_user)
end
end
end
The authenticate_admin! method looks like this:
def authenticate_admin!
return redirect_to new_user_session_path if current_user.nil?
unless current_user.try(:admin?)
flash[:error] = "Unauthorized access!"
redirect_to root_path
end
end
EDIT -- What do you mean "it doesn't work?"
To help clarify, I get this error when I try to "hack" another user's account:
Render and/or redirect were called multiple times in this action.
Please note that you may only call render OR redirect, and at most
once per action. Also note that neither redirect nor render terminate
execution of the action, so if you want to exit an action after
redirecting, you need to do something like "redirect_to(...) and
return".
If I put the method code inline in the individual controller actions, they do work. But, I don't want to do that because it isn't DRY.
I should also specify I've tried:
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
redirect_to(current_user) and return
end
end
If you think about it, return in the private method just exits the method and passes control back to the controller - it doesn't quit the action. If you want to quit the action you have to return again
For example, you could have something like this:
class PostsController < ApplicationController
def show
return if redirect_guest_posts(params[:guest], params[:id])
...
end
private
def redirect_guest_post(author_is_guest, post_id)
redirect_to special_guest_post_path(post_id) if author_is_guest
end
end
If params[:guest] is present and not false, the private method returns something truthy and the #show action quits. If the condition fails then it returns nil, and the action continues.
You are trying and you want to authorize users before every action. I would suggest you to use standard gems like CanCan or declarative_authorization.
Going ahead with this approach you might end up reinventing the wheel.
In case you decide on using cancan, all you have to do is add permissions in the ability.rb file(generated by rails cancan:install)
can [:read,:write,:destroy], :role => "admin"
And in the controller just add load_and_authorize_resource (cancan filter). It will check if the user has permissions for the current action. If the user doesnt have persmissions, then it will throw a 403 forbidden expection, which can be caught in the ApplicationController and handled appropriately.
Try,
before_filter :redirect_guests, :except => [:new, :create, :destroy]
should work.
This is because you are using redirect twice, in authenticate_admin! and redirect_guests for new, create and destroy actions.
"Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action."
That's the reason of the error. In show method, if you are neither the owner of this account nor the admin, you are facing two actions: redirect_to and render
My suggestion is to put all of the redirect logic into before_filter
I am creating a basic product landing page with Rails in which users can enter their email address to be notified when the product launches. (Yes, there are services/gems etc that could do this for me, but I am new to programming and want to build it myself to learn rails.)
On successful submit of the form, I would like to redirect to a custom '/thanks' page in which I thank users for their interest in the product (and also encourage them to complete a short survey.)
Currently, successful submits are displayed at "/invites/:id/" eg "invites/3" which I do not want since it exposes the number of invites that have been submitted. I would like to instead redirect all successful submits to a "/thanks" page.
I have attempted to research "rails custom URLs" but have not been able to find anything that works. The closest I was able to find was this Stackoverflow post on how to redirect with custom routes but did not fully understand the solution being recommended. I have also tried reading the Rails Guide on Routes but am new to this and did not see anything that I understood to allow for creating a custom URL.
I have placed my thanks message which I would like displayed on successful form submit in "views/invites/show.html.haml"
My Routes file
resources :invites
root :to => 'invites#new'
I tried inserting in routes.rb:
post "/:thanks" => "invites#show", :as => :thanks
But I don't know if this would work or how I would tell the controller to redirect to :thanks
My controller (basically vanilla rails, only relevant actions included here):
def show
#invite = Invite.find(params[:id])
show_path = "/thanks"
respond_to do |format|
format.html # show.html.erb
format.json { render json: #invite }
end
end
# GET /invites/new
# GET /invites/new.json
def new
#invite = Invite.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
# POST /invites
# POST /invites.json
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
#format.js { render :action => 'create_success' }
format.json { render json: #invite, status: :created, location: #invite }
else
format.html { render action: "new" }
#format.js { render :action => 'create_fail' }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end
It would seem as if creating a standard URL for displaying a confirmation would be relatively straightforward. Any advice on how to achieve this would be appreciated.
I guess you want to redirect after your create action, which is executed when the form is submitted.
Just add redirect_to in the following way:
def create
#invite = Invite.new(params[:invite])
if #invite.save
...
redirect_to '/thanks'
else
...
redirect_to new_invite_path # if you want to return to the form submission page on error
end
end
I omitted some of the code for brevity.
In your routes add:
get '/thanks', to: "invites#thanks"
Add the thanks action to your invites controller:
def thanks
# something here if needed
end
And create a thanks.html.erb page in app/views/invites.
I would do get "/thanks" => "invites#thanks" in routes.rb and then add this in your controller:
def thanks
end
Then add a file app/views/invites/thanks.html.erb with your thank-you content.
You could create a route like this:
resources :invites do
collection do
get 'thanks'
end
end
This will also create a path helper called thanks_invites_path.
It will be at the invites/thanks path, but if you want it to be on/thanks, you could just do as Jason mentioned:
get "/thanks" => "invites#thanks", :as => :thanks
The as part will generate a helper to access that page: thanks_path.
You would need a extra action in the controller called thanks, and put whatever info you need inside, and also you will need a additional view called thanks.html.erb
Since you want everybody to go to that page after a successful submit, in your create action you would have:
format.html { redirect_to thanks_invites_path} (or thanks_path), what ever you choose, when you name the route you can check it with rake routes if it's okay, and whatever rake routes says, just add _path at the end.
This is my users_controller.rb code:
...
def edit
#user = #current_user
end
def update
#user = #current_user # makes our views "cleaner" and more consistent
if #user.update_attributes(params[:user])
flash[:notice] = "Account settings updated."
redirect_back_or_default account_url
else
render :action => 'edit'
end
end
...
I have the following views/users:
new.html.erb
password.html.erb
profile.html.erb
show.html.erb
Everytime I try to update my user account settings, the following pops up:
ActionView::MissingTemplate (Missing template users/edit.erb in view path app/views:vendor/plugins/admin_data/app/views):
app/controllers/users_controller.rb:46:in `update'
It's not the fault of admin_data is you are curious. It's the controller. What have I done wrong? Thanks.
First, if you want the update action to return to :show, you should change
render :action => 'edit'
to
render :action => 'show'
otherwise, it will of course look for edit.html.erb or edit.erb
Second, if you use 'show' for letting the user edit his/her information, why do you actually have the :edit action in your controller? If you don't use it, you should probably remove the following:
def edit
#user = #current_user
end
unless you tell the edit method to render something else or redirect to some where, the default is that it will look for a template of edit.erb...