How to update a profile attribute by link_to - ruby-on-rails

I want to update a Profile model attribute by using link_to. The Profile model have a lang column, and I want to change to :en.
I could find out that I should use method: :put.
<%= link_to t('english'), profile_path(profile: {lang: :en}), method: :put %>
But it's ends up with error:
ActionController::UrlGenerationError in StaticPages#home
Showing /Users/ironsand/dev/phrasebook/app/views/layouts/_header.html.erb where line #21 raised:
No route matches {:action=>"update", :controller=>"profiles", :profile=>{:lang=>:en}} missing required keys: [:id]
I have this line in routes.rb to use the path:
resources :profiles, only: :update
How can I enable the function like this?
I found a similar question, but the case is a bit difference.
Edit
class ProfilesController < ApplicationController
def update
return redirect_to root_path unless current_user # If user is not logged in, redirect to /
if current_user.profile.update(profile_params) # Don't forget about validation for lang in Profile model
redirect_to root_path
else
redirect_to root_path
end
end
private
def profile_params
params.require(:profile).permit(:lang)
end
end

You need to identify the profile somehow, that's why it asks for id. But you can update profile without id, you just need to improve update method:
def update
return redirect_to root_path unless current_user # If user is not logged in, redirect to /
if current_user.profile.update(profile_params) # Don't forget about validation for lang in Profile model
redirect_to success_path
else
redirect_to error_path
end
end
private
def profile_params
params.require(:profile).permit(:lang)
end
For route, try this:
resources :profiles, only: [] do
collection do
put :update
end
end
or just:
put '/profiles' => 'profiles#update'

