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
Related
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.
I have my app/views/devise views, and all I want to do is be able to pass a few instance variables into my edit.html.erb.
Is there a way for me to do that without having to move my registrations/edit.html.erb to another folder?
Edit 1
This is what I have done.
app/views/users/registrations_controller.rb:
class Users::RegistrationsController < Devise::RegistrationsController
def edit
if current_user_subscribed?
#plan = Stripe::Plan.retrieve(current_user.plan_name)
end
super
end
end
This is my routes.rb:
devise_for :users, controllers: {
invitations: 'invitations',
registrations: 'users/registrations'
},
path_names: { :sign_up => "register",
:sign_in => "login",
:sign_out => "logout",
:settings => "settings" }
This is my app/views/devise/registrations/edit.html.erb:
<% if current_user_subscribed? %>
<div class="col-md-12 subscribed-card">
<%= render partial: "subscriptions/card_brand", locals: { brand: current_user.card_brand, plan: #plan } %>
</div>
<% end %>
This is my app/views/subscriptions/_card_brand.html.erb
<div class="payment-card">
<div class="row">
<h2>Subscribed To: <%= plan.name %> for <%= formatted_price(plan.amount) %></h2>
</div>
</div>
But this is the error I am getting:
NoMethodError at /settings
undefined method `name' for nil:NilClass
Edit 2
This is my server log, which doesn't seem to be sending the request to Users::RegistrationsController like I would want:
Processing by Devise::RegistrationsController#edit as HTML
User Load (3.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 7], ["LIMIT", 1]]
Rendering devise/registrations/edit.html.erb within layouts/devise
Role Load (3.4ms) SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'coach') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 7]]
School Load (1.6ms) SELECT "schools".* FROM "schools" WHERE "schools"."school_type" IN (1, 2)
Rendered subscriptions/_card_brand.html.erb (12.2ms)
Rendered devise/registrations/edit.html.erb within layouts/devise (904.5ms)
Completed 500 Internal Server Error in 994ms (ActiveRecord: 35.1ms)
What am I missing?
You want to expand on the default behaviour of the Devise::RegistrationsController, without moving the files around.
The easiest solution in my opinion would be to inherit the registrations controller into a new class and override the controller used.
Start by creating a new controller, let's say RegistrationsController -> app/controllers/registrations_controller.rb.
Inherit the Devise::RegistrationsController to keep all behaviours and views and add your code to edit action under super.
class RegistrationsController < Devise::RegistrationsController
def edit
super
#my_instance = "Hello World"
end
end
Then point devise's routes to the correct registrations controller.
devise_for :users, :controllers => {:registrations => "registrations"}
Now you should have access to your instance within app/views/devise/registrations/edit.html.erb.
https://gist.github.com/kinopyo/2343176
OP Edit
Make sure to specify the devise_scope to the right path:
devise_scope :user do
get "settings", to: "users/registrations#edit"
end
Which is based on the fact that my version gets routed to the controller users/registrations.
Devise's sign_in_after_reset_password setting supposedly signs in my user after successfully resetting the password.
However, what it seems to do in practice is to redirect back to '/', which ultimately results in showing the login page.
Why isn't it signing me in?
In user.rb:
devise :database_authenticatable,
:recoverable,
:validatable,
:encryptable
In application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
before_action :authenticate_user!, unless: :devise_controller?
#omitting business logic filters and helpers
end
Custom routes (solely to change the paths. Have tried to keep the resulting route list identical to the defaults other than the names):
devise_for :users, skip: [ :passwords, :sessions ]
devise_scope :user do
get 'users/login' => 'devise/sessions#new', as: 'new_user_session'
post 'users/login' => 'devise/sessions#create', as: 'user_session'
delete 'users/logout' => 'devise/sessions#destroy', as: 'destroy_user_session'
post 'users/password' => 'devise/passwords#create', as: 'user_password'
get 'users/password/forgot' => 'devise/passwords#new', as: 'new_user_password'
get 'users/password/reset' => 'devise/passwords#edit', as: 'edit_user_password'
patch 'users/password' => 'devise/passwords#update', as: nil
put 'users/password' => 'devise/passwords#update', as: nil
end
The investigation so far:
I had a lot of minor deviations from the structure of a sample devise application which I have been golfing back bit by bit to try and make things easier to figure out. So now my authenticate_user! filter is being specified in the same place as most examples.
Something I noticed is that after successfully resetting the password, it isn't clearing the reset password token. Maybe that's normal, it's just suspicious.
I have debugged in Devise's PasswordsController itself and after it executes the sign_in line, signed_in? does return true.
I have attempted to breakpoint inside signed_in? at the point of requesting the root path, but it seems like I get the 401 error from the web server without signed_in? ever being called. So perhaps Warden is directly kicking me out before the application even gets to run.
I'm starting to run out of avenues for investigation, so I thought I would post it here in case anyone had seen the exact same issue before.
Logs of the event:
Started PUT "/users/password" for ::1 at 2016-07-13 17:14:44 +1000
Processing by Devise::PasswordsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"[FILTERED]", "user"=>{"reset_password_token"=>"[FILTERED]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Change my password"}
Can't verify CSRF token authenticity
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."reset_password_token" = ? ORDER BY "users"."id" ASC LIMIT 1 [["reset_password_token", "[FILTERED]"]]
(0.0ms) begin transaction
Note Load (0.1ms) SELECT "notes".* FROM "notes" WHERE "notes"."notable_id" = ? AND "notes"."notable_type" = ? [["notable_id", 2], ["notable_type", "User"]]
Organisation Load (0.1ms) SELECT "organisations".* FROM "organisations" WHERE "organisations"."id" = ? LIMIT 1 [["id", 1]]
User Exists (0.1ms) SELECT 1 AS one FROM "users" WHERE ("users"."login" = 'test1' AND "users"."id" != 2) LIMIT 1
(0.0ms) commit transaction
Redirected to http://localhost:3000/
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 2]]
Completed 302 Found in 44ms (ActiveRecord: 1.7ms)
Started GET "/" for ::1 at 2016-07-13 17:14:44 +1000
Processing by SessionsController#welcome as HTML
Completed 401 Unauthorized in 0ms (ActiveRecord: 0.0ms)
Started GET "/users/login" for ::1 at 2016-07-13 17:14:44 +1000
Processing by Devise::SessionsController#new as HTML
Rendered users/shared/_links.html.erb (0.1ms)
Rendered users/sessions/new.html.erb within layouts/application (2.2ms)
Completed 200 OK in 148ms (Views: 147.3ms | ActiveRecord: 0.0ms)
Not really answering the question itself, but I found a workaround which is at least a solution, even if it's a bad solution.
In my ApplicationController:
after_action :fix_login_after_password_reset, if: ->(controller) {
controller.controller_path == 'devise/passwords' &&
controller.action_name == 'update'
}
def fix_login_after_password_reset
user = current_user
if user && user.errors.empty?
user = User.where(id: user.id).first
sign_out
bypass_sign_in(user)
end
end
Essentially, I found a post where people who wrote their own "change password" pages found that Devise (or Warden?) was logging out the user immediately afterwards. The workaround for them was to change the call to sign_in to bypass_sign_in.
In this situation, though, Devise have already called sign_in. So I thought I'd try adding a filter that applies onto to their one action with the problem, and sign the user out, then sign in a fresh copy of the user. This fixes the issue - now the user is signed in and the root page appears.
I don't like this solution much though so I'm still digging inside Warden to try and figure out why the user is not being signed in with a call to sign_in.
I had the same issue, and found that passing an extra parameter to the sign_up function solves the problem:
sign_in(#user, :bypass => true)
This was something I found in this tutorial for setting up omniauth with a finish_signup feature. My implementation below is part of a feature I'm writing for a user to change their password and still retain their status as a logged in user.
~/app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :set_current_user, only: [:edit_password, :update_password]
def edit_password
end
def update_password
if #user.update(user_params)
# Sign in the user by passing validation in case their password changed
sign_in(#user, :bypass => true)
redirect_to root_path
else
render :edit_password
end
end
private
def set_current_user
#user = User.find(current_user.id)
end
end
~/app/views/users/edit_password.html.erb
<%= form_for(#user, :url => { :action => "update_password" } ) do |f| %>
<div class="field">
<%= f.label :password, "Password" %><br />
<%= f.password_field :password, :autocomplete => "off" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</div>
<div class="action_container">
<%= f.submit %>
</div>
<% end %>
routes.rb
resource :user, only: [:none] do
collection do
get :edit_password
patch :update_password
end
end
I have a projects model and a task model, however a task can have many projects and vice versa...so i created a relationship model between the two. Now from the project profile, i want to be able to create a task and automatically have it create the task and the relationship between the new task and the project it was created from.
However when i try to accomplish this i am getting the following error:
ActiveRecord::RecordNotFound in TasksController#create
Couldn't find Project with 'id'=
A user is on the Project show page and clicks a link to 'submit new task'. I realize i'm not passing the project ID somehow but I can't seem to figure out how to do this because I'm using a TaskRelationship model to associate the task and the project (I'm not nesting the task in the project in my routes).
views/projects/show.html.erb:
<%= link_to "+ Submit New Task", new_task_path, :class => "btn btn-info col-md-12" %>
From the new tasks view, I need to create both the task and the relationship between task and project.
views/tasks/new.html.erb:
<div class="container sign-in-register">
<div class="authform">
<%= form_for #task, :html => {:multipart => true} do |f| %>
<h3>Submit new task to this project...</h3><br/>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :Title %>
<%= f.text_field :title, class: 'form-control' %>
<%= f.label :Description %>
<%= f.text_area :description, class: 'form-control' %>
<br clear="all">
<%= f.submit "Add this Task", class: "btn btn btn-info" %>
<% end %>
</div>
</div>
TaskRelationship Model (links tasks to projects):
class TaskRelationship < ActiveRecord::Base
belongs_to :taskproject, class_name: "Project"
belongs_to :projecttask, class_name: "Task"
validates :taskproject_id, presence: true
validates :projecttask_id, presence: true
end
Project Model:
class Project < ActiveRecord::Base
belongs_to :owner, :foreign_key=>'user_id', :class_name=>'User'
has_many :tasks
has_many :taskrelationships, foreign_key: "taskproject_id", dependent: :destroy
has_many :projecttasks, through: :taskrelationships, source: :projecttask
validates :title, presence: true
validates :background, presence: true
def related?(some_task)
taskrelationships.find_by_projecttask_id(some_task.id)
end
def relate!(some_task)
self.taskrelationships.create!(projecttask_id: some_task.id)
end
end
Task Model:
class Task < ActiveRecord::Base
belongs_to :owner, :foreign_key=>'user_id', :class_name=>'User'
has_many :projects
has_many :reverse_taskrelationships, foreign_key: "projecttask_id",
class_name: "TaskRelationship",
dependent: :destroy
has_many :taskprojects, through: :reverse_taskrelationships, source: :taskproject
validates :title, presence: true
validates :description, presence: true, length: { maximum: 140 }
end
Tasks Controller:
class TasksController < ApplicationController
def new
#task = Task.new
end
def create
#project = Project.find(params[:taskproject_id])
#task = current_user.own_tasks.build(task_params)
if #task.save
flash[:success] = "Your task has been created."
redirect_to #task
#project.relate!(#task) unless #project.related?(#task) # establish task relationship w/ project only if doesn't exist
else
render 'task'
end
end
private
def task_params
params.require(:task).permit(:title, :description, :user_id, task_relationship_attributes: [:taskproject_id, :projecttask_id])
end
end
Task_Relationships_Controller:
class TaskRelationshipsController < ApplicationController
before_filter :authenticate_user!
def create
end
def destroy
end
# I assume (maybe incorrectly) that i don't need create/destroy actions but do need strong params
private
def task_relationship_params
params.require(:taskrelationship).permit(:taskproject_id, :projecttask_id)
end
end
How can i get this correct ID passed so that the new task is created and the new taskRelationship between task and project? thx,
UPDATE:
I've added the log for more detail
Terminal Log when trying to post:
Started GET "/tasks/new" for ::1 at 2016-04-15 19:55:54 -0500
Started GET "/tasks/new" for ::1 at 2016-04-15 19:55:54 -0500
Processing by TasksController#new as HTML
Processing by TasksController#new as HTML
Rendered shared/_error_messages.html.erb (0.1ms)
Rendered shared/_error_messages.html.erb (0.1ms)
Rendered tasks/new.html.erb within layouts/application (24.5ms)
Rendered tasks/new.html.erb within layouts/application (24.5ms)
Rendered layouts/_shim.html.erb (0.0ms)
Rendered layouts/_shim.html.erb (0.0ms)
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 4]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 4]]
Rendered layouts/_navigation_links.html.erb (1.6ms)
Rendered layouts/_navigation_links.html.erb (1.6ms)
Rendered layouts/_header.html.erb (2.9ms)
Rendered layouts/_header.html.erb (2.9ms)
Rendered layouts/_footer.html.erb (0.0ms)
Rendered layouts/_footer.html.erb (0.0ms)
Completed 200 OK in 192ms (Views: 185.6ms | ActiveRecord: 1.0ms)
Completed 200 OK in 192ms (Views: 185.6ms | ActiveRecord: 1.0ms)
Started POST "/tasks" for ::1 at 2016-04-15 19:55:59 -0500
Started POST "/tasks" for ::1 at 2016-04-15 19:55:59 -0500
Processing by TasksController#create as HTML
Processing by TasksController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"DGGG+zWPMbB7OwZz8oCVLB5O6sMfTe/Orj6KfeP6mrveOH0ImAP4aow0gufqefOdwsp8v4GDEt8ppJiL4CvQVg==", "task"=>{"title"=>"test", "description"=>"test"}, "commit"=>"Add this Evidence"}
Parameters: {"utf8"=>"✓", "authenticity_token"=>"DGGG+zWPMbB7OwZz8oCVLB5O6sMfTe/Orj6KfeP6mrveOH0ImAP4aow0gufqefOdwsp8v4GDEt8ppJiL4CvQVg==", "task"=>{"title"=>"test", "description"=>"test"}, "commit"=>"Add this Evidence"}
Project Load (0.3ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT 1 [["id", nil]]
Project Load (0.3ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT 1 [["id", nil]]
Completed 404 Not Found in 2ms (ActiveRecord: 0.3ms)
Completed 404 Not Found in 2ms (ActiveRecord: 0.3ms)
ActiveRecord::RecordNotFound (Couldn't find Project with 'id'=):
app/controllers/tasks_controller.rb:8:in `create'
ActiveRecord::RecordNotFound (Couldn't find Project with 'id'=):
app/controllers/tasks_controller.rb:8:in `create'
1) You'll need to pass the project ID to TasksController#new somehow.
One approach is to pass it as part of the request URL, something like:
<host>/tasks/new?project_id=<project ID>
This will make it available in the params variable of the request.
2) In your TasksController#new action, pass project_id from params to the view. The easiest way is using an instance variable:
#project_id = params[:project_id]
There is a philosophy of only passing one object to a view and here we're passing 2: #task and #project_id. I wouldn't worry about it but you might want to read up on form objects: https://robots.thoughtbot.com/activemodel-form-objects
3) Add a hidden field on your form with the project ID. Because #project_id isn't part of the #task model, we'll use an input tag helper instead of a form based helper:
<%= hidden_field_tag 'project_id', #project_id %>
API doc: http://apidock.com/rails/ActionView/Helpers/FormTagHelper/hidden_field_tag
Now the value of #project_id will be passed to the #create action as params[:project_id] when the user clicks on the submit button.
4) Change the params in TasksController#create action. project_id will be nested in with the task parameters:
#project = Project.find(params[:project_id])
5) You'll need to create your TaskRelationship relationship. There are a couple of ways to do this. I usually use build:
#task.taskprojects.build(project: #project)
so your #create action would look something like:
#project = Project.find(params[:project_id])
#task = current_user.own_tasks.build(task_params)
#task.taskprojects.build(project: #project)
if #task.save
...
In your controller statement Project.find(params[:taskproject_id]), it looks like params[:taskproject_id] is nil. Looking at the code in your form view the params passed to the controller should be params[:id]. It's also not clear where task_params is defined
If you still are getting the error, check the params in the log output from when you submit your form and post them here.
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.