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.
Related
My method is executing, but Devise is not using the return value at all. On the sign in page, it just reloads the page with a 'Signed in successfully' notice. It doesn't redirect to the value returned from the method.
Log
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 22:19:50 -0500
Processing by Users::SessionsController#create as HTML
Parameters: {"utf8"=>"√", "authenticity_token"=>"tQd5a43StP85oyyCpEmFU8cAkFXdJL2OLpuAK1+sqQC6/rIqcd+fB2iE4RT0RoPKPCqreNBYlv2bxjl9gZFrWg==", "user"=>{"email"=>"test11#example.com", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["email", "test11#example.com"], ["LIMIT", 1]]
(5.0ms) BEGIN
User Exists (3.0ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 AND ("users"."id" != $2) LIMIT $3 [["email", "test11#example.com"], ["id", 23], ["LIMIT", 1]]
Sector Load (0.0ms) SELECT "sectors".* FROM "sectors" INNER JOIN "sectors_users" ON "sectors"."id" = "sectors_users"."sector_id" WHERE "sectors_users"."user_id" = $1 [["user_id", 23]]
Region Load (0.0ms) SELECT "regions".* FROM "regions" INNER JOIN "regions_users" ON "regions"."id" = "regions_users"."region_id" WHERE "regions_users"."user_id" = $1 [["user_id", 23]]
Criterium Load (0.0ms) SELECT "criteria".* FROM "criteria" INNER JOIN "criteria_users" ON "criteria"."id" = "criteria_users"."criterium_id" WHERE "criteria_users"."user_id" = $1 [["user_id", 23]]
AssetType Load (0.0ms) SELECT "asset_types".* FROM "asset_types" INNER JOIN "asset_types_users" ON "asset_types"."id" = "asset_types_users"."asset_type_id" WHERE "asset_types_users"."user_id" = $1 [["user_id", 23]]
Company Load (1.0ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2 [["id", 42], ["LIMIT", 1]]
(5.0ms) ROLLBACK
############### /users/23/edit
Rendering users/sessions/new.haml within layouts/application
Rendered users/shared/_links.html.erb (3.0ms)
Rendered users/sessions/new.haml within layouts/application (251.2ms)
Rendered layouts/_footer.haml (15.0ms)
Completed 200 OK in 6554ms (Views: 3364.9ms | ActiveRecord: 86.1ms)
Notice it is rendering users/sessions/new.haml instead of the edit page?
Code
class ApplicationController < ActionController::Base
...
def after_sign_in_path_for(resource)
logger.debug '############### ' + edit_user_path(resource) if resource.is_a?(User) && resource.signature.blank?
return edit_user_path resource if resource.is_a?(User) && resource.signature.blank?
stored_location_for(resource) ||
if resource.is_a?(User)
dashboard_path
elsif resource.is_a?(Facilitator) && resource.name.nil?
edit_facilitator_path resource
elsif resource.is_a?(Facilitator)
facilitator_path resource
else
super
end
end
I completely commented out the method and it still reloaded the login page.
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 22:25:21 -0500
...
Rendering users/sessions/new.haml within layouts/application
Devise 4.4.0
Documentation:
https://github.com/plataformatec/devise/wiki/How-To%3A-Redirect-to-a-specific-page-on-successful-sign-in-and-sign-out
http://www.rubydoc.info/github/plataformatec/devise/master/Devise/Controllers/Helpers:after_sign_in_path_for
I added
def after_sign_in_path_for(resource)
logger.debug '############# ' + resource.errors.full_messages.join(', ')
And did discover validation errors like
############# Title can't be blank, Country can't be blank, Signature can't be blank, ...
But it does show the notice
Signed in successfully.
And I do have a session and can navigate elsewhere. My validations are on: :update.
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true, on: :update
This should not cause log in behavior errors.
I commented all validations on the model and it does work, but this is highly unusual! Validations should not affect login behavior. There has to be a workaround.
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 23:11:43 -0500
SQL (15.0ms) UPDATE "users" SET "current_sign_in_at" = $1, "last_sign_in_at" = $2, "current_sign_in_ip" = $3, "sign_in_count" = $4, "updated_at" = $5 WHERE "users"."id" = $6 [["current_sign_in_at", "2018-03-06 04:11:44.225501"], ["last_sign_in_at", "2017-11-09 01:22:28.245231"], ["current_sign_in_ip", "127.0.0.1/32"], ["sign_in_count", 6], ["updated_at", "2018-03-06 04:11:44.230506"], ["id", 23]]
Redirected to http://localhost:3000/users/23/edit
Completed 302 Found in 2183ms (ActiveRecord: 48.0ms)
As you only want your validations on update, I guess that you only need them for a specific form, since your users are still valid even without this validations.
In that case I would use a so called form object, that does the on update validations for you and remove the on update validations on your user model. In that case your validations don't affect other parts of your app.
Here is a good guide on how to do that with just using ActiveModel.
Example:
app/models/user.rb
class User < ApplicationRecord
# remove the validations here
end
app/forms/user_edit_form.rb
class UserEditForm
include ActiveModel::Model
ATTRIBUTES = :email, :name, :title, :phone,
:address1, :city, :state, :zip,
:country, :type, :signature
attr_accessor *ATTRIBUTES
validates *ATTRIBUTES, presence: true
def update(user)
if valid?
user.update(self.attributes)
end
end
def self.for_user(user)
new(user.slice(*ATTRIBUTES)
end
end
users_controller.rb
class UsersController
def edit
#user = User.find(params[:id])
#user_edit_form = UserEditForm.for_user(#user)
end
def update
#user = User.find(params[:id])
#user_edit_form = UserEditForm.new(user_update_params).update(#user)
if #user_edit_form.errors?
render :edit
else
redirect_to user_path(#user)
end
end
def user_update_params
# ...
end
end
edit.html.erb
<%= form_for #user_edit_form, url: user_path(#user), method: :patch do |f| %>
# ...
<%= f.submit %>
<% end %>
Alternative
An alternative could be to add a virtual attribute to the model and run your validations conditionally in the user controller.
class User < ApplicationRecord
attr_accessor :profile_complete
with_options if: -> { profile_complete } do
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true
end
end
users_controller.rb
class UsersController < ApplicationController
def update
#user = User.find(params[:id])
#user.profile_complete = true
if #user.update(user_update_params)
redirect_to #user
else
render :edit
end
# ...
end
end
Note: Instead of using a virtual attribute (attr_accessor) you could also use a real DB attribute, so you can also actually know which users filled out their profile completely.
Alternative 2
In some other projects I also used state machine gems (there are a couple e.g. aasm or statemachines-activerecord) to do somehing similar. Some of the state machine gems even support having validations only for certain states or transisions.
Check this documentation https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in. They have clearly mentioned when you will go in loop and solution for it. Check Preventing redirect loops section in above doc.
You might need conditional validation on your model. Something like this:
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true, on: :update, unless: Proc.new {|user| user.current_sign_in_at.present? }
Devise will update sign_in_at whenever sign_in happens. Which will trigger update action and related validations.
Also Documentation said the allow_nil: true instruct the model to validate the fields ONLY if it exists on the submitted form.
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'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
In my Rails app I have made an api for an iOS app to consume.
I have a user model with a association to a profile:
class User < ActiveRecord::Base
has_one :personal_profile
accepts_nested_attributes_for :personal_profile
end
class PersonalProfile < Profile
belongs_to :user
end
The users_controller.rb looks like this:
class Api::V1::UsersController < Api::V1::ApiController
before_filter :authenticate_api_user, only: [:show, :update]
def create
#user = User.new(user_params)
if #user.save
#user
else
render json: { errors: #user.errors.full_messages }, status: 422
end
end
def show
#user
end
def update
if #user.update(user_params)
#user
else
render json: { errors: #user.errors.full_messages }, status: 422
end
end
private
def user_params
params.require(:user).permit(:name, :email, :birthday, :gender, :password, :password_confirmation, personal_profile_attributes: [:website, :location, :description, :tagline, :tag_tokens, :image, :image_cache])
end
def authenticate_api_user
authenticate_or_request_with_http_token do |token, options|
#user = User.find_by(auth_token: token)
end
end
end
When I do a put request like this:
curl -H 'Authorization: Token token=AUTH_TOKEN' -H 'Content-Type: application/json' -X PUT -d '{"user": {"name": "Frank", "personal_profile_attributes": { "tagline": "New tagline" }}}' http://localhost:3000/api/user
I get this response:
{"errors":["Personal profile title can't be blank."]}
And it's like the server doesn't recognize my params:
Started PUT "/api/user" for ::1 at 2016-01-19 10:16:33 +0100
Processing by Api::V1::UsersController#update as JSON
Parameters: {"user"=>{"name"=>"Frank", "personal_profile_attributes"=>{"tagline"=>"New tagline"}}}
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."auth_token" = $1 LIMIT 1 [["auth_token", AUTH_TOKEN]]
(0.8ms) BEGIN
PersonalProfile Load (1.2ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."type" IN ('PersonalProfile') AND "profiles"."user_id" = $1 ORDER BY title asc LIMIT 1 [["user_id", 1]]
SQL (1.3ms) UPDATE "profiles" SET "user_id" = $1, "updated_at" = $2 WHERE "profiles"."id" = $3 [["user_id", nil], ["updated_at", "2016-01-19 09:16:33.629907"], ["id", 1]]
User Exists (1.3ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('d#friis.me') AND "users"."id" != 1) LIMIT 1
(0.8ms) ROLLBACK
Completed 422 Unprocessable Entity in 19ms (Views: 0.3ms | ActiveRecord: 6.5ms)
I have another controller setup for the web app, which is pretty standard and works just fine!
Any help is much appreciated!
Okay, so the problem was that I did not pass an ID for the associated record with the request params. This meant that a new associated record was created with the attributes I was trying to update.
Setting the ID solved the problem and updated the record as expected.
{
"user" => {
"personal_profile_attributes" => {
"id" => 1,
"tagline" => "New tagline"
}
}
}
I have Subcategories which belong to Categories. My app saves (build) all my Subcategories under Subcategory with id = 1, even though my code seems to be ok and it is not supposed to do that:
Subcategories controller:
def create
#category = Category.find_by(params[:id])
#subcategory = #category.subcategories.build(subcategory_params)
if #subcategory.save
flash[:success] = "added subcategory"
redirect_to admin_categories_url
else
render :new
end
end
...
private
def subcategory_params
params.require(:subcategory).permit(:name, :category_id)
end
Subcategory.rb
class Subcategory < ActiveRecord::Base
belongs_to :category
has_many :products
validates :name, presence: true
validates :category_id, presence: true
end
Form:
<h3>Add a subcategory</h3>
<%= form_for [#category, #subcategory] do |f| %>
<%= f.text_field :name, placeholder: "Name" %>
<%= f.submit "Add a subcategory" %>
<% end %>
router:
resources :categories do
resources :subcategories
end
URL:
Adding a new subcategory to category with id = 3
http://localhost:3000/categories/3/subcategories/new
Logs:
Started POST "/categories/1/subcategories" for 127.0.0.1 at 2014-04-16 16:04:47 +0400
Processing by SubcategoriesController#create as HTML
Parameters: {"utf8"=>"✓", authenticity_token"=>"74U3VyAN6NqEjhkuHGNHnPda/yzpc+dIcn2xBJ6Zi2A=", "subcategory"=>{"name"=>"A name"}, "commit"=>"Add subcategory", "category_id"=>"1"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Category Load (0.2ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1 [["id", 1]]
(0.1ms) BEGIN
SQL (0.2ms) INSERT INTO "subcategories" ("category_id", "created_at", "name", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["category_id", 1], ["created_at", "2014-04-16 12:04:47.151729"], ["name", "A name"], ["updated_at", "2014-04-16 12:04:47.151729"]]
(0.8ms) COMMIT
Id is 1
What can be the problem here?
Update
When doing
#category = Category.find_by(params[:category_id])
this error comes up:
ERROR: argument of WHERE must be type boolean, not type integer
LINE 1: SELECT "categories".* FROM "categories" WHERE (1) LIMIT 1
If doing
#category = Category.find(params[:category_id])
problem presists
In your create action you are picking up the id of the subcategory, not the category. Change it to:
def create
#category = Category.find(params[:category_id])
...
The same change to setting #category needs to be made in the new method too so that category_id comes back as 3 in the params from the view. Of course this duplication can be also DRYed up using a helper method in the controller.
To see the difference in the id, you can use rake routes and you should see something like:
new_category_subcategory GET /categories/:category_id/subcategories/new subcategories#new
This shows you that it expects the parameter category_id for the id of the category.
Look at your parameters it consist category_id not id
Parameters: {"utf8"=>"✓", authenticity_token"=>"74U3VyAN6NqEjhkuHGNHnPda/yzpc+dIcn2xBJ6Zi2A=", "subcategory"=>{"name"=>"A name"}, "commit"=>"Add subcategory", "category_id"=>"1"}
#=> "category_id"=>"1"
Category.find_by(category_id: params[:category_id])
So you need to do
def create
#category = Category.find_by(category_id: params[:category_id])
#subcategory = #category.subcategories.build(subcategory_params)
if #subcategory.save
flash[:success] = "added subcategory"
redirect_to admin_categories_url
else
render :new
end
end