how to protect a send_file action with cancan - ruby-on-rails

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

Related

How to create controller and views for has_many and through relationship in ruby on rails

please go easy on me as I'm just a beginner, I am breaking my head on this issue for the past 2 days now. To summarize, I have a user and productplan , when a user selects a certain product plan(radiobutton or checkbox) , I want the current user id and selected product id to be stored in the join table called userproducts
But for some reason I could not store the data , I have no clue where I am going wrong, any help appreciated, thanks.
Models:
class User < ActiveRecord::Base
has_many :userproducts
has_many :productplans, :through=>:userproducts
end
class productplan < ApplicationRecord
has_many :userproducts
has_many :users, :through => :userproducts
end
class Userproduct < ApplicationRecord
belongs_to :user
belongs_to :productplan
end
ProductPlan Controllers:
class ProductPlanController < ApplicationController
before_action :authenticate_user!
def new
#user=User.new
end
end
def create
#user = User.create(user_params)
if #user.save
render :action => 'index'
else
render :action =>'new'
end
private
def user_params
params.require(:user).permit(:productplan_ids => [])
end
end
Views:
productplan/_form.html.erb
<%= form_for #user,:url=> productplan_index_path,:method => :post do
|f|%>
<%= f.collection_check_boxes :productplan_ids, ProductPlan.all, :id,
:productplan_name %>
<%= f.submit %>
<% end %>
Since there are less examples on the controller and views regarding has_many through I am pretty sure I have many mistakes in it. Also when I try submitting the form I get this log:
enterStarted POST "/Productplan" for 127.0.0.1 at 2017-11-28 11:08:29 -0600
Processing by ProductPlanController#create as HTML
Parameters
{"utf8"=>"✓","authenticity_token"=>
"mi9RwHCLPKItXai67t5iX0RYrDoE6TN9T
iZzJFYELbKVDlHbhpeNmOC0q2gu1iXyWNaUqGSEsPrBEjcUSE2yYw==", "user"= >
{"productsku_ids"=>["", "4", "7"]}, "commit"=>"Create User"}
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1
ORDER BY "users"."id" ASC LIMIT $2 [["id", 3], ["LIMIT", 1]]
ProductPlan Load (2.6ms) SELECT "productplans".* FROM "productplans" WHERE
"productplans"."id" IN (4, 7)
(0.5ms) BEGIN
Provider Load (0.5ms) SELECT "providers".* FROM "providers" WHERE
"providers"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
CACHE Provider Load (0.0ms) SELECT "providers".* FROM "providers" WHERE
"providers"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.6ms) ROLLBACK
Rendering ups/new.html.erb within layouts/application
ProductPlan Load (0.9ms) SELECT "productplans".* FROM "productplans"
Rendered productplans/_form.html.erb (4.6ms)
Rendered productplans/new.html.erb within layouts/application (6.4ms)
Rendered _navbar.html.erb (0.7ms)
Completed 200 OK in 191ms (Views: 102.1ms | ActiveRecord: 14.6ms) here
For some reason it rollbacks,also I need to get the current user too.
dependent_new GET /dependent/new(.:format) dependent#new
dependent_create GET /dependent/create(.:format) dependent#create
summary_root GET /summary/index(.:format) summary#index
home_plans GET /home/plans(.:format) home#plans
thanks GET /thanks(.:format) charges#thanks
authenticated_root GET / home#roothome
productplan_index GET /productplan(.:format) productplan#index
POST /productplan(.:format) productplan#create
new_productplan GET /productplan/new(.:format) productplan#new
edit_productplan GET /productplan/:id/edit(.:format) productplan#edit
productplan GET /productplan/:id(.:format) productplan#show
PATCH /productplan/:id(.:format) productplan#update
PUT /productplan/:id(.:format) productplan#update
DELETE /productplan/:id(.:format) productplan#destroy
userproducts GET /userproducts(.:format) userproducts#index
POST /userproducts(.:format) userproducts#create
new_userproduct GET /userproducts/new(.:format) userproducts#new
edit_userproduct GET /userproducts/:id/edit(.:format) userproducts#edit
userproduct GET /userproducts/:id(.:format) userproducts#show
PATCH /userproducts/:id(.:format) userproducts#update
PUT /userproducts/:id(.:format) userproducts#update
DELETE /userproducts/:id(.:format) userproducts#destroy
ups POST /ups(.:format) ups#create
new_up GET /ups/new(.:format) ups#new
root GET / home#index
new_user_session GET /users/sign_in(.:format) devise/sessions#new
user_session POST /users/sign_in(.:format) devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
new_user_password GET /users/password/new(.:format) devise/passwords#new
edit_user_password GET /users/password/edit(.:format) devise/passwords#edit
user_password PATCH /users/password(.:format) devise/passwords#update
PUT /users/password(.:format) devise/passwords#update
POST /users/password(.:format) devise/passwords#create
SO I modified my as per #jvillian told to ,but the problem still persists,here is my new code.
Please note I renamed user products to ups,
Model:
class User < ActiveRecord::Base
has_many :ups
has_many :productplans, through: :ups
end
class productplan < ApplicationRecord
has_many :ups
has_many :users, :through => :ups
end
class Up < ApplicationRecord
belongs_to :user
belongs_to :productplan
end
Ups Controller:
class UpsController < ApplicationController
before_action :authenticate_user!
def create
current_user.productplans<<Productplan.where(id: productplans_ids)
render :action => 'index'
end
private
def productplan_ids
params.require(:up).permit(productplan_ids:[])
end
end
Views ups/_form.html.erb
<%= form_for :up,:url=> ups_path do |f| %>
<%= f.collection_check_boxes :productplan_ids, Productplan.all, :id,
:productplan_name %>
<%= f.submit %>
<% end %>
Logs:
Started GET "/ups" for 127.0.0.1 at 2017-11-28 14:43:50 -0600
Processing by UpsController#index as HTML
User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1
ORDER BY "users"."id" ASC LIMIT $2 [["id", 3], ["LIMIT", 1]]
Rendering ups/index.html.erb within layouts/application
Productplan Load (0.7ms) SELECT "productplans".* FROM "productplans"
Rendered ups/_form.html.erb (42.5ms)
Rendered ups/index.html.erb within layouts/application (47.7ms)
Rendered _navbar.html.erb (0.9ms)
Completed 200 OK in 347ms (Views: 235.0ms | ActiveRecord: 15.0ms)
Started POST "/ups" for 127.0.0.1 at 2017-11-28 14:43:53 -0600
Processing by UpsController#create as HTML
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"g7lLydleU7J14G7N9avWSK87E4BbkPQ9sdL
Q9X2bbSCMmEvSL0LiiLgJbR81o5Hls7UrEjv9d7o+5pTFY9Ly8Q==", "up"=>
{"productplan_ids"=>["", "4"]}, "commit"=>"Save Up"}
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1
ORDER BY "users"."id" ASC LIMIT $2 [["id", 3], ["LIMIT", 1]]
Productplan Load (0.7ms) SELECT "productplans".* FROM "productplans" WHERE
"productplans"."id" = $1 [["id", nil]]
(0.4ms) BEGIN
(0.3ms) COMMIT
Rendering ups/index.html.erb within layouts/application
Productplan Load (0.6ms) SELECT "productplans".* FROM "productplans"
Rendered ups/_form.html.erb (4.9ms)
Rendered ups/index.html.erb within layouts/application (7.9ms)
Rendered _navbar.html.erb (1.0ms)
Completed 200 OK in 201ms (Views: 183.4ms | ActiveRecord: 3.0ms)
route.rb
devise_scope :user do
# write all your routes inside this block
resources :productplan
resources :ups
end
There is still no data inserted in the Ups table , no error as well. Although according to log file it doesnt rollback but still the issue persists.
UPDATE: AFTER CODE MODIFICATIONS FROM OP
Much closer!
As you can see here:
Productplan Load (0.7ms) SELECT "productplans".* FROM "productplans" WHERE "productplans"."id" = $1 [["id", nil]]
we didn't get productplan_ids quite right (as indicated by nil). If, in console, you do:
> params = ActionController::Parameters.new(up: {productplan_ids: ["","4"]})
> params.require(:up).permit(productplan_ids: [])
=> {"productplan_ids"=>["", "4"]}
you can see that the productplan_ids method is returning a hash (well, not exactly, but close enough for now). But, you want the value for the :productplan_ids key. So, try:
def productplan_ids
params.require(:up)[:productplan_ids]
end
Which should give you:
> params.require(:up)[:productplan_ids]
=> ["", "4"]
the array you're looking for.
ORIGINAL ANSWER
I'm going to take a swing at this, but it's going to be tough.
Okay, let's assume:
You have a logged in user that is available through current_user in your create action (it looks like you're using Devise?)
You have already created your ProductPlans
In routes.rb, you have resources :user_products (not userproducts, see note later on...)
Your form_for should look like:
<%= form_for :user_product, user_products_path do |f| %>
<%= f.collection_check_boxes :product_plan_ids, ProductPlan.all, :id, :product_plan_name %>
<%= f.submit %>
<% end %>
Note: You can use the symbol :user_product instead of the instance variable #user because you're not basing any form fields on #user.
Note: A form automatically does a post, so you don't need to specify method.
Then, UserProductsController (not ProductPlanController since you're creating one or more user_products not product_plans) should look something like:
class UserProductsController < ApplicationController
def create
current_user.product_plans << ProductPlan.where(id: product_plan_ids)
render action: some_success_condition ? 'index' : 'new'
end
private
def product_plan_ids
params.require(:user_product).permit(product_plan_ids: [])
end
end
Note: You probably don't need a new action because you don't really need to instantiate anything (at least not based on anything you've shown).
Note: You'll need to sort out what some_success_condition is. But it's certainly not #user.save. Because you're creating user_products, not a user.
ALSO, You say:
params.require(:user).permit(:productplan_ids => [])
But your params are:
{
...,
"user"=> {
"productsku_ids"=>["", "4", "7"]
},
"commit"=>"Create User"
}
You permit productplan_ids (should be product_plan_ids), but have productsku_ids (should be product_sku_ids) in your params.
Some other things:
You should read through the ruby style guide to get your naming correct.
Class names should be CamelCase. So, ProductPlan, not Productplan.
Symbols and variables should be SnakeCase. So, :user_products, not :userproducts.
Controller names should be plural, not singular, if they are resource-oriented. So, ProductPlansController, not ProductPlanController. (I assume you have all the usual RESTful actions for ProductPlan, like new, create, edit, update, etc.)
AND, it's more conventional these days to do through: :user_products than :through => :user_products.
So:
class User < ActiveRecord::Base
has_many :user_products
has_many :product_plans, through: :user_products
end
class ProductPlan < ApplicationRecord
has_many :user_products
has_many :users, through: :user_products
end
class UserProduct < ApplicationRecord
belongs_to :user
belongs_to :product_plan
end
Also, you have a bunch of mis-matched def ... end statements:
class ProductPlanController < ApplicationController
before_action :authenticate_user!
def new
#user=User.new
end
end # <= extra 'end' here
def create
#user = User.create(user_params)
if #user.save
render :action => 'index'
else
render :action =>'new'
end
# <= missing 'end' here
private
def user_params
params.require(:user).permit(:productplan_ids => [])
end
end
I assume that's some sort of copy-paste error
I have no idea why you're doing:
def create
#user = User.create(user_params)
if #user.save
render :action => 'index'
else
render :action =>'new'
end
end
In your ProductPlansController. It's ProductPlansController. It should create a ProductPlan, not a User.

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

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

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

best_in_place with a many to many relationship in rails 4

I have made a github repo that you can find here just for this question. I have 3 models:
class User < ActiveRecord::Base
has_many :user_countries
has_many :event_countries,
-> { where(user_countries: {:event => true}) },
:through => :user_countries,
:source => :country
has_many :research_countries,
-> { where(user_countries: {:research => true}) },
:through => :user_countries
:source => :country
end
class UserCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class Country < ActiveRecord::Base
# ...
end
So a user should be able to choose event_countries and research_countries.
here's my users controller (nothing complicated):
class UsersController < ApplicationController
respond_to :html, :json
before_action :get_user, only: [:show, :edit, :update]
before_action :get_users, only: [:index]
def index
end
def show
end
def edit
end
def update
#user.update_attributes(user_params)
respond_with #user
end
private
def get_user
#user = User.find(params[:id])
end
def get_users
#users = User.all
end
def user_params
params.require(:user).permit(:first_name, :event_countries => [:id, :name])
end
end
And here's my user show page:
<%= best_in_place #user, :first_name %>
<p> event countries: </p>
<%= best_in_place #user, :event_countries, place_holder: "click here to edit", as: :select, collection: Country.all.map {|i| i.name} %>
<%= link_to "users index", users_path %>
So there's really nothing complicated here. I can also succesfully edit my users first name, best_in_place is working fine.
The question is: how do I edit the event_countries ? As you can see I tried to use the collection option with the countries but when I try to select a country I get the following:
Processing by UsersController#update as JSON
Parameters: {"user"=>{"event_countries"=>"3"}, "authenticity_token"=>"l5L5lXFmJFQ9kI/4klMmb5jDhjmtQXwn6amj1uwjSuE=", "id"=>"6"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 6]]
(0.1ms) begin transaction
(0.1ms) rollback transaction
Completed 500 Internal Server Error in 3ms
NoMethodError (undefined method `each' for nil:NilClass):
app/controllers/users_controller.rb:17:in `update'
I don't understand what it's doing, I know it must be a problem with the collection option. If you need to see any file please check my repo here:
https://github.com/spawnge/best_in_place_join_models_twice . I have a spent a lot of time on this any answer/suggestion would be greatly appreciated :)
update:
I have tried this:
<%= best_in_place #user, :event_country_ids, as: :select, collection: Country.all.map { |i| i.name }, place_holder: "click here to edit", html_attrs: { multiple: true } %>
and I have added :event_country_ids to my user params:
params.require(:user).permit(:first_name, :event_country_ids)
And now I can see all the countries but when I select one here's what I get:
Started PUT "/users/3" for 127.0.0.1 at 2014-12-18 01:19:27 +0000
Processing by UsersController#update as JSON
Parameters: {"user"=>{"event_country_ids"=>"1"}, "authenticity_token"=>"aZAFIHgzdSL2tlFcGtyuu+XIJW3HX2fwQGHcB9+iYpI=", "id"=>"3"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 3]]
(0.0ms) begin transaction
Country Load (0.0ms) SELECT "countries".* FROM "countries" WHERE "countries"."id" = ? LIMIT 1 [["id", 1]]
Country Load (0.0ms) SELECT "countries".* FROM "countries" INNER JOIN "user_countries" ON "countries"."id" = "user_countries"."country_id" WHERE "user_countries"."event" = 't' AND "user_countries"."user_id" = ? [["user_id", 3]]
SQL (0.1ms) DELETE FROM "user_countries" WHERE "user_countries"."user_id" = ? AND "user_countries"."country_id" = 2 [["user_id", 3]]
SQL (0.0ms) INSERT INTO "user_countries" ("country_id", "event", "user_id") VALUES (?, ?, ?) [["country_id", 1], ["event", "t"], ["user_id", 3]]
(20.9ms) commit transaction
Completed 204 No Content in 28ms (ActiveRecord: 21.3ms)
As you can see it seems that it insert the right content: INSERT INTO "user_countries" ("country_id", "event", "user_id") VALUES (?, ?, ?) [["country_id", 1], ["event", "t"], ["user_id", 3]] However I get the Completed 204 No Content just after that. I don't understand when I refresh the page the input is empty. Any suggestion ?
Update 2:
I checked in the console and it works, I can add event_countries to a user. However it doesn't display the user's event_countries when I refresh the page, I guess that's because I'm using the event_country_ids object.
I think the following code should work:
<%= best_in_place #user, :event_country_ids, as: :select,
collection: Country.all.each_with_object({}) { |i, memo| memo[i.id] = i.name },
place_holder: "click here to edit",
html_attrs: { multiple: true } %>
Assuming you want the user to be able to assign multiple event_countries.
Reference
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many, specifically the collection_singular_ids= method created by has_many.
https://github.com/bernat/best_in_place#select, the structure of the collection needs to be a hash. For each key => value pair, the key is what's submitted with the form and the value is what's displayed to the user.
http://ruby-doc.org/core-2.1.5/Enumerable.html#method-i-each_with_object, each_with_object is a part of the core Ruby library.

Rails file download not working in JSON API with devise token authentication

After ruining one working day, I am desperate for help.
I have a Rails 4.1.0 application which also exposes a JSON API. In the API I am using devise with token_authentication. There is an Image model with paperclip attachment. While trying to download the image, the postman plugin shows success with status 200. But the image isn't downloaded.
I have tried using both the send_file and send_data method. For both the server shows two log entries. While debugging also, I can see that the 'authenticate_user_from_token' method of ApiController gets executed twice and in the second time the 'X-Auth-Token header' is missing(which is obvious because I am not sending this second request). This results in a 401 Unauthorized error and the file isn't downloaded(See logs at the bottom). I am not sure why send_file or send_data method is causing a second request to server.
Here is my code.
controllers/api/v1/images_controller.rb
class Api::V1::ImagesController < Api::V1::ApiController
def download
#image = Image.find(params[:id])
# Tried send_file
send_file #image.pic.path(:original)
# Tried send_data
data = File.read(#image.pic.path(:original))
send_data data, filename: #image.pic.original_filename, type: #image.pic.content_type
end
end
controllers/api/v1/api_controller.rb
class Api::V1::ApiController < ApplicationController
respond_to :json
before_filter :authenticate_user_from_token!
before_filter :authenticate_user!
protect_from_forgery with: :null_session
private
def authenticate_user_from_token!
user_email = params[:email].presence
user = user_email && User.find_by_email(user_email)
auth_token = request.headers["X-Auth-Token"]
if user && Devise.secure_compare(user.authentication_token, auth_token)
sign_in user, store: false
end
end
end
controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
models/image.rb
class Image < ActiveRecord::Base
PAPERCLIP_ROOT = "#{Rails.root}/storage"
has_attached_file :pic, :styles => {:original => "720x720", :medium => "120x120", :thumbnail => "40x40"}
end
config/routes.rb
Rails.application.routes.draw do
#API
namespace :api , defaults: {format: :json} do
namespace :v1 do
resources :images do
member do
get 'download'
end
end
devise_scope :user do
post "/sign_in", :to => 'sessions#create'
post "/sign_out", :to => 'sessions#destroy'
end
end
end
end
server_log
Started GET "/api/v1/images/14/download?email=user#example.com" for 127.0.0.1 at 2014-09-21 13:57:25 +0530
ActiveRecord::SchemaMigration Load (0.7ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by Api::V1::ImagesController#download as */*
Parameters: {"email"=>"user#example.com", "id"=>"14", "image"=>{}}
.
.
Sent file /Users/rajveershekhawat/workspace/dine_connect/storage/images/pics/000/000/014/original/images.jpg (0.5ms)
Completed 200 OK in 14067ms (ActiveRecord: 13.5ms)
Started GET "/api/v1/images/14/download?email=user#example.com" for 127.0.0.1 at 2014-09-21 13:57:40 +0530
Processing by Api::V1::ImagesController#download as HTML
Parameters: {"email"=>"user#example.com", "id"=>"14"}
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'user#example.com' LIMIT 1
Completed 401 Unauthorized in 3462ms
Please help. Thanks a lot.
Update:
Can somebody tell me why there are two request logs?
Can we even download, an image over json api whithout using Base64 encoding, like a normal download?
Try using send_data instead of send_file?
You might have to do a read on the paperclip file path.

Resources