I am using a custom passwordless login for my app. A user simply needs to enter their username or email, and then a unique login link with a token is sent to them. They enter their username or email into a simple form :
<%= form_tag request_token_path, id:'login-form' do %>
<%= text_field_tag :user_id %>
<% end %>
This posts to a sessions#request_token method which verifies whether that user exists and then sends along the login link.
def request_token
lookup = session_params[:user_id]
if lookup.include? '#'
#user = User.find_by(email: lookup)
else
#user = User.cached_find(lookup)
end
if #user
#user.send_login_link
redirect_to login_path, notice: "#{#user.username.capitalize} your email was sent!"
else
redirect_to login_path, notice: "Whoops! Looks like #{lookup} is not registered on this site. Please check spelling or signup!"
end
end
My question is that in my SessionsController file I defined the sessions_params
private
def session_params
params.require(:session).permit(:user_id,:auth_token)
end
I know that means that I have to use a session object or in order to pass along the :user_id from the form since I defined :user_id as a param that is on valid as an attribute of a session. I am wondering the correct way to do this. Making a new session object doesn't make sense since that isn't even a model I have but is it safe to just take it from the params?
and instead make lookup = params[:user_id] ?
If you have a session object that responds to user_id attribute, you need to create the form for that object specifically:
<%= form_for #session do |f| %>
<%= f.text_field :user_id %>
<% end %>
If that's not the case, and you need to stick to form_tag, try making the attribute name something that would come up in the controller as a session hash:
<%= text_field_tag "session[user_id]" %>
When you do
params.require(:session)
it means you're requiring your params hash to have a session key, which in turn should have the permitted user_id attribute:
{params: {session: {user_id: "something"}}
And thats why you'd need form_for #session OR the textfield with the suggested "session[user_id]" name
Related
I am trying to create a custom form_for where I check if the record exists in the table or not. I've done tons of research but haven't come up with anything useful.
My current approach is to create a simple search form and display all similar records. However, that's not what I am looking for. Ideal scenario would be:
Get record's name from form_for
Check if this record present
If present - redirect to one page. If not - redirect to another page
My controller:
def validate_name
#room = Room.new
name = params[:name]
if name != nil
puts "Redirect to page A"
else
puts "Redirect to page B"
end
end
The problem here is that whenever the user comes to the page it automatically triggers the code above. My goal is to create a form validation that tries to find the exact record and then redirect based on if else condition.
Current form_for:
= form_for(Room.new, url: name_room_path, method: :get, action: :validate_name) do |f|
= f.text_field :name
= f.submit
I am sure that my form is incorrect too because I got lost. I found ways to create custom forms but can't figure out how to trigger database check based on the user's input.
PS: these are not new or update actions.
Thank you for your help and time.
Try this:
class RoomsController < ActionController::Base
def validate_name
if params[:name] && Room.where(name: params[:name]).last.present?
puts "Redirect to page A"
elsif params[:name] && Room.where(name: params[:name]).last.nil?
puts "Redirect to page B"
end
end
end
in routes.rb:
get '/rooms/validate_name', to: 'rooms#validate_name'
in view:
<%= form_tag(rooms_validate_name_path, :method => :get )do %>
<%= text_field_tag :name %>
<%= submit_tag %>
<% end %>
I am building a digital library, and I have completed a lot of the functionalities needed. I am currently having an issue with integrating the digital library with a Learning Management System (LMS).
I already have an admin authentication system for the digital library using the Devise gem. My goal is to allow users who want to access the digital library to login to the digital library using their Learning Management System (LMS) credentials (username and password).
I have been provided with the Login API endpoint and other needed parameters of the Learning Management System (LMS), and I have created the User Model, the Sessions Controller and the Sessions View Templates.
I am currently using the RestClient Gem for the API call, but I having an error undefined local variable or method `username' for # Did you mean? user_path. I can't figure out where things went wrong.
Sessions Controller
require 'rest-client'
class SessionsController < ApplicationController
def new
end
def create
response = RestClient::Request.execute(
method: :post,
url: 'https://newapi.example.com/token',
payload: { 'username': "#{username}",
'password': "#{password}",
'grant_type':'password' },
headers: { apiCode: '93de0db8-333b-4f478-aa92-2b43cdb7aa9f' }
)
case response.code
when 400
flash.now[:alert] = 'Email or password is invalid'
render 'new'
when 200
session[:user_id] = user.id
redirect_to root_url, notice: 'Logged in!'
else
raise "Invalid response #{response.to_str} received."
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: 'Logged out!'
end
end
Sessions New View
<p id=”alert”><%= alert %></p>
<h1>Login</h1>
<%= form_tag sessions_path do %>
<div class="field">
<%= label_tag :username %>
<%= text_field_tag :username %>
</div>
<div class="field">
<%= label_tag :password %>
<%= password_field_tag :password %>
</div>
<div class="actions">
<%= submit_tag 'Login' %>
</div>
<% end %>
User Model
class User < ApplicationRecord
has_secure_password
validates :username, presence: true, uniqueness: true
end
Any form of help with code samples will be greatly appreciated. I am also open to providing more information about this integration if required. Thank you in advance.
I think that the problem is in fact that inside your SessionsController in create action, you are interpolating username and password. There's no definition for these methods in your code so you get undefined local variable or method.
You could probably pick those from params like this:
def username
params[:username]
end
def password
params[:password]
end
Or interpolate them directly in payload replacing current method calls with params[:username] and params[:password].
In such situations, it is good to use byebug or pry to debug your code and see what's happening inside your controller.
You could also think of closing some parts of your logic in Service objects - you shouldn't have more 10-15 lines in your controller action (unless the situation requires it)
Maybe you should use params[:username] rather than only username ?
username and password in payload are undefined variables. Please set their values. Possible values could be params[:username] and params[:password]
I have a request object that I want to pass to my charges_controller. I can pass it in to the new action but after that new action is called I need that same request object passed to the create object in the same charges_controller. I can not get it. Here is the initial request_controller action passing the request_id to the charges controller.
class Users::RequestsController < ApplicationController
before_filter :authenticate_user!
def create
#user = current_user
if #request = #user.request.create!(authorization_params)
if #request.user_paying == true
redirect_to new_users_charge_path(id: #request)
else
redirect_to users_dashboard_path
end
else
redirect_to users_dashboard_path, :error => "There is something wrong and your request has not been submitted."
end
end````
It then passes it to the new method in the charges controller. Here is my charges controller.
class Managers::ChargesController < ApplicationController
before_filter :authenticate_user!
def new
#request = Request.find(params[:id])
#user = current_user
#amount = #request.user_amount
end
def create
# Amount in cents
#request = Request.find params[:request_id]
#user = current_user
#amount = #request.user_amount
customer = Stripe::Customer.create(
:email => #user.email,
:card => params[:stripeToken]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => #amount,
:description => 'Manager Paid Report',
:currency => 'usd'
)
#request.report_paid = true
#request.save!
redirect_to managers_dashboard_path, :notice => "You have succesfully requested a pre-paid report from #{#reportapproval.tenant_last_name}."
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to managers_charges_path
end
end
I can get the request info into the charges/new.html view. But when I create the charge the request is not found. How can I pass the request to the create action?
Here is the new view...
<h4>Total Payment Due: <%= number_to_currency(#amount.to_i/100.0) %>
<%= form_tag users_charges_path do %>
<%= hidden_field_tag :request_id, #request.id %>
<br />
<script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="<%= Rails.configuration.stripe[:publishable_key] %>"
data-description="Payment"
data-amount="<%= #amount %>"
data-locale="auto" >
</script>
<% end %>
Controllers simply respond to HTTP requests - there is no way to forward a request internally in Rails. This is a very conscious design decision.
If you need to stash data in between requests you would either save it in the database, the session or some sort of caching mechanism like Memcached/Redis.
Most commonly in Rails you stash data in the database and pass ID's or other unique identifiers in the request parameters.
If you need to pass the id to the "Request" object you would either pass it in the request url or in the request body in the case of PUT/PATCH/POST.
So in your case you need to ensure that the #new action either posts to:
post "/managers/charges/:request_id"
Or that the form includes
<%= hidden_field_tag 'request_id', #request.id %>
PS
Request although not technically a reserved word is a really bad name for a model since it will eventually cause conflicts and confusion with ActionDispatch::Request especially if you use the controller instance variable #request.
As you can see from the my answer above it gets confusing as h*ll since the word request is allready has a very specific connotation in web development.
The same applies to Response.
Use PaymentRequest or any other synonym.
Assuming you have a form in the charges/newview, you can set the request_id using a hidden form field:
<%= hidden_field_tag :request_id, #request.id %>
You should add the line above between <%= form_for ... do |f| %> and <% end %>.
In that example, you were assuming the create action would somehow have a request_id passed in automatically. Rails doesn't do that, in every request you need to explicitly provide all the information for the controller to process that request.
I hope that helps! ;)
At the beginning let me excuse for easy question for those who are experienced but for me it is difficult right now. In the Rails in one of the views/_form.html.erb, I want to change line below (it works):
<%= f.collection_select :user_id, User.all, :id, :email %>
into hidden field that will hold the id of the user that is logged in. I try to change it into:
<% f.hidden_field :user_id, :id %>
but it throws an error:
NoMethodError in Orders#new
undefined method `merge' for :id:Symbol
Can sb help me to solve that?
Use this (if you already have current_user method available):
<%= f.hidden_field :user_id, :value => current_user.id %>
If you don't have current_user method implemented, in your corresponding controller, you can have something like this:
#current_user = User.find(params[:user_id])
Then, in your view, you can do:
<%= f.hidden_field :user_id, :value => #current_user.id %>
Update
After the above conversation, if you want to use session to store the user_id, then you can do something like this.
You can create a SessionsHelper module (which you can include in your ApplicationController) where you can define a log_in method:
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
(You can also put this: session[:user_id] = user.id in the create action where you create an user.)
You can also define a current_user method in this module:
# Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
Here are some other useful helper methods that you can add in this module:
# Returns true if the given user is the current user.
def current_user?(user)
user == current_user
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
# Logs out the current user.
def log_out
session.delete(:user_id)
#current_user = nil
end
Finally, I would suggest you to take a look at Devise gem which is a very popular authentication solution for Rails application.
This error means that it expects a hash, but you are putting an empty symbol.
You need to send a hash
If you have the current_user method:
<%= f.hidden_field :user_id, :value => current_user.id %>
If you don't, you may use this in your controller
id = User.find(someid)
And keep the same code in the view.
Look # the documentation for more info
The first two answers are correct. Here's why
f.hidden_field is a method on the form object you created somewhere either in that partial or in a file/partial that includes it. The documentation linked by Hristo Georgiev is for the hidden_field method from ActionView::Helpers::FormHelper (link). The one you're trying to call is from ActionView::Helpers::FormBuilder (link)
hidden_field works just as you seem to be expecting it to, ie
hidden_field(:user_object, :id)
these are two different methods
f.hidden_field works a bit differently, because it already has access to some of the information used to build the hidden field. It expects a method to call on the form object and an optional hash which it converts to attributes on the hidden field (thus the :value => user.id hash)
Assuming you had the following form
form_for(#user) do |f|
...
end
And you wanted the id to be a hidden field, you would put this within that block
f.hidden_field(:id)
That would generate the following HTML
<input type="hidden" id="user_id" name="user[id]" value="1" />
See this line from ActionView
TL;DR
You're calling the FormBuilder#hidden_field method with the arguments expected by FormHelper#hidden_field
Say I have a site like this (generic Q&A site) in Rails and I wanted this "ask" page w/ a text box to be the first page a user sees, even if he's not logged in. He enters a question, and on the 'new' method I check that he's not logged in, and bounced him to /session/new, where he can either log in or create a new account. Question is, how do I (and what is the best way to) preserve that question that he initially asked all through this process?
I'm understanding the flow of action described in the question to be
user is presented with a form
user is redirected to log in page on submit
user is redirected back to form on successful log in
repopulate form on load (Question asks how to do this step)
user finally submits their form.
With steps 2-4 omitted if the user is logged in.
I'm sorry, but I see your question more as a symptom of an underlying UI issue than a rails question.
If only logged in users can post questions, then why display the text box?
If a user is going to have log in any way, why not get that out of the way first. An even better solution is to integrate the log in and form.
Something like this in the view:
<% form_for :question do |form| %>
<% unless logged_in? %>
<% fields_for :session do |session_form|%>
<%= session_form.label :login %>
<%= session_form.text_field :login %>
<%= session_form.label :password %>
<%= session_form.password_field :password %>
<%end%>
<%end%>
<%= form.text_area :question %>
<%end%>
And in the controller
def new
...
unless params[:session].nil?
self.current_user = User.authenticate(params[:session][:login], params[:session][:password])
end
if logged_in?
flash[:notice] = "Logged in successfully"
else
flash[:error] = "Incorrect username and or password."
end
if logged_in? && #question.save
.... process successful entry
else
... process unsuccessful entry
end
end
Edit: Mohamad's raises the question of reusing this pattern across multiple controllers and forms. So the answer was updated to address reuse of this pattern.
To simplify this for reuse, you could put this block in a helper function that is referenced in the before_filter for actions that require it.
def login
unless params[:session].nil?
self.current_user = User.authenticate(params[:session][:login], params[:session][:password])
if logged_in?
flash[:notice] = "Logged in successfully"
else
flash[:error] = "Incorrect username and or password."
end
end
end
as in:
before_filter :login => :only [:new , :edit, :update, :delete]
On the view side, it shouldn't be too hard to construct a new variant of form_for that embeds the session parameters. Maybe form_for_with_session?
As for handling an unsuccessful response, I would suggest helper function that takes a block of code. Sorry I don't have time to write out or test one for you.
You keep it in the session. So after logging in, when the user goes back to asking his question, you see there's already something in session.
And you can directly display it.
def create
if current_user # Implement this method in your auth framework
#question = Question.new(params[:question] || session.delete[:question])
# (the usual stuff you'd do to save)
else
session[:question] = params[:question]
redirect_to :controller => :sessions, :action => "new"
end
end
Then, after your user creation and authentication stuff is all done in your login action, just make sure you POST back to this create action if session[:question] is defined.