Rails 404 not found for existing user - ruby-on-rails

I have a rails application in which I'm trying to add a future to ban existing users. My react request is as follows:
handleUserBan(evt) {
evt.preventDefault();
let user_id = evt.target.dataset.userId;
API.put('admin/users/'+user_id, {user: {banned: true}}, function(res) {
this.loadUsers();
}.bind(this))
}
And my 'UsersController' inside admin namespace is:
before_action :enforce_admin!
def show
#user = User.find(ban_params[:id])
end
def update
#user = User.find(ban_params[:id])
prms = ban_params
if prms.include?(:banned)
#user.update_attributes!(prms)
#user.save!
return render :status=>200, :json => {success: true}
end
end
private
def ban_params
params.require(:user).permit(:banned)
end
But I'm getting an error:
ActiveRecord: Record not found
Couldn't find User with 'id'=
Even though a user exists with the selected id in my database. My request is structured as follows:
Request
Parameters:
{"user"=>{"banned"=>"true"},
"id"=>"7",
"format"=>"json"}
And here are my routes for admin namespace:
namespace :admin do
put 'ban_user', :to => 'users#ban_user'
resources :charges
resources :coaches
resources :events
resources :invoices
resources :reviews
resources :users
end

try just params[:id] instead of ban_params[:id]
method ban_params will return only value of banned from params. In this case params contains { id: 'user_id', action: "your action", controller: 'controller', ..., user: { banned: true } }
def ban_params
params.require(:user).permit(:banned)
end

This code is filtering out the id parameter, since it's only permitting the banned parameter:
def ban_params
params.require(:user).permit(:banned)
end
Something like this might work, although it loses the permit constraint:
params.permit(:id, :user => [:banned])

Related

