I migrated my Rails from 3.2 to Rails 4.2.6. I am having 2 tables where report :has_many => icons. I added strong parameters for report and icon_attributes. The create functionality is working fine and when coming to update functionality, I am able to update reports but couldn't update icons, instead new icon is created every time it hits update action.
This is my code:
report.rb:
class Report < ActiveRecord::Base
has_many :icons, -> { order 'position_id ASC'}
accepts_nested_attributes_for :icons, :reject_if => lambda { |a| a[:icon].blank? }, :allow_destroy => true
end
icon.rb:
class Icon < ActiveRecord::Base
belongs_to :report
end
reports_controller:
def update
respond_to do |format|
if #report.update_attributes(report_params)
#report.save
format.html { redirect_to(user_reports_url, :notice => 'Report was successfully updated.') }
format.json { render :json => { :success => true, :report_id => #report.id, :report_title => #report.title, :icon_array => #report.icons, :redirect => report_url(#report.id) } }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #report.errors, :status => :unprocessable_entity }
end
end
end
private
def report_params
params.require(:report).permit(:title, :comments, :remarks,{:icons_attributes => [:id, :icon, :rotation, :top, :_destroy]})
end
I have seen the log by putting puts in the controller, the icons are inserting at #report.update_attributes(report_params) step and this is the log:
Processing by ReportsController#update as JSON Parameters:
{"utf8"=>"✓", "report"=>{"title"=>"title1", "comments"=>"This is a comment",
"icons_attributes"=>{"0"=>{"id"=>"", "icon"=>"market_indicator",
"rotation"=>"0", "top"=>"", "_destroy"=>"false"}, "id"=>"87"}
Report Load (0.3ms) SELECT "reports".* FROM "reports" WHERE
"reports"."deleted_at" IS NULL AND "reports"."id" = ? LIMIT 1 [["id",
87]]
SQL (1.6ms) INSERT INTO "icons" ("icon", "rotation", "top")
VALUES (?, ?, ?) [["icon", "market"], ["rotation", "0"], ["top", ""],
["left", ""]] (12.0ms) commit transaction
ActiveRecord::Associations::CollectionProxy
I have put log as:
def update
puts #report.icons.inspect
respond_to do |format|
.....
end
it resulted as:
Icon Load (0.9ms) SELECT "icons".* FROM "icons" WHERE "icons"."report_id" = ? ORDER BY position_id ASC [["report_id", 91]]
<ActiveRecord::Associations::CollectionProxy [#<Icon id: 204, report_id: 91, icon: "asking_price", rotation: "", top: "150", left: "165">]>
Your "icon_attributes" is not passing the id of the icon along.
"icons_attributes"=>{"0"=>{"id"=>"", "icon"=>"market_indicator", "rotation"=>"0", "top"=>"", "_destroy"=>"false"}, "id"=>"87"}
You'll notice the id is blank. Since the id is blank rails thinks it is a new record and thus creates a new icon. The error lies in how you have made your form.
Related
In the following class...
class Parent < ActiveRecord::Base
has_many :relatives
has_many :kids, through: :relatives
accepts_nested_attributes_for :relatives,
:reject_if => lambda { |a| a['kid_id'].blank? },
:allow_destroy => true
end
...I've added some code which I expected to prevent 'relatives' from saving on the parent model if their fields were blank. It doesn't seem to work. It saved both blank and non-blank fields.
I.e. when I go into the console and query the database, I can pull out the parent record and run queries like so:
2.2.2 :004 > p.relatives.find(17)
Relative Load (5.4ms) SELECT "relatives".* FROM "relatives" WHERE "relatives"."parent_id" = ? AND "relatives"."id" = ? LIMIT 1 [["parent_id", 24], ["id", 17]]
=> #<Relative id: 17, relationship: "yes", parent_id: 24, kid_id: 1, created_at: "2015-11-12 09:56:07", updated_at: "2015-11-12 09:56:07">
That's what I expected - I entered data for that 'kid'.
2.2.2 :005 > r = p.relatives.find(18)
Relative Load (3.4ms) SELECT "relatives".* FROM "relatives" WHERE "relatives"."parent_id" = ? AND "relatives"."id" = ? LIMIT 1 [["parent_id", 24], ["id", 18]]
=> #<Relative id: 18, relationship: "", parent_id: 24, kid_id: nil, created_at: "2015-11-12 09:56:07", updated_at: "2015-11-12 09:56:07">
This record should never have saved because it violates the lambda above, i.e. ...
2.2.2 :006 > r.relationship.blank?
=> true
2.2.2 :007 > r.kid.blank?
=> true
...the fields are blank!
Here's the controller in question (extract):
class ParentsController < ApplicationController
before_action :set_parent, only: [:show, :edit, :update, :destroy]
before_action :lookup_kids, only: [:new, :edit]
# GET /parents/new
def new
#parent = Parent.new
4.times { #parent.relatives.build }
end
# POST /parents
# POST /parents.json
def create
logger.debug(" SANITY CHECK ")
logger.debug(parent_params)
#parent = Parent.new(parent_params)
parent_params[:relatives_attributes].each do |k,r|
#parent.relatives.build(r.except(:_destroy))
end
respond_to do |format|
if #parent.save
format.html { redirect_to #parent, notice: 'Parent was successfully created.' }
format.json { render :show, status: :created, location: #parent }
else
format.html { render :new }
format.json { render json: #parent.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_parent
#parent = Parent.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def parent_params
params.require(:parent).permit(:name,
relatives_attributes: [:parent_id, :kid_id, :relationship, :_destroy])
end
def lookup_kids
#kids = Kid.all
end
end
I think this might help
https://fabianosoriani.wordpress.com/2011/06/07/accepts_nested_attributes_for-3-0-5-reject_if-still-have-gotchas/
There are some gotchas behind accepts_nested_attributes_for with validations.
Based on my understanding since model kid is associated by Relative, you should do like this. Try this in your Parent Model
accepts_nested_attributes_for :relatives, :allow_destroy => true
Then in Your Relative Model,
accepts_nested_attributes_for :kids,
:reject_if => lambda { |a| a['kid_id'].blank? },
:allow_destroy => true
Thanks to #user5554692, discovered the only way to do this is as follows:
Adding this to the parent controller:
parent_params[:relatives_attributes].delete_if { |k,v| v['kid_id'].blank?}
Removed all blank records, then...
#parent = Parent.new(parent_params)
Creates the parent object as usual.
Apparently, reject_if just doesn't work in this particular scenario, for some reason.
I want to call create action of controller user_clubs and I did this way:
View Clubs
<button>
<%= link_to "Join Club", user_clubs_path(:user_id => current_user.id, :club_id => #club.id, :join_date => Date.current), :method => :post %>
</button>
Controller user_clubs
def create
#user_club = UserClub.new(user_club_params)
respond_to do |format|
if #user_club.save
format.html { redirect_to #user_club, notice: 'User club was successfully created.' }
format.json { render :show, status: :created, location: #user_club }
else
format.html { render :new }
format.json { render json: #user_club.errors, status: :unprocessable_entity }
end
end
end
def user_club_params
params.require(:user_club).permit(:user_id, :club_id, :join_date) --->**Error here**
end
Error information
app/controllers/user_clubs_controller.rb:75:in user_club_params'
app/controllers/user_clubs_controller.rb:28:increate'
Request
Parameters:
{"_method"=>"post",
"authenticity_token"=>"5Grhb+LIGt9B8XbnEcvg7BZQlDE935KO/aeikZoqxYs=",
"club_id"=>"1",
"join_date"=>"2014-11-17",
"user_id"=>"2"
}
Clubs and UserClubs are different. Club is a model that represents a team of people and user_clubs is the model that represents the many-to-many relationship between Users and Clubs.
First, can someone explain me how the call to user_clubs_path followed by the arguments know that has to go to the action create of user_clubs controller?
In second, the objective problem, why is this an error?
First question
Because of your routes definition, type into a terminal:
rake routes
And you'll see all generated routes and its associated helpers. First column (rake output) references the named helper: user_clubs => user_clubs_path):
Second question
You should add the parameters into user_club key, because you're requiring (by strong_parameters) this "scope" params.require(:user_club):
user_clubs_path(:user_club => {:user_id => current_user.id, :club_id => #club.id, :join_date => Date.current})
You'll receive in the controller:
{
"_method" => "post",
"authenticity_token" => "...",
"user_club" => {
"club_id" => "1",
"join_date"=> "2014-11-17",
"user_id"=> "2"
}
}
The parameters need to be nested under the user_club key. Try this instead:
user_clubs_path(:user_club => {:user_id => current_user.id, :club_id => #club.id, :join_date => Date.current})
I have models
class Riskfactor < ActiveRecord::Base
has_many :bodies_riskfactors
has_many :bodies, through: :bodies_riskfactors
end
class Body < ActiveRecord::Base
has_many :bodies_riskfactors
has_many :riskfactors, through: :bodies_riskfactors
end
class DisordersArticle < ActiveRecord::Base
belongs_to :disorder
belongs_to :article
end
in view
= form_for [:admin, #riskfactor], html: {role: "form"} do |f|
= f.select :body_ids, Body.all.collect {|x| [x.name, x.id]}, {}, :multiple => true
and I have error on update
What could be wrong?
Processing by Admin::RiskfactorsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"CVkdBHLzLAeQ8FRFBVyonUuyn1FZAYG+X9fX/mmj+Bo=", "riskfactor"=>{"body_ids"=>["", "1"], "position"=>"1", "slug"=>"risk-factor-2", "name_ru"=>"High Blood Pressure", "published_ru"=>"no", "content_ru"=>"", "meta_description_ru"=>"", "meta_keywords_ru"=>"", "name_en"=>"High Blood Pressure", "published_en"=>"no", "content_en"=>"High Blood Pressure High Blood Pressure High Blood Pressure High Blood Pressure High Blood Pressure", "meta_description_en"=>"", "meta_keywords_en"=>""}, "button"=>"", "id"=>"4"}
User Load (0.3ms) SELECT users.* FROM users WHERE users.id = 1 ORDER BY users.id ASC LIMIT 1
Riskfactor Load (0.3ms) SELECT riskfactors.* FROM riskfactors WHERE riskfactors.id = 4 LIMIT 1
(0.1ms) BEGIN
Body Load (0.3ms) SELECT bodies.* FROM bodies WHERE bodies.id = 1 ORDER BY position LIMIT 1
Body Load (2.6ms) SELECT bodies.* FROM bodies INNER JOIN bodies_riskfactors ON bodies.id = bodies_riskfactors.body_id WHERE bodies_riskfactors.riskfactor_id = 4 ORDER BY position
Riskfactor::Translation Load (0.3ms) SELECT riskfactor_translations.* FROM riskfactor_translations WHERE riskfactor_translations.riskfactor_id = 4
Riskfactor Exists (0.6ms) SELECT 1 AS one FROM riskfactors WHERE (riskfactors.slug = 'risk-factor-2' AND riskfactors.id != 4) LIMIT 1
(0.2ms) ROLLBACK
Completed 500 Internal Server Error in 15ms
NoMethodError (undefined method body' for #<Riskfactor:0x007f9a0351c350>):
app/controllers/admin/riskfactors_controller.rb:45:inblock in update'
app/controllers/admin/riskfactors_controller.rb:44:in `update'
RiskfactorsController
class Admin::RiskfactorsController < Admin::BaseController
before_action :set_riskfactor, only: [:show, :edit, :update, :destroy]
def update
respond_to do |format|
if #riskfactor.update(riskfactor_params)
format.html { redirect_to [:edit, :admin, #riskfactor], notice: 'Riskfactor was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #riskfactor.errors, status: :unprocessable_entity }
end
end
end
private
def set_riskfactor
#riskfactor = Riskfactor.find(params[:id])
end
def riskfactor_params
params.require(:riskfactor).permit!
end
end
Imho the problem is that you have body_ids in your view, but you named you association as bodies. Try to rename body_ids to bodies_ids or something like that.
I'm working with a Model called Recover. Prior to creating the model I would like to save the boolean attribute, Combo.occupied = true using the Recover.combo_id attribute as a reference.
It appears my SQL is executing the query properly, but it is not saving this attribute. How can I save Combo.occupied = true?
recover.rb:
before_create :checkin
protected
def checkin
x = Combo.find_by_id(combo_id).occupied =
true
end
Rails Console:
Started POST "/recovers" for 127.0.0.1
at 2011-01-06 17:07:24 -0800
Processing by
RecoversController#create as HTML
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"o1Iu3Y9/rVBOZPoDUgVP/tRfQ8GxbdWC40DbPq9YxUE=",
"recover"=>{"combo_id"=>"4",
"email"=>"jz#marin.edu"},
"commit"=>"Create Recover"} Recover
Load (0.2ms) SELECT "recovers"."id"
FROM "recovers" WHERE
("recovers"."email" =
'justin.zollars#marin.edu') LIMIT 1
Recover Load (0.1ms) SELECT
"recovers"."id" FROM "recovers" WHERE
("recovers"."combo_id" = 4) LIMIT 1
Combo Load (0.5ms) SELECT "combos".*
FROM "combos" WHERE ("combos"."id" =
4) LIMIT 1 AREL (0.5ms) INSERT INTO
"recovers" ("locker_number", "email",
"requests", "created_at",
"updated_at", "combo_id") VALUES
(NULL, 'justin.zollars#marin.edu',
NULL, '2011-01-07 01:07:24.287072',
'2011-01-07 01:07:24.287072', 4)
Redirected to
http://localhost:3000/recovers/14
Completed 302 Found in 119ms
RecoversController#create
def create
#recover = Recover.new(params[:recover])
respond_to do |format|
if #recover.save
format.html { redirect_to(#recover, :notice =>
'Recover was successfully created.') }
format.xml { render :xml => #recover, :status => :created,
:location => #recover }
else
format.html { render :action => "new" }
format.xml { render :xml => #recover.errors, :status =>
:unprocessable_entity }
end
end
end
You need to call save for the new value to be written to the database:
def checkin
combo = Combo.find_by_id(combo_id)
combo.occupied = true
combo.save!
end
This is easier if you use update_attribute. Also, if you have a belongs_to relationship, you can dispense with the find:
belongs_to :combo
def checkin
if combo # true unless combo_id is nil
combo.update_attribute(:occupied,true)
end
end
Note that update_attribute bypasses validation. If you need to validate, use update_attributes(:occupied=>true) instead.
Here's the model file:
class ProfileTag < ActiveRecord::Base
def self.create_or_update(options = {})
id = options.delete(:id)
record = find_by_id(id) || new
record.id = id
record.attributes = options
puts "record.profile_id is"
puts record.profile_id
record.save!
record
end
end
This gives me the correct print out in my log. But it also says that there's a call to UPDATE that sets profile_id to NULL. Here's some of the output in the log file:
Processing ProfilesController#update (for 127.0.0.1 at 2010-05-28 18:20:54) [PUT]
Parameters: {"commit"=>"Save", "profile"=>{"id"=>"2", "password_confirmation"=>"", "username"=>"user2", "first_name"=>"user2_first", "password"=>"", "last_name"=>"user2_last"}, "authenticity_token"=>"...", "tag"=>"1", "id"=>"2"}
?[4;36;1mProfileTag Create (0.0ms)?[0m ?[0;1mINSERT INTO `profile_tags`
(`reputation_value`, `updated_at`, `tag_id`, `id`, `profile_id`, `created_at`) VALUES(0, '2010-05-29 01:20:54', 1, NULL, 4, '2010-05-29 01:20:54')?[0m
?[4;35;1mSQL (2.0ms)?[0m ?[0mCOMMIT?[0m
?[4;36;1mSQL (0.0ms)?[0m ?[0;1mBEGIN?[0m
?[4;35;1mSQL (0.0ms)?[0m ?[0mCOMMIT?[0m
?[4;36;1mProfileTag Load (0.0ms)?[0m ?[0;1mSELECT * FROM `profile_tags` WHERE (`profile_tags`.profile_id = 4) ?[0m
?[4;35;1mSQL (1.0ms)?[0m ?[0mBEGIN?[0m
?[4;36;1mProfileTag Update (0.0ms)?[0m ?[0;1mUPDATE `profile_tags` SET profile_id = NULL WHERE (profile_id = 4 AND id IN (35)) ?[0m
I'm not sure I understand why the INSERT puts the value into profile_id properly, but then it sets it to NULL on an UPDATE.
[Edit]
In ProfileController:
def update
#...stuff. Set tags array.
save_tags(tags) #These tags are correct. Verified by printouts before and after this call.
respond_to do |format|
if #profile.update_attributes(params[:profile])
flash[:notice] = 'Profile was successfully updated.'
#format.html { redirect_to(#profile) }
format.html { redirect_to :action=>'show' }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #profile.errors, :status => :unprocessable_entity }
end
end
end
def save_tags(tags)
profile = find_profile #finds the correct profile. And I confirm that it exists with a printout
tags.each do |t|
ProfileTags.create_or_update(:profile_id => profile.profile_id, :tag_id => t.id)
end
end
If you need more specifics, please let me know. I'm thinking that the save functionality does many things other than INSERTs into the database, but I don't know what I need to specify so that it will properly set profile_id.
Look at the line:
ProfileTags.create_or_update(:profile_id => profile.profile_id, :tag_id => t.id)
I believe you want to pass profile.id, and not profile.profile_id (which is probably null).
save! itself should't do this.
Maybe your problem is the name of the method. ActiveRecord::Base already have a method named create_or_update (see http://github.com/rails/rails/blob/2-3-stable/activerecord/lib/active_record/base.rb#L2913) which is called by save! - maybe replacing it causes this weird problem.
Try changing the name of your method to something else, it might help.
You aren't passing the id attribute to the create_or_update method in the first place, so you don't need to call it, just call create instead, like so:
def save_tags(tags)
profile = find_profile #finds the correct profile. And I confirm that it exists with a printout
tags.each do |t|
ProfileTag.create(:profile_id => profile.profile_id, :tag_id => t.id)
end
end