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