Action Controller exception undefined method `merge' for xxx:String

I'm doing simple Reddit like site. I'm trying to add button to reporting posts. I create report model, using button_to i try to post data to report controller to create it but i received NoMethodError in ReportsController#create undefined method merge' for "post_id":String
model/report.rb
class Report < ApplicationRecord
belongs_to :reporting_user, class_name: 'Author'
has_one :post
end
report_controller.rb
class ReportsController < ApplicationController
def create
report = Report.new(report_params)
flash[:notice] = if report.save
'Raported'
else
report.errors.full_messages.join('. ')
end
end
def report_params
params.require(:post).merge(reporting_user: current_author.id)
end
end
and button in view
= button_to "Report", reports_path, method: :post, params: {post: post}
What cause that problem?
edit:
params
=> <ActionController::Parameters {"authenticity_token"=>"sX0DQfM0rp97q8i16LGZfXPoSJNx15Hk4mmP35uFVh52bzVa30ei/Bxk/Bm40gnFmd2NvFEqj+Wze8ted66kig==", "post"=>"1", "controller"=>"reports", "action"=>"create"} permitted: false>
To start with you want to use belongs_to and not has_one.
class Report < ApplicationRecord
belongs_to :reporting_user, class_name: 'Author'
belongs_to :post
end
This correctly places the post_id foreign key column on reports. Using has_one places the fk column on posts which won't work.
And a generally superior solution would be to make reports a nested resource:
# /config/routes.rb
resources :posts do
resources :reports, only: [:create]
end
# app/controller/reports_controller.rb
class ReportsController
before_action :set_post
# POST /posts/:post_id/reports
def create
#report = #post.reports.new(reporting_user: current_author)
if #report.save
flash[:notice] = 'Reported'
else
flash[:notice] = report.errors.full_messages.join('. ')
end
redirect_to #post
end
private
def set_post
#post = Post.find(params[:post_id])
end
end
This lets you simplify the button to just:
= button_to "Report", post_reports_path(post), method: :post
Since the post_id is part of the path we don't need to send any additional params.
If you do want to let the user pass additional info through a form in the future a better way to create/update resources with params and session data is by passing a block:
#report = #post.reports.new(report_params) do |r|
r.reporting_user = current_user
end
ActionController::Parameters#require returns the value of the required key in the params. Usually this would be an object passed back from a form. In this example require would return {name: "Francesco", age: 22, role: "admin"} and merge would work.
Your view is sending back parameters that Rails is formatting into {post: 'string'}. We would need to see your view code to determine what exactly needs to change.
Update: From the new code posted we can see that the parameter sent back is "post"=>"1". Normally we would be expecting post: {id: 1, ...}.
Update: The button in the view would need the params key updated to something ala params: {post: {id: post.id}} EDIT: I agree that params: {report: { post_id: post}} is a better format.
The problem seems to be in report_params. When you call params.require(:post), it fetches :post from params -> the result is string. And you are calling merge on this string.
I'd recommend change in view:
= button_to "Report", reports_path, method: :post, params: { report: { post_id: post} }
then in controller:
def report_params
params.require(:report).permit(:post_id).merge(reporting_user_id: current_author.id)
end
Note, that I changed also the naming according to conventions: model_id for id of the model, model or model itself.

Overriding serializer when using devise_token_auth and active_model_serializer

I am unable to override the Rails serializer when using devise_token_auth and active_model_serializer for Devise sign_up method.
I would like to customize the returned fields from the Devise sign_up controller when querying my API.
The devise_token_auth gem documentation indicates:
To customize json rendering, implement the following protected controller methods
Registration Controller
...
render_create_success
...
Note: Controller overrides must implement the expected actions of the controllers that they replace.
That is all well and good, but how do I do this?
I've tried generating a UserController serializer like the following:
class UsersController < ApplicationController
def default_serializer_options
{ serializer: UserSerializer }
end
# GET /users
def index
#users = User.all
render json: #users
end
end
but it's only being used for custom methods such as the index method above: it's not being picked up by devise methods like sign_up
I would appreciate a detailed response since I've looked everywhere but I only get a piece of the puzzle at a time.
For the specific serialiser question, here's how I did it:
overrides/sessions_controller.rb
module Api
module V1
module Overrides
class SessionsController < ::DeviseTokenAuth::SessionsController
# override this method to customise how the resource is rendered. in this case an ActiveModelSerializers 0.10 serializer.
def render_create_success
render json: { data: ActiveModelSerializers::SerializableResource.new(#resource).as_json }
end
end
end
end
end
config/routes.rb
namespace :api, defaults: {format: 'json'} do
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
sessions: 'api/v1/overrides/sessions'
}
# snip the rest
Devise sign_up corresponds to devise_token_auth registrations controller and Devise sign_in corresponds to devise_token_auth sessions controller. Therefore when using this gem, customizing Devise sign_in and sign_up methods requires customizing both of these devise_token_auth controllers.
There are two ways to go about this based on what you need to accomplish.
Method #1
If you want to completely customize a method in the controller then follow the documentation for overriding devise_token_auth controller methods here: https://github.com/lynndylanhurley/devise_token_auth#custom-controller-overrides
This is what I did and it's working fine:
#config/routes.rb
...
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
sessions: 'overrides/sessions',
registrations: 'overrides/registrations'
}
...
This will route all devise_token_auth sessions and registrations to LOCAL versions of the controllers if a method exists in your local controller override. If the method does not exist in your local override, then it will run the method from the gem. You basically have to copy the controllers from the gem into 'app/controllers/overrides' and make any changes to any method you need to customize. Erase the methods from the local copy you are not customizing. You can also add callbacks in this way. If you want to modify the response, customize the the render at the end of the method that will return the response as json via active_model_serializer.
This is an example of my sessions controller which adds a couple of custom before_actions to add custom functionality:
#app/controllers/overrides/sessions_controller.rb
module Overrides
class SessionsController < DeviseTokenAuth::SessionsController
skip_before_action :authenticate_user_with_filter
before_action :set_country_by_ip, :only => [:create]
before_action :create_facebook_user, :only => [:create]
def create
# Check
field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
#resource = nil
if field
q_value = resource_params[field]
if resource_class.case_insensitive_keys.include?(field)
q_value.downcase!
end
#q = "#{field.to_s} = ? AND provider='email'"
q = "#{field.to_s} = ? AND provider='#{params[:provider]}'"
#if ActiveRecord::Base.connection.adapter_name.downcase.starts_with? 'mysql'
# q = "BINARY " + q
#end
#resource = resource_class.where(q, q_value).first
end
#sign in will be successful if #resource exists (matching user was found) and is a facebook login OR (email login and password matches)
if #resource and (params[:provider] == 'facebook' || (valid_params?(field, q_value) and #resource.valid_password?(resource_params[:password]) and (!#resource.respond_to?(:active_for_authentication?) or #resource.active_for_authentication?)))
# create client id
#client_id = SecureRandom.urlsafe_base64(nil, false)
#token = SecureRandom.urlsafe_base64(nil, false)
#resource.tokens[#client_id] = { token: BCrypt::Password.create(#token), expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i }
#resource.save
sign_in(:user, #resource, store: false, bypass: false)
yield #resource if block_given?
#render_create_success
render json: { data: resource_data(resource_json: #resource.token_validation_response) }
elsif #resource and not (!#resource.respond_to?(:active_for_authentication?) or #resource.active_for_authentication?)
render_create_error_not_confirmed
else
render_create_error_bad_credentials
end
end
def set_country_by_ip
if !params['fb_code'].blank?
if !params['user_ip'].blank?
#checks if IP sent is valid, otherwise raise an error
raise 'Invalid IP' unless (params['user_ip'] =~ Resolv::IPv4::Regex ? true : false)
country_code = Custom::FacesLibrary.get_country_by_ip(params['user_ip'])
country_id = Country.find_by(country_code: country_code)
if country_id
params.merge!(country_id: country_id.id, country_name: country_id.name, test: 'Test')
I18n.locale = country_id.language_code
else
params.merge!(country_id: 1, country_name: 'International')
end
else
params.merge!(country_id: 1, country_name: 'International')
end
end
end
def create_facebook_user
if !params['fb_code'].blank?
# TODO capture errors for invalid, expired or already used codes to return beter errors in API
user_info, access_token = Omniauth::Facebook.authenticate(params['fb_code'])
if user_info['email'].blank?
Omniauth::Facebook.deauthorize(access_token)
end
#if Facebook user does not exist create it
#user = User.find_by('uid = ? and provider = ?', user_info['id'], 'facebook')
if !#user
#graph = Koala::Facebook::API.new(access_token, ENV['FACEBOOK_APP_SECRET'])
Koala.config.api_version = "v2.6"
new_user_picture = #graph.get_picture_data(user_info['id'], type: :normal)
new_user_info = {
uid: user_info['id'],
provider: 'facebook',
email: user_info['email'],
name: user_info['name'],
first_name: user_info['first_name'],
last_name: user_info['last_name'],
image: new_user_picture['data']['url'],
gender: user_info['gender'],
fb_auth_token: access_token,
friend_count: user_info['friends']['summary']['total_count'],
friends: user_info['friends']['data']
}
#user = User.new(new_user_info)
#user.password = Devise.friendly_token.first(8)
#user.country_id = params['country_id']
#user.country_name = params['country_name']
if !#user.save
render json: #user.errors, status: :unprocessable_entity
end
end
#regardless of user creation, merge facebook parameters for proper sign_in in standard action
params.merge!(provider: 'facebook', email: #user.email)
else
params.merge!(provider: 'email')
end
end
end
end
Notice the use of params.merge! in the callback to add custom parameters to the main controller methods. This is a nifty trick that unfortunately will be be deprecated in Rails 5.1 as params will no longer inherit from hash.
Method #2
If you just want to add functionality to a method in your custom controller, you can get away with subclassing a controller, inheriting from the original controller and passing a block to super as described here:
https://github.com/lynndylanhurley/devise_token_auth#passing-blocks-to-controllers
I have done this to the create method in my custom registrations controller.
Modify the routes as in method #1
#config/routes.rb
...
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
sessions: 'overrides/sessions',
registrations: 'overrides/registrations'
}
...
and customize the create method in the custom controller:
#app/controllers/overrides/registrations_controller.rb
module Overrides
class RegistrationsController < DeviseTokenAuth::RegistrationsController
skip_before_action :authenticate_user_with_filter
#will run upon creating a new registration and will set the country_id and locale parameters
#based on whether or not a user_ip param is sent with the request
#will default to country_id=1 and locale='en' (International) if it's not sent.
before_action :set_country_and_locale_by_ip, :only => [:create]
def set_country_and_locale_by_ip
if !params['user_ip'].blank?
#checks if IP sent is valid, otherwise raise an error
raise 'Invalid IP' unless (params['user_ip'] =~ Resolv::IPv4::Regex ? true : false)
country_code = Custom::FacesLibrary.get_country_by_ip(params['user_ip'])
#TODO check if there's an internet connection here or inside the library function
#params.merge!(country_id: 1, country_name: 'International', locale: 'en')
country_id = Country.find_by(country_code: country_code)
if country_id
params.merge!(country_id: country_id.id, locale: country_id.language_code, country_name: country_id.name)
else
params.merge!(country_id: 1, country_name: 'International', locale: 'en')
end
else
params.merge!(country_id: 1, country_name: 'International', locale: 'en')
end
end
#this will add behaviour to the registrations controller create method
def create
super do |resource|
create_assets(#resource)
end
end
def create_assets(user)
begin
Asset.create(user_id: user.id, name: "stars", qty: 50)
Asset.create(user_id: user.id, name: "lives", qty: 5)
Asset.create(user_id: user.id, name: "trophies", qty: 0)
end
end
end
end

How to use friendly_id history with nested resources

I have User and Campaign models. I nest campaigns under users and URLs look like this:
http://localhost:3000/user-slug/campaign-slug
Routes:
resources :users, :path => '' do
resources :campaigns, :path => ''
end
Campaign model:
class Campaign < ActiveRecord::Base
...
extend FriendlyId
friendly_id :title, use: :history
...
end
My User model doesn't use history.
Campaign controller (from friendly_id guide):
class CampaignsController < ApplicationController
before_filter :find_campaign
def show
#campaign = Campaign.friendly.find(params[:id])
end
private
def find_campaign
#campaign = Campaign.friendly.find(params[:id])
# If an old id or a numeric id was used to find the record, then
# the request path will not match the post_path, and we should do
# a 301 redirect that uses the current friendly id.
if request.path != campaign_path(#campaign)
return redirect_to #campaign, :status => :moved_permanently
end
end
end
When I visit an old slug to trigger redirection I get this error:
ActionController::UrlGenerationError in CampaignsController#show
No route matches {:action=>"show", :controller=>"campaigns", :format=>nil, :id=>nil, :user_id=>#<bunch of other stuff in here>} missing required keys: [:id]
Not sure how I should tweak the redirect method to make it work.
Managed to get it to work with this:
def find_campaign
#campaign = Campaign.friendly.find(params[:id])
#user = #campaign.user
# If an old id or a numeric id was used to find the record, then
# the request path will not match the campaign_path, and we should do
# a 301 redirect that uses the current friendly id.
request_slug = params[:id]
if request_slug != #campaign.slug
return redirect_to user_campaign_path(#user, #campaign), :status => :moved_permanently
end
end
Instead of comparing the request path I compared the request slug which doesn't have the part before the slash. And I needed to redirect to the correct route user_campaign_path.

Publication status, Rails

I want to change the status of hotels in my site. When user create new hotel, he have status "pending". As an administrator, I can upgrade the hotel status from pending to approved or rejected. But I can not approved of in the rejected and vice versa.
I decided to do it with three buttons in admin panel in the place where showing all hotels but this code not working.
routes.rb
HotelAdvisor::Application.routes.draw do
devise_for :admins
devise_for :users
devise_scope :admin do
get '/admin', to: 'devise/sessions#new'
end
post '/rate' => 'rater#create', :as => 'rate'
root to: 'hotels#list'
resources :hotels do
resources :comments
get 'list', on: :collection
post 'comment'
end
resources :ratings, only: :update
namespace :admin do
resources :hotels, :users
end
base_controller
class Admin::BaseController < ApplicationController
before_filter :authenticate_admin!
layout 'admin'
end
hootels_controller(in admin folder)
class Admin::HotelsController < Admin::BaseController
def index
#hotels = Hotel.all
end
def new
#hotel = Hotel.new
end
def create
#hotel = Hotel.new(hotel_params)
#hotel.user_id = current_admin.id
if #hotel.save
render :index
else
render :new
end
end
def update
#hotel = Hotel.find(params[:id])
#hotel.update_attributes(params[:hotel])
end
end
index(in /admin/hotels)
- #hotels.each do |hotel|
.ui.segment
.ui.three.column.grid
.column
.ui.large.image
=image_tag hotel.avatar_url
=link_to hotel_path(hotel), class:'blue ui corner label' do
%i.fullscreen.icon
.column
.ui.message
.header
=hotel.title
.wraper=hotel.description.truncate(300)
.column
=simple_form_for Hotel.find([hotel.id]),:method => :put do |f|
=f.hidden_field :status, value: 'approved'
=f.button :submit, 'Approved', class: 'secondary button'
%br
%hr
I don't know why, but I see this error,
Missing template hotels/update, application/update with...
I think out that in updating rails do not use the controller in the folder admin. Perhaps this is causing the error
Given you didn't implement what to be done, e.g. render, redirect, etc. rails fallbacks to the default, which is to render views with the name of the action, in this case, update.
You might want to take some action, depending on the outcome of update_attributes, for instance:
if #hotel.update_attributes(params[:hotel])
redirect_to(#hotel)
else
render(:edit)
end
You might also want to take a look at Responders to DRY your actions.

User to User Messages in Rails

I've been building messaging in a rails app for users to be able to send each other messages. I've looked at a few gems such as mailboxer but ultimately decided to build my own.
I'm hoping someone can help me put these pieces together. I've been following a similar question's answer here.
I'm testing in the rails console and I keep getting the following error:
undefined method `send_message' for #
How can I fix this?
Controller
class MessagesController < ApplicationController
# create a comment and bind it to an article and a user
def create
#user = User.find(params[:id])
#sender = current_user
#message = Message.send_message(#sender, #user)
flash[:success] = "Message Sent."
flash[:failure] = "There was an error saving your comment (empty comment or comment way to long)"
end
end
Routes
resources :users, :except => [ :create, :new ] do
resources :store
resources :messages, :only => [:create, :destroy]
end
Messages Model
class Message < ActiveRecord::Base
belongs_to :user
scope :sent, where(:sent => true)
scope :received, where(:sent => false)
def send_message(from, recipients)
recipients.each do |recipient|
msg = self.clone
msg.sent = false
msg.user_id = recipient
msg.save
end
self.update_attributes :user_id => from.id, :sent => true
end
end
You are invoking the method on a class level: Message.send_message. For this to work, it would expect a declaration like this:
def self.send_message(from, recipients)
# ...
end
But, you got this instead:
def send_message(from, recipients)
# ...
end
So, either invoke the method on the instance you need it for, or refactor to make it work on a class level.

Resources