Rails nested attributes not being updated - ruby-on-rails

I'm building my first Rails app and one of the important features in it is having users who speak and/or want to learn languages. In the user edit profile page, I allow him/her to choose what languages he/she speaks and/or wants to learn from a list (I'm using ryanb's nested_form gem):
There are 3 models involved in this: User, Speaks, Language
The languages table is just a table with languages of the world, it doesn't change. It consists basically of ISO codes for languages and their names. I populate it by running a script that reads from the official file I downloaded. Still I was simply using Rails defaults, so the table had an id column, and it was all working fine.
Then I decided to make a change and remove the id column, because it didn't make any sense anyway. I want my app to be up to date with the ISO list. I want the ISO code to identify the languages, not a meaningless id. I want to use
user.speaks.create!(language_id: "pt", level: 6)
instead of
user.speaks.create!(language_id: 129, level: 6)
I know it's unlikely that the ISO list will change but, if it does, I want to simply run my script again with the new file and not worry if the id column will still match the same ISO code as before. So I made the change. Now I can use user.speaks.create the way I want and the association works perfectly in the console. The problem is my form simply isn't working anymore. The data is sent but I don't understand the logs. They show a bunch of SELECTS but no INSERTS or UPDATES, I don't get why. Does anybody have any idea?
Here are my models:
class User < ActiveRecord::Base
attr_accessible ..., :speaks, :speaks_attributes, :wants_to_learn_attributes
has_many :speaks, :class_name => "Speaks", :dependent => :destroy
has_many :speaks_languages, :through => :speaks, :source => :language #, :primary_key => "iso_639_1_code"
has_many :wants_to_learn, :class_name => "WantsToLearn", :dependent => :destroy
has_many :wants_to_learn_languages, :through => :wants_to_learn, :source => :language #, :primary_key => "iso_639_1_code"
...
accepts_nested_attributes_for :speaks #, :reject_if => :speaks_duplicate, :allow_destroy => true
accepts_nested_attributes_for :wants_to_learn #, :reject_if => :wants_to_learn_duplicate, :allow_destroy => true
# EDIT 1: I remembered these pieces of code silenced errors, so I commented them out
...
end
class Speaks < ActiveRecord::Base
self.table_name = "speak"
attr_accessible :language, :language_id, :level
belongs_to :user
belongs_to :language
validates :user, :language, :level, presence: true
...
end
#EDIT 4:
class WantsToLearn < ActiveRecord::Base
self.table_name = "want_to_learn"
attr_accessible :language, :language_id
belongs_to :user
belongs_to :language
validates :user, :language, presence: true
...
end
class Language < ActiveRecord::Base
attr_accessible :iso_639_1_code, :name_en, :name_fr, :name_pt
has_many :speak, :class_name => "Speaks"
has_many :users_who_speak, :through => :speak, :source => :user
has_many :want_to_learn, :class_name => "WantsToLearn"
has_many :users_who_want_to_learn, :through => :want_to_learn, :source => :user
end
Controller:
def update
logger.debug params
if #user.update_attributes(params[:user])
#user.save
flash[:success] = "Profile updated"
sign_in #user
redirect_to :action => :edit
else
render :action => :edit
end
end
View:
<%= nested_form_for(#user, :html => { :class => "edit-profile-form"} ) do |f| %>
<%= render 'shared/error_messages' %>
<table border="0">
<tr><td colspan="2"><h2 id="languages" class="bblabla">Languages</h2></td></tr>
<tr>
<td><span>Languages you speak</span></td>
<td class="languages-cell">
<div id="speaks">
<%= f.fields_for :speaks, :wrapper => false do |speaks| %>
<div class="fields">
<%= speaks.select(:language_id,
Language.all.collect {|lang| [lang.name_en, lang.id]},
{ :selected => speaks.object.language_id, :include_blank => false },
:class => 'language') %>
<%= speaks.label :level, "Level: " %>
<%= speaks.select(:level, Speaks.level_options, { :selected => speaks.object.level }, :class => 'level') %>
<%= speaks.link_to_remove raw("<i class='icon-remove icon-2x'></i>"), :class => "remove-language" %>
</div>
<% end %>
</div>
<p class="add-language"><%= f.link_to_add "Add language", :speaks, :data => { :target => "#speaks" } %></p>
</td>
</tr>
...
Log:
Started PUT "/users/1" for 127.0.0.1 at 2013-07-19 08:41:16 -0300
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZmaU9...", "user"=>{"speaks_attributes"=>{"0"=>{"language_id"=>"pt", "level"=>"6", "_destroy"=>"false"}, "1374234067848"=>{"language_id"=>"en", "level"=>"5", "_destroy"=>"false"}}, "wants_to_learn_attributes"=>{"0"=>{"language_id"=>"ro", "_destroy"=>"false", "id"=>"1"}}, "home_location_attributes"=>{"google_id"=>"7789d9...", "latitude"=>"-22.9035393", "longitude"=>"-43.20958689999998", "city"=>"Rio de Janeiro", "neighborhood"=>"", "administrative_area_level_1"=>"Rio de Janeiro", "administrative_area_level_2"=>"", "country_id"=>"BR", "id"=>"1"}, "gender"=>"2", "relationship_status"=>"2", "about_me"=>""}, "commit"=>"Save changes", "id"=>"1"}
[1m[35mUser Load (0.3ms)[0m SELECT "users".* FROM "users" WHERE "users"."remember_token" = 'bjdvI...' LIMIT 1
[1m[36mUser Load (0.2ms)[0m [1mSELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1[0m [["id", "1"]]
{"utf8"=>"✓", "_method"=>"put", "authenticity_token"=>"ZmaU9W...", "user"=>{"speaks_attributes"=>{"0"=>{"language_id"=>"pt", "level"=>"6", "_destroy"=>"false"}, "1374234067848"=>{"language_id"=>"en", "level"=>"5", "_destroy"=>"false"}}, "wants_to_learn_attributes"=>{"0"=>{"language_id"=>"ro", "_destroy"=>"false", "id"=>"1"}}, "home_location_attributes"=>{"google_id"=>"7789d9...", "latitude"=>"-22.9035393", "longitude"=>"-43.20958689999998", "city"=>"Rio de Janeiro", "neighborhood"=>"", "administrative_area_level_1"=>"Rio de Janeiro", "administrative_area_level_2"=>"", "country_id"=>"BR", "id"=>"1"}, "gender"=>"2", "relationship_status"=>"2", "about_me"=>""}, "commit"=>"Save changes", "action"=>"update", "controller"=>"users", "id"=>"1"}
[1m[35m (0.1ms)[0m BEGIN
[1m[36mWantsToLearn Load (0.2ms)[0m [1mSELECT "want_to_learn".* FROM "want_to_learn" WHERE "want_to_learn"."user_id" = 1 AND "want_to_learn"."id" IN (1)[0m
[1m[35mLocation Load (0.3ms)[0m SELECT "locations".* FROM "locations" WHERE "locations"."google_id" = '7789d...' AND "locations"."latitude" = '-22.9035393' AND "locations"."longitude" = '-43.20958689999998' AND "locations"."city" = 'Rio de Janeiro' AND "locations"."neighborhood" = '' AND "locations"."administrative_area_level_1" = 'Rio de Janeiro' AND "locations"."administrative_area_level_2" = '' AND "locations"."country_id" = 'BR' LIMIT 1
[1m[36mUser Exists (40.0ms)[0m [1mSELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('ariel#pontes.com') AND "users"."id" != 1) LIMIT 1[0m
[1m[35m (96.7ms)[0m UPDATE "users" SET "remember_token" = 'd0pb...', "updated_at" = '2013-07-19 11:41:16.808422' WHERE "users"."id" = 1
[1m[36m (28.7ms)[0m [1mCOMMIT[0m
[1m[35m (0.1ms)[0m BEGIN
[1m[36mUser Exists (0.3ms)[0m [1mSELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('ariel#pontes.com') AND "users"."id" != 1) LIMIT 1[0m
[1m[35m (0.3ms)[0m UPDATE "users" SET "remember_token" = 'gKlW...', "updated_at" = '2013-07-19 11:41:17.072654' WHERE "users"."id" = 1
[1m[36m (0.4ms)[0m [1mCOMMIT[0m
Rendered shared/_error_messages.html.erb (0.0ms)
[1m[35mSpeaks Load (0.3ms)[0m SELECT "speak".* FROM "speak" WHERE "speak"."user_id" = 1
[1m[36mWantsToLearn Load (0.2ms)[0m [1mSELECT "want_to_learn".* FROM "want_to_learn" WHERE "want_to_learn"."user_id" = 1[0m
[1m[35mLanguage Load (0.3ms)[0m SELECT "languages".* FROM "languages"
[1m[36mCountry Load (0.3ms)[0m [1mSELECT "countries".* FROM "countries" WHERE "countries"."iso_3166_code" = 'BR' LIMIT 1[0m
[1m[35mCACHE (0.0ms)[0m SELECT "languages".* FROM "languages"
[1m[36mCACHE (0.0ms)[0m [1mSELECT "languages".* FROM "languages" [0m
Rendered users/edit.html.erb within layouts/application (39.8ms)
Rendered layouts/_shim.html.erb (0.0ms)
Rendered layouts/_header.html.erb (1.1ms)
Rendered layouts/_footer.html.erb (0.2ms)
Completed 200 OK in 576ms (Views: 160.7ms | ActiveRecord: 168.7ms)
Hope someone has an insight cause I've been looking all over the internet for the past 2 days with no luck. Thanks in advance!
EDIT 1
I placed the accepts_nested_attributes_for lines after the associations were made, as suggested by ovatsug25, but it didn't seem to make any change. I remembered, however, that there were some options in the User model that silenced errors, which of course hinders the debugging, so I commented these options out. Now I have the following error:
PG::Error: ERROR: operator does not exist: character varying = integer
LINE 1: ...M "languages" WHERE "languages"."iso_639_1_code" = 0 LIMIT ...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT "languages".* FROM "languages" WHERE "languages"."iso_639_1_code" = 0 LIMIT 1
I have NO IDEA why Rails is trying to select a language with pk = 0. Even if the pk WAS an integer this wouldn't make sense (would it???) since the default id starts from 1. And even if it started from zero, why would it be trying to select it anyway? Where is this zero comming from?? And I can't "add explicit type casts". The pk is a string and will never be 0 or '0' for that matter. This query doesn't make sense and simply isn't supposed to happen!
EDIT 2
I tried to update the attributes in the console and got the following:
irb(main):006:0> ariel = User.find(1)
User Load (101.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
=> #<User id: 1, first_name: "Ariel", last_name: "Pontes", ...>
irb(main):007:0> params = {"user"=>{"speaks_attributes"=>{"0"=>{"language_id"=>"pt", "level"=>"6", "_destroy"=>"false"}, "1374444891951"=>{"language_id"=>"en", "l
evel"=>"5", "_destroy"=>"false"}}, "wants_to_learn_attributes"=>{"0"=>{"language_id"=>"ro", "_destroy"=>"false"}}, "home_location_attributes"=>{"google_id"=>"778...c5a", "latitude"=>"-22.9035393", "longitude"=>"-43.20958689999998", "city"=>"Rio de Janeiro", "neighborhood"=>"", "administrative
_area_level_1"=>"Rio de Janeiro", "administrative_area_level_2"=>"", "country_id"=>"BR", "id"=>"1"}, "gender"=>"2", "relationship_status"=>"2", "about_me"=>""}}
=> {"user"=>{"speaks_attributes"=>{"0"=>{"language_id"=>"pt", "level"=>"6", "_destroy"=>"false"}, "1374444891951"=>{"language_id"=>"en", "level"=>"5", "_destroy"=
>"false"}}, "wants_to_learn_attributes"=>{"0"=>{"language_id"=>"ro", "_destroy"=>"false"}}, "home_location_attributes"=>{"google_id"=>"778...c5a", "latitude"=>"-22.9035393", "longitude"=>"-43.20958689999998", "city"=>"Rio de Janeiro", "neighborhood"=>"", "administrative_area_level_1"=>"Rio de
Janeiro", "administrative_area_level_2"=>"", "country_id"=>"BR", "id"=>"1"}, "gender"=>"2", "relationship_status"=>"2", "about_me"=>""}}
irb(main):008:0> ariel.update_attributes(params[:user])
(0.1ms) BEGIN
User Exists (0.5ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('ariel#pontes.com') AND "users"."id" != 1) LIMIT 1
(24.9ms) UPDATE "users" SET "remember_token" = '0tv...Cw', "updated_at" = '2013-07-22 15:45:30.705217' WHERE "users"."id" = 1
(54.3ms) COMMIT
=> true
irb(main):009:0>
Basically, it only updates the remember_token and updated_at for some reason.
EDIT 3
I tried to update only the spoken languages and it worked:
irb(main):012:0> ariel.update_attributes({"speaks_attributes"=>{"0"=>{"language_id"=>"pt", "level"=>"6", "_destroy"=>"false"}, "1374444891951"=>{"language_id"=>"e
n", "level"=>"5", "_destroy"=>"false"}}})
(0.2ms) BEGIN
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Language Load (0.8ms) SELECT "languages".* FROM "languages" WHERE "languages"."iso_639_1_code" = 'pt' LIMIT 1
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Language Load (0.2ms) SELECT "languages".* FROM "languages" WHERE "languages"."iso_639_1_code" = 'en' LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('ariel#pontes.com') AND "users"."id" != 1) LIMIT 1
(0.2ms) UPDATE "users" SET "remember_token" = 'MYh5X1XoF6OsVIo3rhDNzQ', "updated_at" = '2013-07-22 22:05:08.198025' WHERE "users"."id" = 1
SQL (42.9ms) INSERT INTO "speak" ("created_at", "language_id", "level", "updated_at", "user_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Mo
n, 22 Jul 2013 22:05:08 UTC +00:00], ["language_id", "pt"], ["level", 6], ["updated_at", Mon, 22 Jul 2013 22:05:08 UTC +00:00], ["user_id", 1]]
SQL (0.4ms) INSERT INTO "speak" ("created_at", "language_id", "level", "updated_at", "user_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Mon
, 22 Jul 2013 22:05:08 UTC +00:00], ["language_id", "en"], ["level", 5], ["updated_at", Mon, 22 Jul 2013 22:05:08 UTC +00:00], ["user_id", 1]]
(14.7ms) COMMIT
=> true
I'm starting to fear it may be a case of witchcraft.
PS: Does anybody know why it loads the User 3 times? Seems rather pointless and wasteful.

The biggest clue is this error that caught your eye:
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT "languages".* FROM "languages" WHERE "languages"."iso_639_1_code" = 0 LIMIT 1
If you're providing a string value for a model attribute, but the underlying database column is a numeric column, Rails will try to convert the string value to the appropriate numeric type. So, if the underlying column is of type integer, the string input will be interpreted as an integer, using String#to_i. If the string doesn't start with a number, it will be converted to 0.
The Rails console (rails c) can be a useful tool for debugging issues like this. In this case, on the console, you can run WantsToLearn.columns_hash['language_id'].type to see what type Rails thinks it should be using for that attribute. Of course, you can also just as easily check the migrations.

I used to have a problem like this and solved it by segreating the accepts_attributes_for calls to the very bottom after all associations and accessible attributes have been declared. (I also merged attr_accesible into one call. I think ryanb says something in this video about the order of the calls. http://railscasts.com/episodes/196-nested-model-form-revised?view=asciicast.
Makes sense? No. But it worked for me.

Related

Associations cause "EMPTY" in Active Admin

So I'm learning more about belongs_to and has_many Associations in Rails and am combining it with ActiveAdmin.
I have created a Model "Semester" and a Model "Field". A Semester has many Fields and a Field belongs to Semester.
My field Class looks like this:
class Field < ApplicationRecord
belongs_to :semester
accepts_nested_attributes_for :semester, allow_destroy: true
end
and my Semester class looks like this:
class Semester < ApplicationRecord
has_many :fields
accepts_nested_attributes_for :fields, allow_destroy: true
end
Now I registered the Models with active admin with the following two files:
ActiveAdmin.register Field do
permit_params :name, semesters_attributes: [:name]
end
and
ActiveAdmin.register Semester do
permit_params :name, :fields, fields_attributes: [ :field_id, :name]
end
And now there are two issues that come up upon proceeding that I absolutely can not ged rid off:
1) If I do not add optional: true after belongs_to :semester I will get an error message "must exist" upon trying to create a new Field with a respective Semester.
2) If I do add optional: true after belongs_to :semester I will be able to create a new Field but the "Semester" will just be "EMPTY" in the new field.
The console output of case 2) will look like this:
Started POST "/admin/fields" for 127.0.0.1 at 2018-08-17 15:23:54 +0200
Processing by Admin::FieldsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+GPjjNPOv9GsjXnEtEjBcC0xUMHKKC+YpFLfiUFUOgsgBJ+pLCucscrN0YaTk551GFp4K5lBEI2RW1clw2vCWw==", "field"=>{"semester_id"=>"2", "name"=>"MAVT"}, "commit"=>"Create Field"}
AdminUser Load (0.1ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = ? ORDER BY "admin_users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
Unpermitted parameter: :semester_id
(0.0ms) begin transaction
↳ /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
Field Create (0.6ms) INSERT INTO "fields" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "MAVT"], ["created_at", "2018-08-17 13:23:54.026418"], ["updated_at", "2018-08-17 13:23:54.026418"]]
↳ /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
(12.2ms) commit transaction
↳ /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
Redirected to http://0.0.0.0:3000/admin/fields/22
Completed 302 Found in 22ms (ActiveRecord: 13.0ms)
Started GET "/admin/fields/22" for 127.0.0.1 at 2018-08-17 15:23:54 +0200
Processing by Admin::FieldsController#show as HTML
Parameters: {"id"=>"22"}
AdminUser Load (0.2ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = ? ORDER BY "admin_users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
Field Load (0.1ms) SELECT "fields".* FROM "fields" WHERE "fields"."id" = ? LIMIT ? [["id", 22], ["LIMIT", 1]]
↳ /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
Rendering /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activeadmin-1.3.1/app/views/active_admin/resource/show.html.arb
(0.1ms) SELECT COUNT(*) FROM "active_admin_comments" WHERE "active_admin_comments"."resource_type" = ? AND "active_admin_comments"."resource_id" = ? AND "active_admin_comments"."namespace" = ? [["resource_type", "Field"], ["resource_id", 22], ["namespace", "admin"]]
↳ /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
ActiveAdmin::Comment Exists (0.1ms) SELECT 1 AS one FROM "active_admin_comments" WHERE "active_admin_comments"."resource_type" = ? AND "active_admin_comments"."resource_id" = ? AND "active_admin_comments"."namespace" = ? LIMIT ? OFFSET ? [["resource_type", "Field"], ["resource_id", 22], ["namespace", "admin"], ["LIMIT", 1], ["OFFSET", 0]]
↳ /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
Rendered /home/divepit/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activeadmin-1.3.1/app/views/active_admin/resource/show.html.arb (73.4ms)
Completed 200 OK in 77ms (Views: 74.9ms | ActiveRecord: 0.5ms)
Thanks in advance for any tips on how to solve this! :)
First things first. Your associations are correct, but as per your associations you should not have field_id in semester table. Instead you should have semester_id in fields table. Also you should change semesters_attributes to semester_attributes
Unpermitted parameter: :semester_id
You should permit semester_id in the fields_attributes
fields_attributes: [ :semester_id, :name]
And finally in Rails 5, whenever a belongs_to association is defined, it is required to have the associated record present by default. To avoid this default behavior, you need to add optional: true

How to create check boxes, select multiple items and store them -- in rails?

Working on a video-game review website for practice.
When creating a new video-game object, I have to assign platforms to them (xbox, playstation etc.)
my form is below and the Game model has many platforms, while the Platforms model belongs to game
Currently I use collection select. how do i turn this into checkboxes, and be able to select multiple platofrms with the checkboxes and save them?
thanks so much.
server log
Started PATCH "/games/6" for ::1 at 2015-07-08 05:07:59 -0400
Processing by GamesController#update as HTML
Parameters: {"utf8"=>"√", "authenticity_token"=>"n9+Tnko0702pGPDbC3/rzdqGx92yR
ZZymKw3zd+dLwjl+zVqlrEa1yby1/xeXJVeXr+sht8XgXOD0ytCL+dnMw==", "game"=>{"title"=>
"Test", "release_date(1i)"=>"2015", "release_date(2i)"=>"7", "release_date(3i)"=
>"8", "genre_id"=>["1", ""], "platform_id"=>["1", ""]}, "commit"=>"Update Game",
"id"=>"6"}
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDE
R BY "users"."id" ASC LIMIT 1 [["id", 1]]
Game Load (1.0ms) SELECT "games".* FROM "games" WHERE "games"."id" = ? LIMIT
1 [["id", 6]]
Unpermitted parameters: genre_id, platform_id
(0.0ms) begin transaction
(0.0ms) commit transaction
Redirected to http://localhost:3000/games/6
Completed 302 Found in 10ms (ActiveRecord: 1.0ms)
Started GET "/games/6" for ::1 at 2015-07-08 05:07:59 -0400
Processing by GamesController#show as HTML
Parameters: {"id"=>"6"}
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDE
R BY "users"."id" ASC LIMIT 1 [["id", 1]]
Game Load (0.0ms) SELECT "games".* FROM "games" WHERE "games"."id" = ? LIMIT
1 [["id", 6]]
(0.0ms) SELECT COUNT("previews"."vote") FROM "previews" WHERE "previews"."ga
me_id" = ? [["game_id", 6]]
CACHE (0.0ms) SELECT COUNT("previews"."vote") FROM "previews" WHERE "previews
"."game_id" = ? [["game_id", 6]]
(0.0ms) SELECT COUNT("reviews"."vote") FROM "reviews" WHERE "reviews"."game_
id" = ? [["game_id", 6]]
CACHE (0.0ms) SELECT COUNT("reviews"."vote") FROM "reviews" WHERE "reviews"."
game_id" = ? [["game_id", 6]]
News Load (1.0ms) SELECT "news".* FROM "news" WHERE "news"."game_id" = ? OR
DER BY "news"."id" DESC LIMIT 5 [["game_id", 6]]
Rendered games/_review.html.erb (2.0ms)
Review Load (1.0ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."game_i
d" = ? [["game_id", 6]]
(0.0ms) SELECT COUNT(*) FROM "reviews" WHERE "reviews"."game_id" = ? [["gam
e_id", 6]]
Review Load (0.0ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."game_
id" = ? ORDER BY "reviews"."id" DESC LIMIT 5 [["game_id", 6]]
Rendered games/show.html.erb within layouts/application (16.0ms)
Completed 200 OK in 162ms (Views: 157.1ms | ActiveRecord: 2.0ms)
server log 2 when using Emu's suggestion <-- still not saving, but unpermitted parameters error is gone.
Started PATCH "/games/6" for ::1 at 2015-07-08 05:56:55 -0400
Processing by GamesController#update as HTML
Parameters: {"utf8"=>"√", "authenticity_token"=>"lzku1Wa89d6a2i2yArfyiyQwfhLcW
iigah0Pv6XpUAjtHYghujkARBUwCpVXlIwYoAkVSbEIP6FxYhMwVZMYMw==", "game"=>{"title"=>
"Test", "release_date(1i)"=>"2015", "release_date(2i)"=>"7", "release_date(3i)"=
>"8"}, "platform_ids"=>["4", "5"], "commit"=>"Update Game", "id"=>"6"}
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDE
R BY "users"."id" ASC LIMIT 1 [["id", 1]]
Game Load (0.0ms) SELECT "games".* FROM "games" WHERE "games"."id" = ? LIMIT
1 [["id", 6]]
(0.0ms) begin transaction
(0.0ms) commit transaction
(0.0ms) begin transaction
(0.0ms) commit transaction
Redirected to http://localhost:3000/games/6
Completed 302 Found in 9ms (ActiveRecord: 0.0ms)
Started GET "/games/6" for ::1 at 2015-07-08 05:56:55 -0400
Processing by GamesController#show as HTML
Parameters: {"id"=>"6"}
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDE
R BY "users"."id" ASC LIMIT 1 [["id", 1]]
Game Load (0.0ms) SELECT "games".* FROM "games" WHERE "games"."id" = ? LIMIT
1 [["id", 6]]
(0.0ms) SELECT COUNT("previews"."vote") FROM "previews" WHERE "previews"."ga
me_id" = ? [["game_id", 6]]
CACHE (0.0ms) SELECT COUNT("previews"."vote") FROM "previews" WHERE "previews
"."game_id" = ? [["game_id", 6]]
(0.0ms) SELECT COUNT("reviews"."vote") FROM "reviews" WHERE "reviews"."game_
id" = ? [["game_id", 6]]
CACHE (0.0ms) SELECT COUNT("reviews"."vote") FROM "reviews" WHERE "reviews"."
game_id" = ? [["game_id", 6]]
News Load (0.0ms) SELECT "news".* FROM "news" WHERE "news"."game_id" = ? OR
DER BY "news"."id" DESC LIMIT 5 [["game_id", 6]]
Rendered games/_review.html.erb (7.0ms)
Review Load (0.0ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."game_i
d" = ? [["game_id", 6]]
(0.0ms) SELECT COUNT(*) FROM "reviews" WHERE "reviews"."game_id" = ? [["gam
e_id", 6]]
Review Load (1.0ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."game_
id" = ? ORDER BY "reviews"."id" DESC LIMIT 5 [["game_id", 6]]
Rendered games/show.html.erb within layouts/application (26.0ms)
Completed 200 OK in 197ms (Views: 177.1ms | ActiveRecord: 3.0ms)
games table
create_table "games", force: :cascade do |t|
t.string "title"
t.string "image"
t.date "release_date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "platform_id"
t.integer "genre_id"
t.integer "review_id"
t.integer "steelbook_id"
end
_game.html.erb <-- if I change the check boxes to use "genre_ids" and "platform_ids" i get a no method error at games#edit controller
<%= form_for #game do |g| %>
<p><%= g.label :title %></p>
<p><%= g.text_field :title %></p>
<p><%= g.label :release_date %></p>
<p><%= g.date_select :release_date %></p>
<p><%= g.label :genre_id %></p>
<p><%= g.collection_check_boxes :genre_id, #genres, :id, :category %></p>
<p><%= g.label "Platform(s)" %></p>
<p><%= g.collection_check_boxes :platform_id, #platforms, :id, :system %></p>
<p><%= g.label :image %></p>
<p><%= g.file_field :image %></p>
<hr>
<p><%= g.submit %></p>
<% end %>
game controller
class GamesController < ApplicationController
before_action :authenticate_user!
before_action :set_game, only: [:show, :edit, :update, :destroy]
def index
#games = Game.all
end
def show
#reviews = Review.where(game_id: #game.id)
#previews = Preview.where(game_id: #game.id)
#news = News.where(game_id: #game.id)
end
def create
#game = Game.new(game_params)
#game.save
redirect_to #game
end
def new
#game = Game.new
set_platforms
set_genres
end
def edit
set_platforms
set_genres
end
def update
#game.update(game_params)
redirect_to #game
end
def destroy
#game.destroy
redirect_to root_path
end
private
def game_params
params.require(:game).permit(:title, :image, :release_date, :genre_ids => [], :platform_ids => [])
end
def set_game
#game = Game.find(params[:id])
end
def set_platforms
#platforms = Platform.order(:system)
end
def set_genres
#genres = Genre.order(:category)
end
end
routes
Rails.application.routes.draw do
devise_for :users
root "games#index"
resources :games do
resources :news
resources :reviews, except: [:show]
resources :previews, except: [:show]
end
resources :platforms
resources :genres
end
platforms model
class Platform < ActiveRecord::Base
belongs_to :game
end
game model
class Game < ActiveRecord::Base
has_many :platforms
has_many :preview, through: :users
has_many :reviews, through: :users
has_many :news
has_one :genre
end
You can use collection_check_boxes.
<p><%= g.collection_check_boxes :platform_ids, #platforms, :id, :system %></p>
And change game_params to the following.
def game_params
params.require(:game).permit(:title, :image, :release_date, :genre_id, :platform_ids => [])
end
Didn't tested it. I guess you require a little tweak in the code.
For each platform create a new checkbox using the loop below:
<% #platforms.each do |platform| %>
<%= check_box_tag "platform_ids[]", platform.id %>
<%= platform.name %> // I assume that your platform has a name which can be used as a label
<% end %>
In the controller you can access the values of the checkboxes using params[:platform_ids]
Just change the game_params method in the controller with the following code:
def game_params
params.require(:game).permit(:title, :image, :release_date, :genre_id, :platform_ids => [])
end
or
you can see a railscasts video.

During testing in Rails 3, updating a record causes related records to be deleted

I'm running some tests using minitest in Rails 3. I have a requirements model and a ind_requirements model which serves as an intersection between the industries model and requirements model. I have a dependent :destroy set on the has_many on the requirements model, but removing that doesn't seem to have any impact on what I'm seeing.
This is all controller testing, so I'm not interacting with the UI or I would suspect something like what is reported here: Active record update_attribute executes a DELETE query on an associated object(child) of an object(parent) when parent is updated
When the #requirement.update_attributes is hit, then I get the following output in the log:
Processing by RequirementsController#update as HTML
Parameters: {"requirement"=>{"reqText"=>"second version", "reqTitle"=>"Second Version"}, "id"=>"1"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
SQL (0.1ms) SELECT "requirements"."id" AS t0_r0, "requirements"."reqTitle" AS t0_r1, "requirements"."reqText" AS t0_r2, "requirements"."created_at" AS t0_r3, "requirements"."updated_at" AS t0_r4, "requirements"."category_id" AS t0_r5, "requirements"."status" AS t0_r6, "requirements"."source_id" AS t0_r7, "requirements"."sortOrder" AS t0_r8, "requirements"."version" AS t0_r9, "requirements"."active" AS t0_r10, "categories"."id" AS t1_r0, "categories"."catName" AS t1_r1, "categories"."created_at" AS t1_r2, "categories"."updated_at" AS t1_r3, "categories"."catAbbr" AS t1_r4 FROM "requirements" LEFT OUTER JOIN "categories" ON "categories"."id" = "requirements"."category_id" WHERE "requirements"."active" = 't' AND "requirements"."id" = ? ORDER BY categories.catName LIMIT 1 [["id", "1"]]
(0.2ms) SELECT COUNT(*) FROM "roles" INNER JOIN "role_assignments" ON "roles"."id" = "role_assignments"."role_id" WHERE "role_assignments"."user_id" = 1 AND (roleName = 'Editor')
(0.2ms) SELECT COUNT(*) FROM "roles" INNER JOIN "role_assignments" ON "roles"."id" = "role_assignments"."role_id" WHERE "role_assignments"."user_id" = 1 AND (roleName = 'Administrator')
(0.2ms) SAVEPOINT active_record_1
Industry Load (0.2ms) SELECT "industries".* FROM "industries" INNER JOIN "ind_requirements" ON "industries"."id" = "ind_requirements"."industry_id" WHERE "ind_requirements"."requirement_id" = 1 ORDER BY indName
SQL (0.2ms) DELETE FROM "ind_requirements" WHERE "ind_requirements"."requirement_id" = 1 AND "ind_requirements"."industry_id" = 2
SQL (0.5ms) INSERT INTO "requirement_versions" ("category_id", "created_at", "reqText", "reqTitle", "req_id", "status", "updated_at", "version") VALUES (?, ?, ?, ?, ?, ?, ?, ?) [["category_id", nil], ["created_at", Wed, 12 Mar 2014 06:00:13 UTC +00:00], ["reqText", "This is a test general requirement for the purpose of, um, testing things"], ["reqTitle", "Test Requirement 19"], ["req_id", 1], ["status", "Public"], ["updated_at", Wed, 12 Mar 2014 06:00:13 UTC +00:00], ["version", 1]]
(0.1ms) UPDATE "requirements" SET "reqText" = 'second version', "reqTitle" = 'Second Version', "version" = 2, "updated_at" = '2014-03-12 06:00:13.418730' WHERE "requirements"."id" = 1
(0.1ms) RELEASE SAVEPOINT active_record_1
Note the DELETE FROM. This then breaks my test as once these records are deleted from the test database, my subsequent code doesn't work properly. This delete is not triggered when running in development, just in test.
Is there a config option I'm missing?
Here is the requirement.rb file (without all of the other methods that aren't part of this):
class Requirement < ActiveRecord::Base
has_many :ind_requirements, dependent: :destroy
has_many :requirement_versions
has_many :industries, through: :ind_requirements
has_many :responses
belongs_to :category
scope :active, where(:active => true)
default_scope active.includes(:category).order('categories.catName')
scope :submitted, where(:status => :Submitted)
scope :public, where(:status => :Public)
accepts_nested_attributes_for :requirement_versions
attr_accessible :reqText, :reqTitle, :industry_ids, :category_id, :catName, :status, :user_id, :source_id, :catAbbr, :reqNumber, :sortOrder, :requirement_versions_attributes
Here is the actual test case:
test "When a requirement is reverted, the previous associated industry list is restored" do
login_admin
#requirement = create(:requirement)
#industry = create(:industry, indName: "First Industry")
#indLink = create(:ind_requirement, requirement_id: #requirement.id)
#indLink.save!
put :update, id: #requirement, requirement: { reqText: "second version", reqTitle: "Second Version"}
#industry = create(:industry, indName: "Second Industry")
#indLink.industry_id = #industry.id
#indLink.save!
get :revert, id: #requirement
#requirement = Requirement.find(#requirement.id)
#indLink = #requirement.ind_requirements.first
assert_equal(#indLink.industry.indName, "First Industry")
end
The problem is that when I was sending the update to the requirements, I was only sending the updated parameters: put :update, id: #requirement, requirement: { reqText: "second version", reqTitle: "Second Version"}, but not including the parameters for the checkboxes, as I wasn't modifying them. Unfortunately, the end result is that the industries are then deleted. I imagine because the absence of parameters is interpreted not that they aren't being updated, but rather that they have been deleted.

nested attributes works for create but fails when update a record

I have a problem with nested attributes. Creating works but when I update, the error message shows me that the values in the relation are not set. I can't find the reason.
The main model
class Product < ActiveRecord::Base
has_many :product_options
accepts_nested_attributes_for :product_options,
:allow_destroy => true,
:reject_if => proc { | r | r["name"].blank? or r["value"].blank? }
end
The nested model
class ProductOption < ActiveRecord::Base
belongs_to :product
validates :name, :presence => true
validates :value, :presence => true
end
The controller is a bit shorted.
Items is a model where Product is related to as has_one
class Admin::ProductsController < Admin::ApplicationController
before_action :set_product, only: [ :new, :show, :edit, :update, :destroy ]
def create
#product = Product.new( product_params )
respond_to do |format|
if #product.save
#product.product_options.build
format.js { render :js => "alert( 'Daten gespeichert!' );" }
else
format.js { render :js => 'alert( "Fehler beim Speichern!" );' }
end
end
end
def update
respond_to do |format|
if #product.update( product_params )
format.js { render :js => "alert( 'Daten gespeichert!' );" }
else
require "pp"
pp #product.errors
format.js { render :js => 'alert( "Fehler beim Speichern!" );' }
end
end
end
private
# UPDATE: creating the product_options at this time
# produces the described error :)
def set_product
#item = Item.find_by_id( params[ :item_id ] ) if params[ :item_id ]
#product = #item.product ? #item.product : #item.build_product
# WRONG Place for generating new options
# 2.times { #product.product_options.build }
end
def product_params
params.require( :product ).permit( :item_id, :name, :title, :active, :product_options_attributes => [ :id, :name, :value, :_destroy ] )
end
end
The console output for the create is working and looks like
Started POST "/admin/items/653/product" for 127.0.0.1 at 2014-02-14 15:12:14 +0100
Processing by Admin::ProductsController#create as JS
Parameters: {"utf8"=>"✓", "product"=>{"item_id"=>"653", "name"=>"1", "title"=>"1", "active"=>"1", "product_options_attributes"=>{"0"=>{"name"=>"aaa", "value"=>"aaaa"}}}, "commit"=>"Create Product", "item_id"=>"653"}
User Load (1.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
(0.6ms) BEGIN
SQL (17.5ms) INSERT INTO "products" ("created_at", "item_id", "name", "title", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["item_id", 653], ["name", "1"], ["title", "1"], ["updated_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00]]
SQL (1.3ms) INSERT INTO "product_options" ("created_at", "name", "product_id", "updated_at", "value") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["name", "aaa"], ["product_id", 28], ["updated_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["value", "aaaa"]]
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 ORDER BY "items"."id" ASC LIMIT 1 [["id", 653]]
ProductOption Load (1.3ms) SELECT "product_options".* FROM "product_options" WHERE "product_options"."product_id" = $1 [["product_id", 28]]
Rendered admin/products/_show.html.erb (7.6ms)
Rendered admin/products/create.js.erb (9.2ms)
Completed 200 OK in 448ms (Views: 40.0ms | ActiveRecord: 27.1ms)
The the update. I doesn't work and gives an error that the nested fields are empty. It's the pp inside the update method
Started PATCH "/admin/items/653/product" for 127.0.0.1 at 2014-02-14 15:15:03 +0100
Processing by Admin::ProductsController#update as JS
Parameters: {"utf8"=>"✓", "product"=>{"item_id"=>"653", "name"=>"1", "title"=>"1", "active"=>"1", "product_options_attributes"=>{"0"=>{"name"=>"aaa", "value"=>"aaaa", "id"=>"9"}, "1"=>{"name"=>"bbb", "value"=>"bbbb"}}}, "commit"=>"Update Product", "item_id"=>"653"}
User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
Item Load (0.6ms) SELECT "items".* FROM "items" WHERE "items"."id" = 653 LIMIT 1
Product Load (0.9ms) SELECT "products".* FROM "products" WHERE "products"."item_id" = $1 ORDER BY "products"."id" ASC LIMIT 1 [["item_id", 653]]
(0.6ms) BEGIN
ProductOption Load (1.3ms) SELECT "product_options".* FROM "product_options" WHERE "product_options"."product_id" = $1 AND "product_options"."id" IN (9) [["product_id", 28]]
(0.5ms) ROLLBACK
#<ActiveModel::Errors:0x007f8bdeb9f818
#base=
#<Product id: 28, item_id: 653, content: nil, active: 1, created_at: "2014-02-14 14:12:14", updated_at: "2014-02-14 14:12:14", name: "1", title: "1", ordernumber: "">,
#messages=
{:"product_options.name"=>["can't be blank"],
:"product_options.value"=>["can't be blank"]}>
Completed 200 OK in 18ms (Views: 0.1ms | ActiveRecord: 5.1ms)
I think I know what is the problem with your code. The accepts_nested_attributes_for does not require you to build any of the associated models. If the appropriate params are passed in then the model automatically builds or updates the associations.
In your case in the update method what you do is the following:
You find the relevant product. So far so good (although you could actually use a specific product id in your form)
Then you build two product options (in #set_product). This is the problem.
And in the end you update the model based on the parameters.
Now the problem with the second step is that you basically build two empty associated instances. Those are not affected by the accepts_nested_attributes. As a result you are trying to save 2+2 product options (the ones you build and the ones created by the params). Obviously you get the validation error due to the fact the two of the models have no attributes set.
You can make sure my hypothesis is correct by removing the validators from ProductOption. On update you should get 4 associated product options persisted.

Rails/ActionMailer/Postmark-rails sending same e-mail multiple times

I have a problem. My mailer sends the same e-mail 4 times. I'm pretty sure I only call it once though. What could be the reason?
Running Rails 3.0.7 on Ruby 1.9.2 and Unicorn, if that matters.
Here is the mailer:
class NotificationMailer < ActionMailer::Base
default :from => "herald#artistsnclients.com"
def order_new(user, order)
#user = user
#order = order
mail( :to => "#{user.name} <#{user.email}>", :subject => "You have one new order to review (##{order.id})" )
end
end
What calls the mailer is a callback in the model Notification:
class Notification < ActiveRecord::Base
attr_accessible :read, :user_id, :happening_type, :happening_id, :happening_status
enum_attr :happening_status, %w(new approved rejected cancelled payed completed accepted new_post)
belongs_to :user
belongs_to :happening, :polymorphic => true
after_create :send_email
private
def send_email
# Send e-mail here
if [:new, :approved, :rejected, :cancelled, :payed, :completed, :accepted].include? self.happening_status
right_mailer = NotificationMailer.method("order_#{self.happening_status}".to_sym)
right_mailer.call(self.user, self.happening).deliver
elsif [:new_post].include? self.happening_status
NotificationMailer.note_new(self.user, self.happening).deliver
end
end
end
The Notification object is created from a callback in the Note model:
class Note < ActiveRecord::Base
...
after_create :notify_artist
private
def notify_artist
self.notifications.create :read => false, :happening_status => :new, :user_id => self.artist_id
end
end
Also, here is the part of the log that belongs to the controller from which the Notification model is created:
Started POST "/notes" for 178.8.127.86 at 2011-07-15 18:03:45 +0000
Processing by NotesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"8UhgV74SUZibcrowriqQZAketiALnkpHMhu0bkuZ4VQ=", "note"=>{"content"=>"Okaaay, *will do*", "order_id"=>"1"}, "commit"=>"Reply"}
[1m[36mUser Load (0.1ms)[0m [1mSELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1[0m
[1m[35mSQL (0.1ms)[0m BEGIN
[1m[36mSQL (0.5ms)[0m [1mdescribe `notes`[0m
[1m[35mAREL (0.3ms)[0m INSERT INTO `notes` (`user_id`, `order_id`, `content`, `created_at`, `updated_at`) VALUES (1, 1, 'Okaaay, *will do*', '2011-07-15 18:03:45', '2011-07-15 18:03:45')
[1m[36mOrder Load (0.1ms)[0m [1mSELECT `orders`.* FROM `orders` WHERE `orders`.`id` = 1 LIMIT 1[0m
[1m[35mNotification Load (0.5ms)[0m SELECT `notifications`.* FROM `notifications` WHERE (`notifications`.happening_id = 12 AND `notifications`.happening_type = 'Note') LIMIT 1
[1m[36mSQL (0.9ms)[0m [1mdescribe `notifications`[0m
[1m[35mAREL (0.3ms)[0m INSERT INTO `notifications` (`read`, `user_id`, `happening_id`, `happening_type`, `created_at`, `updated_at`, `happening_status`) VALUES (0, 2, 12, 'Note', '2011-07-15 18:03:45', '2011-07-15 18:03:45', 'new_post')
[1m[36mUser Load (0.2ms)[0m [1mSELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1[0m
[1m[35mNote Load (0.3ms)[0m SELECT `notes`.* FROM `notes` WHERE `notes`.`id` = 12 LIMIT 1
Rendered notification_mailer/note_new.html.haml (3.6ms)
Sent mail to [MY E-MAIL] (2362ms)
Date: Fri, 15 Jul 2011 18:03:46 +0000
From: herald#artistsnclients.com
To: Dummy <[MY E-MAIL]>
Message-ID: <4e208102724f_55c11225a4584d7#artistsnclients.mail>
Subject: New message in order #1
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<p>Hello Dummy,</p>
<p>There is a new message in your order #1.</p>
<blockquote>
Okaaay, *will do*
</blockquote>
<hr>
<p>
With lots and lots of love,
<br>
Artists&Clients Staff
</p>
[1m[36mSQL (564.1ms)[0m [1mCOMMIT[0m
Redirected to http://dev.artistsnclients.com/orders/1
Completed 302 Found in 3623ms
Please try newer version of the gem named 0.9.8, I think we managed to fix that bug:
https://rubygems.org/gems/postmark
Let me know if it helped.

Resources