Ruby on Rails - Why the code inside my controller is not called - ruby-on-rails

I retrieved a Web API coded in Ruby on Rails.
I'm trying to be aware of the code of my predecessor, but fails to understand why my route does not lead to my controller.
My route is configured this way:
resources :bars, :defaults => { :format => 'json' } do
member do
post :subscription
delete :subscription
get :bar_comments
get :events
get :default_event
get :dates
post :store_sections
put :store_sections
get :store_sections, to: 'bars#store_sections_index'
post :tables
put :tables
get :tables, to: 'bars#tables_index'
post :traffic_breakpoint
get :traffic
get :entrance_traffic
put :auto_events
get :fake_counter
post :fake_counter
put :stripe_info
end
collection do
get :favorites
get :test, :defaults => { :format => 'json' }
end
end
I want to catch a get request on bars/favorites url.
Here is how the favorites function is defined on bars_controller
def favorites
logger.debug "FAVORITE BARS!!!#####################################################################"
if current_user.present?
#bars = Bar.average_join.filter_favorite(current_user)
render :index
else
render json: { error: { error_code: 5, error_message: 'You need to be authentificated' } }, status: :unprocessable_entity
end
end
When I call bars/favorites, here is what I see in terminal and logs:
Started GET "/bars/favorites" for 111.111.11.111 at 2017-08-25 22:00:52 +0200
Processing by BarsController#favorites as JSON
Parameters: {"bar"=>{}}
AccessToken Load (0.8ms) SELECT "access_tokens".* FROM "access_tokens" WHERE "access_tokens"."access_token" = 'ebeddb822e1f36848162818a585df3d0' LIMIT 1
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Completed 404 Not Found in 11ms (Views: 0.5ms | ActiveRecord: 1.3ms)
The route is found and the controller too. If I rename the favorites function in controller, I will get an error:
AbstractController::ActionNotFound (The action 'favorites' could not be found for BarsController):
app/helpers/sabayon_middleware.rb:25:in `call'
I can't understand why my log message (logger.debug "FAVORITE BARS!!!.....) doesn't appear on logs, it's like the favorite function was called but nothing of what is supposed to do happens (inside the function).
Plus I have exactly the same structure for another entity (clubs), and it's just working perfectly. Here is how the clubs route is defined:
resources :clubs, :defaults => { :format => 'json' } do
member do
post :subscription
delete :subscription
get :club_comments
get :events
get :default_event
get :dates
post :store_sections
put :store_sections
get :store_sections, to: 'clubs#store_sections_index'
post :tables
put :tables
get :tables, to: 'clubs#tables_index'
post :traffic_breakpoint
get :traffic
get :entrance_traffic
put :auto_events
get :fake_counter
post :fake_counter
put :stripe_info
end
collection do
get :favorites
end
end
And the favorite function in controller:
def favorites
logger.debug "FAVORITES CLUBS #####################################################################"
if current_user.present?
#clubs = Club.average_join.filter_favorite(current_user)
render :index
else
render json: {error: {error_code: 5, error_message: 'You need to be authentificated'}}, status: :unprocessable_entity
end
end
In this case everything works perfectly, when I call clubs/favorites I get:
Started GET "/clubs/favorites" for 111.111.11.111 at 2017-08-25 22:23:26 +0200
Processing by ClubsController#favorites as JSON
Parameters: {"club"=>{}}
AccessToken Load (0.7ms) SELECT "access_tokens".* FROM "access_tokens" WHERE "access_tokens"."access_token" = '5835db5eb54c0d05114ceb86688b2a31' LIMIT 1
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
FAVORITES CLUBS #####################################################################
Club Load (2.4ms) SELECT clubs.*, round(AVG (club_comments.rate), 2) AS rate_average, clubs.*, club_subscriptions.id IS NOT NULL AS is_favorite FROM "clubs" LEFT OUTER JOIN club_comments ON club_comments.club_id = clubs.id INNER JOIN club_subscriptions ON club_subscriptions.club_id = clubs.id AND club_subscriptions.user_id = 1 GROUP BY clubs.id, club_subscriptions.id
Rendered clubs/index.json.jbuilder (4.7ms)
Completed 200 OK in 28ms (Views: 14.4ms | ActiveRecord: 5.5ms)
UPDATE 28.08.2017
I managed to fix the issue by commenting this line in bars_controller:
load_and_authorize_resource
So I don't understand why clubs/favorites is working and bars/favorites, but at least I can work now.
UPDATE 29.08.2017
As asked by #Leito, here is the content of my Ability Class:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
#can :read, :all
default_abilities
if user.persisted?
user_basic_abilities(user)
if user.admin?
club_owner_abilities(user)
can :manage, :all
elsif user.club_owner?
club_owner_abilities(user)
elsif user.user?
end
else
not_connected_abilities
end
end
def default_abilities
can [:index, :show, :events, :dates, :club_comments, :tables_index, :store_sections_index, :letsencrypt, :fake_counter], Club
can [:index, :show], Artist
can [:index, :show, :dates, :tables_index, :premium], Event
can [:index, :show], Ticket
end
def user_basic_abilities(user)
can [:index, :show, :update, :me, :update_password, :club_comments, :customer, :get_customer, :source, :select_source, :delete_source, :bookings], User, id: user.id
can :upload, Picture
can :destroy, Picture, user_id: user.id
can :create, Booking
can :share, Booking do |booking|
booking.owner.id == user.id
end
can :show, Booking do |booking|
booking.user_entrances.where(user_id: user.id).count > 0
end
can :subscription, Club
can :favorites, Club, user_id: user.id
can :create, ClubComment
can [:update, :destroy], ClubComment, user_id: user.id
can [:subscription, :vote], Artist
can [:favorites, :subscription], Event
can :create, Cart
can [:index, :update, :destroy, :cart_items, :user_infos, :order], Cart, user_id: user.id
can [:payment], Order do |order|
order.user.id == user.id
end
end
def club_owner_abilities(user)
can :create, Club
can :create, Artist
can :upload, Picture
can :destroy, Picture, user_id: user.id
can [:update, :tables, :store_sections, :traffic_breakpoint, :traffic, :entrance_traffic, :default_event, :auto_events], Club, id: user.club_id
can [:update, :stats, :votes], Artist
can :create, Event
can [:update, :destroy, :tables, :default_tables, :bookings], Event, club_id: user.club_id
can [:create, :update, :destroy], Promotion do |promo|
promo.event && promo.event.club_id == user.club_id
end
can [:receipt, :confirmation_table, :cancelation_table], Booking
can [:consume, :show, :payments, :refund_payment], Booking do |booking|
booking.event && booking.event.club_id == user.club_id
end
can [:create, :update, :destroy], Ticket do |ticket|
ticket.event && ticket.event.club_id == user.club_id
end
end
def not_connected_abilities
can :create, AccessToken
can [:create, :login], User
# can :register, MobileDevice
end
end

load_and_authorize_resource comes from cancan or cancancan. These are autorhization gems that limit the records a user has access too. Let's see how club/favorites and bar/favorites are authorized differently.
From your Ability class it appears that the user is missing an authorization on can :favorites, Bar similar to the existing:
can :favorites, Club, user_id: user.id
Depending on how you rescue form CanCan::AccessDenied this could explain the response from your controller.

404 is returned by rails when an active record query fails with ActiveRecord::RecordNotFound. So I say, either User or Bar or any other related find methods are raising an error (due to not being available in DB).

Related

cancancan row based filter

I have declared a role ability in the cancancan gem (rails 5, postgres, devise):
can [:update, :show, :destroy, :index], SalesDatum do |datum|
datum.try(:user_id) == user.id #the ids just access the id's from the user object that is passed into can can and from the table
end
SalesDatum has a user_id field.
This works on the show actions, because I can only show the SalesDatum that has the logged in user_id For example:
http://localhost:8080/projects/19/sales_data/961
correctly gets authenticated because the logged in user_id matches the user_id on the sales_data
http://localhost:8080/projects/19/sales_data/800
correctly does not get authenticated because the logged in user_id does not match the user_id on the sales_data
However when I go to the get index action:
http://localhost:8080/projects/19/sales_data
it shows all of the sales data from the #sales_data variable. So, it would show the data.id 800 and 961.
sales data controller:
load_and_authorize_resource
def index
#sales_data = SalesDatum.where(project_id:params[:project_id])
end
How do I get the index action to only show the data with the relevant user_id? Isn't cancan supposed to filter that according to the user_id?
You need to define the rule with the hash syntax.
can [:update, :show, :destroy, :index], SalesDatum, user_id: user.id
you may also want to define the edit action and therefore:
can [:edit, :update, :show, :destroy, :index], SalesDatum, user_id: user.id
which can be simplified into:
can :manage, SalesDatum, user_id: user.id
if SalesDatum has a belongs_to :user defined you can also write:
can :manage, SalesDatum, user: user

Rails 404 not found for existing user

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])

Rails params for nested forms

I've been battling for days now to get data to save for my nested form. I basically want to be able to store a users reason for cancelling a project, along with the last stage of the project before it was cancelled. But I just can't seem to get the actions cancel, cancel_save, and cancel_params to play nicely!
Controller
before_action :correct_user, only: [:show, :edit, :update, :destroy, :cancel, :cancel_save]
...
def cancel
#project.build_project_close_reason
end
def cancel_save
#project.build_project_close_reason(cancel_params)
#project.update(project_status_id: 10)
redirect_to root_path, notice: 'Project has been successfully cancelled.'
end
private
def correct_user
#user = current_user
#project = current_user.projects.find_by(id: params[:id])
end
redirect_to user_projects_path, notice: "You are not authorised to view this project" if #project.nil?
end
def cancel_params
params.require(:project).permit(project_close_reason_attributes: [:comment]).merge(project_close_reason_attributes: [project_id: #project.id, last_status_id: #project.project_status_id ])
end
Models
class Project < ApplicationRecord
belongs_to :user
has_one :project_close_reason
accepts_nested_attributes_for :project_close_reason #adding this seemed to make no difference?
end
class ProjectCloseReason < ApplicationRecord
belongs_to :project
end
class User < ApplicationRecord
... # All standard devise stuff
has_many :projects
end
Nested form in the view
<%= form_for([#user, #project], url: {action: "cancel_save"}, method: :post) do |f| %>
<%= fields_for :project_close_reason do |pcr| %>
<div class="field entry_box">
<%= pcr.label "Why are you cancelling this project? (This helps us improve!)" %>
<%= pcr.text_area :comment, class: "form-control entry_field_text" %>
</div>
<% end %>
<div class="actions center space_big">
<%= f.submit "Cancel Project", class: "btn btn-lg btn-warning" %>
</div>
<% end %>
Routes
devise_for :users
devise_for :admins
resources :users do
resources :projects do
get "cancel", :on => :member
post "cancel" => 'projects#cancel_save', :on => :member
end
end
This is the error I get when I try and submit the form:
ActionController::ParameterMissing in ProjectsController#cancel_save
param is missing or the value is empty: project. It references the cancel_params action
Thanks for any help!
UPDATE
Here is the log when I call cancel_save
Started POST "/users/2/projects/10/cancel" for ::1 at 2016-09-29 10:03:44 +0200
Processing by ProjectsController#cancel_save as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"h6K+VFyjW/dV189YOePWZm+Pmjey50xAMQIJb+c3dzpEaMv8Ckh3jQGOWfVdlfVH/FxolbB45fXvTO0cdplhkg==", "project_close_reason"=>{"comment"=>"b"}, "commit"=>"Cancel Project", "user_id"=>"2", "id"=>"10"}
User Load (11.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 2], ["LIMIT", 1]]
Project Load (0.7ms) SELECT "projects".* FROM "projects" WHERE "projects"."user_id" = $1 AND "projects"."id" = $2 LIMIT $3 [["user_id", 2], ["id", 10], ["LIMIT", 1]]
ProjectCloseReason Load (0.2ms) SELECT "project_close_reasons".* FROM "project_close_reasons" WHERE "project_close_reasons"."project_id" = $1 LIMIT $2 [["project_id", 10], ["LIMIT", 1]]
Completed 400 Bad Request in 22ms (ActiveRecord: 12.1ms)
ActionController::ParameterMissing (param is missing or the value is empty: project):
The error is saying that there is no params with name project, which you are trying to require in params.require(:project) line in cancel params. I think this is happening beacause in form_for you have mentioned [#user, #project], that means user's project. So the project params must be inside user's. Check your log when you call cancel_save. There must be something like {user => {project => { } }.
In the end I got it to work without requiring projects at all. This is my revised cancel_params in my projects_controller. For anyone else looking at doing this kind of additive databasing, I highly recommend skipping params for nested form if you can do it this way. So much simpler!
private
def cancel_params
params.require(:project_close_reason).permit(:comment).merge(project_id: #project.id, last_status_id: #project.project_status_id )
end

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.

how to protect a send_file action with cancan

I can't seem to approve authorization for the web_videos_display action. I can make it work by using the skip_authorize_resource but then any user can access the URL for the file by knowing the :id. I need to require access to the asset to "see" the file.
thank you for looking into this.
assets_controller.rb
...
class AssetsController < ApplicationController
load_and_authorize_resource :team
load_and_authorize_resource :through => :team
# skip_authorize_resource :only => [:web_videos_display, ...]
# skip_authorize_resource :team, :only => [:web_videos_display, ...]
...
def web_videos_display
# #asset = Asset.find(params[:id]) #loaded by cancan
#file = "#{Rails.root}#{#asset.webVideos.last.file}"
send_file #file, :disposition => 'inline', :x_sendfile=>true
end
...
routes.rb
resources :teams, :path => '', :except => [:index] do
...
resources :assets, :path => '' do
...
get 'web_videos_display'
...
end
end
show.html.erb
...
<%= team_asset_web_videos_display_path(#team, #asset, :id => #asset.id, :team_id => #team.id) %>
...
ability.rb
...
can :read, Team, :memberships => {:id => user.membership_ids}
can :manage, Asset, :team => { :id => user.team_ids }
can :web_videos_display, Asset, :team => { :id => user.team_ids }
...
update in response to #ryanb comment
that returns
1.9.2p318 :006 > ability.can?(:web_videos_display, asset)
Team Load (0.2ms) SELECT "teams".* FROM "teams" WHERE "teams"."id" = 1 LIMIT 1
=> true
but
in development mode it still returns
Started GET "/video-pros/test-1/web_videos_display?id=10" for 127.0.0.1 at 2012-11-09 16:40:19 -0800
Processing by AssetsController#web_videos_display as */*
Parameters: {"id"=>"10", "team_id"=>"video-pros", "asset_id"=>"test-1"}
Team Load (0.1ms) SELECT "teams".* FROM "teams" WHERE "teams"."slug" = 'video-pros' LIMIT 1
Team Load (0.2ms) SELECT "teams".* FROM "teams" WHERE "teams"."admin_user_id" = 1 LIMIT 1
CACHE (0.0ms) SELECT "teams".* FROM "teams" WHERE "teams"."slug" = 'video-pros' LIMIT 1
Access denied on show #<Team id: 1, name: "Video Pros", email: nil, phone: nil, website: nil, slug: "video-pros", admin_user_id: 1, created_at: "2012-11-06 22:43:11", updated_at: "2012-11-06 22:43:11", payment_received: nil>
Redirected to http://0.0.0.0:3000/
Completed 302 Found in 73ms>
thanks Ryan
send_file shouldn't make a difference since the authorization happens in a before_filter before the action gets triggered. As far as I can tell it looks correct so there may be something else going on. Have you tried going through this debugging page? What happens if you mimic what the controller authorization is doing in the console?
user = User.first # fetch any user you want to test abilities on
team = Team.first # any model you want to test against
asset = team.assets.first
ability = Ability.new(user)
ability.can?(:show, team) # see if it allows access
ability.can?(:web_videos_display, asset) # see if it allows access
updated :read to :show action for team

Resources