cannot update attribute within users model in custom action - ruby-on-rails

I am using the Gem Devise for users in my application. I added the role attribute within users that is set to nil. After I sign in, within the application controller I have a redirect that goes to a custom action within the users controller and view called binary_selection if current_user.role = nil. The code is below
application_controller.rb:
def after_sign_in_path_for(resource)
if current_user.role.nil? ## temporary solution
#update_path(resource)
binary_selection_path(resource)
else
root_path(resource)
end
end
users_controller.rb:
def binary_selection
#user = current_user
respond_to do |format|
if #user.update_attributes(params[:user][:role])
format.html { redirect_to root_url, notice: "#{#user.name} was updated." }
else
format.html { render action: "edit" }
end
end
end
views/users/binary_selection.html.erb:
<%= form_for #user, url: binary_selection_path(#user), html: { method: :patch } do |f|
%><%= current_user %>
<div class = "form-group">
<%= f.label :role %>
<%= f.text_field :role, class: 'form-control', placeholder: "Enter wiki title", id: 'wiki_title' %>
</div>
<div class = "form-group">
<%= f.submit class: 'btn btn-success' ,id: 'wiki_submit' %>
</div>
<% end %>
config/routes.rb:
Rails.application.routes.draw do
devise_for :users
get "/users" => "users#binary_selection", as: 'binary_selection'
resources :users
end
When I get redirected to the binary_selection view I get this error:
NoMethodError in UsersController#binary_selection
undefined method `[]' for nil:NilClass
where it highlights this line within the users controller:
if #user.update_attributes(params[:user][:role])
I also noticed it has this for the params on the error page:
{"format"=>"13"}

You redirect your user after sign_in to the binaray_selection_path. This will result in a HTTP GET request. In your users_controller you try to get a attribute from the params that simple does not exist. There is no params[:user], because the form was not sent yet.
You need to redirect the user to a page were the form is rendered.
I advise to read this guide: http://guides.rubyonrails.org/getting_started.html#getting-up-and-running

Your form is not submitting the values properly may be. So inspect your params in the binery select action.
record#update_attributes doesn't take a single value, it accepts hash. So the proper code should be something like:
rails version < 4
if #user.update_attributes(params[:user])
in rails 4.*
if #user.update_attributes(user_params)
in controller make sure you have permitted the user parameters.

Related

Rails form rendering wrong URL after validation errors (not keeping passed parameter)

On a project show page, I pass a very simple parameter on my 'create new task' that stores which project its from:
#project.id), :class => "btn btn-info col-md-12" %>
so that when i create a new task for it, it stores it in the URL on my new task form like this:
http://localhost:3000/task/new?project_id=5
My New form is as follows:
<div class="container sign-in-register">
<div class="authform">
<%= form_for #task, :html => {:multipart => true} do |f| %>
<h3>Add a task for this project...</h3><br/>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= hidden_field_tag 'project_id', #project_id %>
<%= f.fields_for :taskrelationships do |ff| %>
<%= ff.hidden_field :taskproject_id, value: #project_id %>
<%= ff.label :task_url %>
<%= ff.text_field :task_url, class: 'form-control' %>
<% end %>
<br clear="all">
<%= f.submit "Save Task", class: "btn btn btn-info" %>
<% end %>
</div>
</div>
As you can see i'm using nested attributes in the form (I am creating both a task and a TaskRelationship. Now, when I try to save without filling out all the required fields a validation is thrown but for some reason it redirects me to:
http://localhost:3000/tasks
instead of the original:
http://localhost:3000/tasks/new?project_id=5
I have read many posts and none seem to answer this particular case. The stackO post below was close but when I try it with task instead of user it still cannot find the task_ID
Render error on new page with customize url like http://localhost:3000/education_informations/new?user_id=10
How can I have rails simply render the same exact url as I started with - it seems like this should be easy so must be missing something small.
My controller actions:
def new
#project_id = params[:project_id]
#task = Task.new
#task.taskrelationships.build
end
def create
#project = Project.find(params[:project_id])
#task = Task.new(task_params)
if #task.save
flash[:success] = "This task has been added."
#task.taskrelationships.create!(#taskrelationships_params)
redirect_to tasks_project_path(#project)
else
#task.taskrelationships.build(#taskrelationships_params)
flash[:alert] = #task.errors.full_messages.to_sentence
render :new
end
end
private
def task_params
#taskrelationships_params = params.require(:task).permit(taskrelationships_attributes: [
:task_url,
:taskproject_id
])[:taskrelationships_attributes]["0"]
params[:task].delete(:taskrelationships_attributes)
params.require(:task).permit(
:name,
:user_id,
taskrelationships_attributes: [
:task_url,
:taskproject_id
]
).merge(owner: current_user)
end
UPDATE W / ROUTES
resources :projects do
resources :reviews, except: [:destroy]
member do
get :tasks
end
end
resources :tasks
resources :taskrelationships, only: [:create, :destroy] do
post :vote, on: :member, controller: "task_relationships"
end
thanks for any assistance...
Ok firstly an explanation as to what is going on here:
When you invoke http://localhost:3000/task/new?project_id=5 you are actually being routed to the new action on the task controller (with a project_id param).
Your new action then sets the variables and rails will render the new.html.erb which contains your new task form.
When you submit the form it actually is doing a http POST to /tasks, which routes to the create action of your tasks controller. That url and http method is a result of what is generated from the form_for helper:
<%= form_for #task, :html => {:multipart => true} do |f| %>
This is why the url changes from /tasks/new?project_id=5 to /tasks
Now the create action if it fails the validation simply renders the new form - it is not redirecting anywhere - the url remains unchanged from what it was when it entered this action - meaning, it remains as /tasks.
You do not actually need to navigate to /tasks/new?project_id=5 to render the new form but what you do need to do is set #project_id in the controller so the view has access to that variable (just as it does in the new action):
def create
#project = Project.find(params[:project_id])
#task = Task.new(task_params)
if #task.save
flash[:success] = "This task has been added."
#task.taskrelationships.create!(#taskrelationships_params)
redirect_to tasks_project_path(#project)
else
#task.taskrelationships.build(#taskrelationships_params)
#project_id = #project.id
flash[:alert] = #task.errors.full_messages.to_sentence
render :new
end
end
So, to clarify the change in url is not a redirection it's just that the form is posting to a different url than /tasks/new, and this is actually just a cosmetic issue.
Now if it is a concern to you, you can change the routing to something like the following:
resources :tasks, except: [:create, :new]
post 'new_task' => 'tasks#create'
get 'new_task' => 'tasks#new'
This is mapping the POST and GET http methods to /new_task so the url appears the same for the new and create action invocations. Note you do need to change the url in the form_for helper to use this new route:
<%= form_for #task, url: 'new_task', multipart: true do |f| %>
Since Rails default behaviour in forms is with remote: true, you can move the content of the form to a partial(let's name it _my_form.html.erb), add to the controller action (let's say in create action):
respond_to do |format|
format.js {}
end
Then add a create.js.erb file where you will render the form partial
$("#form").html(
"<%= j render partial: 'my_form', locals: { entity: #entity } %>"
);
Thus, validation errors and all attributes will be accessible inside the form and there is no need to hack the "Rails approach"
I had to adapt and experiment with the currently accepted answer,
and the following ended up working well (using schools rather than tasks):
In config/routes.rb:
resources :schools do
...
end
post 'schools/new' => 'schools#create'
And create the form like:
= simple_form_for [#school], url: 'new' do |f|
Thus the path /schools/new was preserved on validation :)

Rails 4 - Updating Specific Attributes in model

I'm building a marketplace app (rails 4) where sellers can list items to sell. I have a seller profile form where users enter some details about their brand.
I'm not able to get the record to update with user inputs. The form submits without any error but the model is not updated.
Code below. Demo at http://mktdemo.herokuapp.com/seller/18 (login: test#test.com / pwd: test1234)
route:
match "/seller/:id", to: "users#sellerprofile", via: [:get, :put], as: :sellerprofile
my form:
<%= form_for(#user, url: sellerprofile_path(id: current_user.id), method: :put, html: { :class => 'edit_profile', :multipart => true }) do |f| %>
<div class="form-group">
<%= f.label :your_story %><i> (required)</i>
<%= f.text_area :profilestory, class:"form-control" %>
</div>
<div class="form-group">
<%= f.label :profile_image %><i> (required)</i>
<%= f.file_field :profileimage, class:"form-control" %>
</div>
<div class="form-group">
<%= f.submit class:"btn btn-primary" %>
</div>
<% end %>
user_controller:
def sellerprofile
#user = User.find(params[:id])
#user.update_attributes(:profilestory => params[:profilestory], :profileimage => params[:profileimage])
end
For the image, I'm using paperclip and have the has_attached... code in my model.
UPDATE:
Here is my user_params in controller:
def user_params
params.require(:user).permit(:bankaccname, :profileimage, :profilestory)
end
When I use #user.update(user_params) in the sellerprofile method, I get a params :user not found error. This error occurs when I load the form (not on submit). Error is in the line params.require(:user)
UPDATE 2:
Here is the update method. I'm using this for another form that takes in some bank account data so I'm not sure if I can modify this for the profile form.
def update
#user.attributes = user_params
Stripe.api_key = ENV["STRIPE_API_KEY"]
token = params[:stripeToken]
recipient = Stripe::Recipient.create(
:name => user_params["bankaccname"],
:type => "individual",
:bank_account => token
)
#user.recipient = recipient.id
respond_to do |format|
if #user.save
format.html { redirect_to edit_user_url, notice: 'Your account was successfully updated.' }
else
format.html { render action: 'edit' }
end
end
end
If you look at your question, it says:
When I use #user.update(user_params) in the sellerprofile method, I get a params :user not found error. This error occurs when I load the form (not on submit)
This line tells us that there is something wrong in the action where your form is rendered. I mean the url you hit in browsers address bar to display your form
And in your comment you told me that the url for rendering your form is /seller/:id and now lets look at your form code
<%= form_for(#user, url: sellerprofile_path(id: current_user.id), method: :put, html: { :class => 'edit_profile', :multipart => true }) do |f| %>
Notice the url used in the form? You are using the same action to render your form and then after submitting form it'll again take you to same action which is wrong. You should make two different routes. One to render your form and other one to create it. To explain it further, if you look at your code
def sellerprofile
#user = User.find(params[:id])
#user.update_attributes(user_params)
end
So when you hit /seller/:id it takes you to sellerprofile method and hence code inside your sellerprofile method fires up and it tries to update your user because of this line #user.update_attributes(user_params) but there are no user_params as you didn't submit any form so you get a params :user not found errorr
Fix:
Just make two different routes, one to render your form and another one to create your record:
match "/seller/:id/new", to: "users#sellernew", via: [:get], as: :sellernewprofile
match "/seller/:id", to: "users#sellerprofile", via: [:put], as: :sellerprofile

How do I allow logging in from two different locations in my app?

My app allows a user to log in from two different places, the header and the new session page. The new session page logs a user in and redirects them to the correct page, but the home pages just reloads the home page without redirecting the user or logging them in.
This is my sessionscontroller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
session[:user_id] = user.id
redirect_back_or feed_user_path(user)
else
session[:user_id] = nil
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
new.html.erb
<%= form_for(:session, url: sessions_path) do |f| %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.submit "Log in", class: "btn btn-large btn-info" %>
<% end %>
Code in my _header.html.erb
<%= form_for :session, :url => {:controller => "sessions", :action => "new"} do |f| %>
<div class="home-login form-group">
<%= f.label :email %>
<%= f.text_field :email, :placeholder => "Email" %>
</div>
<div class="home-login form-group">
<%= f.label :password %>
<%= f.password_field :password, :placeholder => "Password" %>
</div>
<div class="form-group home-login">
<%= f.submit "Log in", class: "btn-info" %>
</div>
<% end %>
This is what shows up in the terminal
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: AegIbI8c1TIddIBPVWTt/B2CBoCAgbJxL+NWDe782Cc=
session: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
email: ** # **
password: ****
commit: Log in
controller: pages
action: home
EDIT****
This is my application controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
end
Home is a static page in my pages controller
class PagesController < ApplicationController
def home
end
end
EDIT****
This is my SessionsHelper
module SessionsHelper
def signed_in?
!!session[:user_id]
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def current_user=(user) # set current user
#current_user = user # session[:user_id] = user.id
end
def current_user?(user) # get current user
user == current_user
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
end
EDIT****
Routes
sessions POST /sessions(.:format) sessions#create
new_session GET /sessions/new(.:format) sessions#new
session DELETE /sessions/:id(.:format) sessions#destroy
try changing your form_for tag to direct it to the create action with a method of post. something like this:
<%= form_for :session, url: session_path, method: 'post', action: 'create' do |f| %>
There's no difference with where do you put your login form unless you refer to any controller-specific data there. I don't see that, so you should be good to go with just copying the exact same form_for call that actually produces working results. At least I don't see anything that prevents doing this.
And the problem is the action. new. Wrong. It's create. "New" is a display page for the form (and uses GET because of that), the actual form's action (data send path) is "create" (that uses POST). HTTP method is picked from the routes, execute rake routes to verify that they are correct.
A form is not bound to a page it's on. Instead, it has its own path, that should accept input data from the form.
I figured it out. The ERB form was wrapped in an HTML form. That was causing the problem.

Couldn't find User without an ID (Devise user model)

I am new to rails. I have a Devise user model. I am trying to get an input from user and compare it with a string and redirect to another page if same. But there is some problem with #user. It will be a simple problem but I am stuck for hours. Please help.
questions_controller
class QuestionsController < ApplicationController
def qn1
#user = User.find_by_email(params[:email])
end
def submit
#user = User.find_by_email(params[:user])
if #user.answer == 'blank'
render root_path
else
render 'qn1'
end
end
end
qn1.html.erb
<%= form_for(#user, :url => {:controller => "questions", :action => "submit"}) do |f| %>
<%= f.label :answer %>
<%= f.text_field :answer %>
<br>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
You should retrieve a param sent by a form_for using it's multidimensional array, like this:
params[:user][:attribute]
You told us that you have Devise installed and running. If you're logged in, the most easy and secure way to access the current user is with the following method (automatically provided by devise on login)
current_user
So, if you want to compare the answer sent with the current user one, you could do something like this:
# QuestionsController
def submit
if current_user.answer == params[:user][:answer]
redirect_to root_path
else
render 'qn1'
end
end
Or, if it is just a matter of compare the answer sent with some arbitrary string, you don't even need the User model:
# QuestionsController
def submit
if params[:user][:answer] == 'blank'
redirect_to root_path
else
render 'qn1'
end
end
You shouldn't retrieve a user, or instantiate a new one, just to set it's answer to the one sent by the form and compare it with a string. It's absolutely unnecessary.

Redirect user depending on user type at login

I've got two controllers: admin and customers, plus one more called sessions for handling login and authentication. I'm trying to use one login form so that when an admin logs in, they are redirected to their part of the site, and if a customer logs in, they are taken to their part.
Edit: changed the params to login by email, but now getting the error No route matches {:action=>"show", :controller=>"customers"} when I try to log in as a customer :S!!
Code:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
customer = Customer.find_by_email(params[:email])
if user and user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to admin_url
elsif customer and customer.authenticate(params[:password])
session[:customer_id] = customer.id
redirect to customer_url
else
redirect_to login_url, alert: "Invalid user/password combination"
end
end
def destroy
session[:user_id] = nil
session[:customer_id] = nil
redirect_to store_url, notice: "Logged out"
end
end
Code for the login page (stored in app/sessions/new.html.erb):
<div class="depot_form">
<% if flash[:alert] %>
<p id="notice"><%= flash[:alert] %></p>
<% end %>
<%= form_tag do %>
<fieldset>
<legend>Please Log In</legend>
<div>
<%= label_tag :email, 'Email:' %>
<%= text_field_tag :email, params[:email] %>
</div>
<div>
<%= label_tag :password, 'Password:' %>
<%= password_field_tag :password, params[:password] %>
</div>
<div>
<%= submit_tag "Login" %>
</div>
</fieldset>
<% end %>
</div>
Also, if relevant, I've got this in the config/routes file:
controller :sessions do
get 'login' => :new
post 'login' => :create
delete 'logout' => :destroy
end
Edit: changed the params to login by email, but now getting the error No route matches {:action=>"show", :controller=>"customers"} when I try to log in as a customer :S!!
The problem is that your params[:email] does not exist at all! Try using the params[:name], and hope that your customer knows that he has to write his email into the name field.
user = User.find_by_name(params[:name])
customer = Customer.find_by_email(params[:name])
If you only have one login form, then the input for the username/email will either end up in the params hash as either
params[:email]
or
params[:name]
But it looks like params[:email] doesn't exist since you're only using one form. Since you can successfully authenticate as an admin, I would guess that the input for the username is titled "name", so in your params hash, when you authenticate as a customer, your authenticating against
params[:name] #this is what I think the input on the form is named
instead of
params[:email] #which is what it looks like your code is looking for.
Can you post the view code?

Resources