Since you're updating a specific profile, you need to supply something that lets your controller know what profile you're updating.
As you can see from the error message generated, your controller can't identify which profile it is that you're asking to be updated. You need to supply the id of the profile in order to update it.
One way this could be achieved with link_to is as follows:
link_to t('english'), profile_path(id: #profile.id, lang: :en), method: :put
lang would then be available in your update action in params[:lang].

Related

ActiveRecord::RecordNotFound "can't find without id"

I have a method that toggles a boolean, but cannot get it to work at the controller level due to an ActiveRecord not found error.
My items_controller.rb:
def remove
#item = Item.friendly.find(params[:id])
respond_to do |format|
if #item.toggle_approved
format.html { redirect_to root_path }
else
format.html { render :show }
end
end
end
When I set the instance variable like this - #item = Item.friendly.find(params[:id]) - I get an error that says Couldn't find Item without an ID. But when I set it like this - #item = Item.find(params[:id]) - I get an error that says Couldn't find Item with 'id'=. I'm passing the object as an argument to the path, so I'm not sure why this isn't working.
My view:
<%= link_to "Remove this item", item_remove_path(#item), class: 'button button-wide red-button', method: :patch %>
My routes.rb
resources :items do
patch '/remove', to: 'items#remove', via: :patch
end
What you are doing wrong:
You have extended the restful resource items, to include remove, and you are expecting to get the :id in params. params[:id] is nil in the request which you can verify by seeing the rails development log for this request.
How to fix this:
Way 1:
Change to patch '/remove', to: 'items#remove', on: :member. Adding a member route will expose the :id of resource in request, enabling your request to process.
Way 2:
Use your existing resource patch '/remove', to: 'items#remove', but use params[:item_id] instead of params[:id] in request.
Note: You DO NOT have to expose :id by patch '/remove/:id',
Change the routes as follow
resouces :items do
member do
patch :remove
end
end
It will create patch items/:id/remove then you dont need to change anything in controller.
You are accessing item's id in items_controller.rb so you have to define a route that accepting id also as #kiddorails mention above in a comment.
patch '/remove/:id', to: 'items#remove', as: :remove_item, via: :patch

Allowing admins to add users with Devise

I'm trying to make it so only admins can add uses with devise. I've gotten it mostly working however now when I'm logged in as an admin and submit the sign up form it kicks me back with the error: You are already signed in.
I've tried to follow the instructions here: http://wiki.summercode.com/rails_authentication_with_devise_and_cancan but it doesn't seem to mention this situation.
Do I need to do further overriding in the editors_controller to allow this?
Here are my routes ("editors" is the name of my user model):
devise_for :admins, :skip => [:registrations]
as :admin do
get 'admin/editors' => 'editors#index', as: :admin_editors
get 'admin/editors/new' => 'editors#new', as: :new_editor
delete 'admin/editors/:id' => 'editors#destroy', as: :destroy_editor
end
devise_for :editors, :skip => [:registrations], :controllers => { :registrations => "editors" }
and my editors_controller in "app/controllers/"
class EditorsController < Devise::RegistrationsController
before_filter :check_permissions, :only => [:new, :create, :cancel]
skip_before_filter :require_no_authentication
def dashboard
render "editors/dashboard.html.haml"
end
def index
#editors = Editor.all
respond_to do |format|
format.html
end
end
private
def check_permissions
authorize! :create, resource
end
end
EDIT
I noticed this Processing by Devise::RegistrationsController#create as HTML in the logs when I submit the form. I had suspected that perhaps the skip_before_filter :require_no_authentication wasn't being called, but assumed that because the EditorsController was inheriting from RegistrationController that before filter would work properly. Is that not the case?
You'll want to implement your own create method on EditorsController instead of inheriting that action from Devise::RegistrationsController. As you're seeing, the method in Devise::RegistrationsController will first check to see if you're already logged in and kick you back if you are. If you're not logged in it will create a User account and then log you in as that user.
You're trying to get around that problem with skip_before_filter :require_no_authentication, but it's likely that your form is POSTing to /editors instead of /admin/editors. So, you'll need to add a route that allows you to get to create on the EditorsController :
as :admin do
post 'admin/editors' => 'editors#create'
# your other :admin routes here
end
Then you'd want to implement a scaled down version of create. You probably want something kind of like this :
class EditorsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
if resource.save
redirect_to admin_editors_path
else
clean_up_passwords resource
respond_with resource
end
end
# your other methods here
end
You'll also want to make sure that the admin/editors/new template is pointing the form to the correct route ('admin/editors').
None of the googleable solutions worked when I tried them. This works
What I did was create a new action in the controller and a new route for it, and connect the links on my views that normally connect to create to now call my route and action.
But that wasn't enough. Because Devise is listening and will grab any add you try to do and validate it through it's own code. So instead I just add the new user record with a sql insert.
Add this route
post 'savenew', to: 'users#savenew'
Add this action to the user controller:
def savenew
rawsql = "insert into users (email, created_at,updated_at) values ('#{user_params[:email]}',now(), now())"
sql = rawsql
ActiveRecord::Base.connection.execute(sql)
redirect_to action: 'index''
end
View: new.html.erb
change the form_for so that submit will go to the new route and action, not the default Rails one.
<%= form_for User, :url => {:action => "savenew"} do |f| %>
Using Rails 4.2.6 here (my model is User instead of Editor). The following solution bypasses (I think) any devise actions that may interfere with new User creation by the admin:
Add this action to the Users controller:
def savenew
User.create_new_user(user_params)
redirect_to action: 'index'
end
Add this private method to the Users controller if it does not exist:
private
def user_params
params.require(:user).permit(:email, :password,
:password_confirmation)
end
Add this to config/routes.rb:
match '/savenew', to: 'users#savenew', via: :post
Add this class method to the User model:
def self.create_new_user(params)
#user = User.create!(params)
end
I don't have a separate Admin class in my application. Instead, I defined an admin attribute for Users and check for it with a :validate_admin before_action filter in the UsersController.
I wanted to be able to create a new user from the :index view, so I added a button:
<%= button_to 'New User', '/new_user', class: 'btn btn-primary',
method: :get %>
You might have to tweak the above solution if you have any after_create actions in the User model (e.g. sending a welcome email).

Rails: Custom built ban user functionality not working

I am trying to ban a user, but it is not working as expected, probably because I have no clue what I am doing:
Routes:
resources :users do
collection do
post 'ban'
end
end
class UsersController < ApplicationController
def ban
#user = User.find(params[:id])
if current_user.admin?
#user.banned = true
#user.avatar = nil unless #user.avatar.nil?
#user.banned_by = current_user.full_name
#user.profile = nil unless #user.profile.nil?
#user.save
redirect_to current_user, notice: "User has been banned."
end
end
end
In my view:
<%= link_to "Ban User", ban_users_path(:id => #user.id), :method=>:post %>
For some reason, it passes the parameter "ban" and says cannot find user.
Your routes should be
resources :users do
member do
post 'ban'
end
end
Member routes act on a member, so your request would look like POST /users/1/ban (user #1 being the member). Collection routes are for acting on the entire collection, i.e., POST /users/ban. Your helper should turn into into ban_user_path(#user)
Also I don't think you need to check if their avatar/profile is nil before setting them to nil. Just set them to nil, there is no need to check. If they are already nil, there is no harm done by setting them to nil again.

Button to Execute Ruby Without Javascript

Route
resources :cars do
collection do
get :f01a
end
end
Controller
class CarsController < ApplicationController
def f01a
#user = User.find(params[:id])
#count = Count.find_by_user_id(#user)
#count.increment!(:f02)
redirect_to #user
end
end
View
<%= button_to "add f01", f01a_cars_path %>
I can't get this to work. I need to execute this code from a button.
button_to sends a POST request, but your routing is setup to only accept GET requests. You should change it to:
resources :cars do
collection do
post :f01a
end
end
Since you're using params[:id] in your action, but not sending it in at all, you'll need to pass it in your button_to:
<%= button_to "add f01", f01a_cars_path(:id => something)
(Replace something with whatever ID you want to pass.)

Forgot password action, can I handle post with the same action name?

What is the rails way, to create seperate actions for get or post?
Is it possible to have the same name but decorate the action to only run if its a get or post?
e.g.
'get only'
def forgot_password
end
'post only'
def forgot_passord
end
Rails way is to have a resource -- Each method should have a responsibility.
resources :password_resets
And then you'll let a user reset their password by visiting the form:
link_to 'Lost your Password?', new_password_reset_path
And then the form will post to create a new password_reset... That will send an email with a link to show the password_reset.
form_tag(password_resets_path, :method=>:post) do
When the use enters their updated password, it will update the password_reset.
# in routes.rb
resources :password_resets
# in app/controllers/password_resets.rb
class PasswordResets < ApplicationController
def new
#user = current_user
# render new reset form
end
def create
#user = current_user
#new_password = User.generate_random_password
if #user.update_attributes(:password => #new_password)
UserMailer.password_reset(#new_password).deliver
flash[:notice] = 'Successfully reset your password, check your email!'
else
flash[:error] = 'Could not reset password'
end
redirect_to login_path
end
end
Name them differently and create two routes in config/routes.rb. If you really really want one action doing different things, which is not a great idea, check request.method or request.get?, request.post?, etc.
you can rename the actions in your controller
#get only
def get_forgot_password
end
#post only
def post_forgot_passord
end
and then on your routes.rb
match '/forgot_password' => 'pass_reset#get_forgot_password', :via => 'get'
match '/forgot_password' => 'pass_reset#post_forgot_password', :via => 'post'
the :via option do the trick.

Resources