2

私は初めての Rails アプリを作成していますが、その重要な機能の 1 つは、言語を話す、または言語を学習したいユーザーがいるということです。ユーザー編集プロファイル ページで、彼/彼女が話す言語や学習したい言語をリストから選択できるようにします (私は ryanb の nested_form gem を使用しています)。

ここに画像の説明を入力

Userこれには、Speaks、 、の 3 つのモデルが関係しています。Language

このlanguages表は、世界の言語を並べた表にすぎず、変更されません。基本的には、言語の ISO コードとその名前で構成されています。ダウンロードした公式ファイルから読み取るスクリプトを実行して、データを入力します。それでも私は単純に Rails のデフォルトを使用していたので、テーブルには id 列があり、すべて正常に機能していました。

次に、とにかく意味がなかったので、変更を加えて id 列を削除することにしました。アプリを ISO リストで最新の状態にしたい。無意味なIDではなく、ISOコードで言語を識別したいのです。使いたい

user.speaks.create!(language_id: "pt", level: 6)

それ以外の

user.speaks.create!(language_id: 129, level: 6)

ISO リストが変更される可能性は低いことはわかっていますが、変更された場合は、新しいファイルでスクリプトを再度実行するだけで、id 列が以前と同じ ISO コードと一致するかどうか心配する必要はありません。だから私は変更を加えました。user.speaks.createこれで思い通りに使用でき、関連付けはコンソールで完全に機能します。問題は、フォームが機能しなくなったことです。データは送信されますが、ログがわかりません。一連の SELECT が表示されますが、INSERTS や UPDATE は表示されません。理由はわかりません。誰にもアイデアはありますか?

ここに私のモデルがあります:

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

コントローラ:

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

意見:

<%= 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>
        ...

ログ:

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)

過去 2 日間、運が悪くてインターネット中を探していたので、誰かが洞察力を持っていることを願っています。前もって感謝します!

編集1

ovatsug25 で提案されているように、関連付けが行われた後に行を配置しましたaccepts_nested_attributes_forが、変更はないようです。ただし、Userモデルにはエラーを黙らせるオプションがいくつかあったことを思い出しました。これはもちろんデバッグを妨げます。そのため、これらのオプションをコメントアウトしました。今、私は次のエラーがあります:

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

なぜ Rails が pk = 0 の言語を選択しようとしているのか、私にはわかりません。たとえ pk が整数だったとしても、デフォルトの ID は 1 から始まるため、これは意味をなさないでしょう (だろうか???)。ゼロから、とにかくそれを選択しようとするのはなぜですか? このゼロはどこから来ているのですか?? そして、「明示的な型キャストを追加する」ことはできません。pk は文字列であり、0 または '0' になることはありません。このクエリは意味がなく、単に発生するはずがありません!

編集2

コンソールで属性を更新しようとしたところ、次の結果が得られました。

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> 

基本的に、何らかの理由でremember_tokenandのみを更新します。updated_at

編集3

話し言葉のみを更新しようとしましたが、うまくいきました:

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

私はそれが魔法のケースかもしれないと恐れ始めています.

PS: ユーザーを 3 回ロードする理由を知っている人はいますか? かなり無意味で無駄に思えます。

4

2 に答える 2

2

最大の手がかりは、あなたの目を引いた次のエラーです。

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

モデル属性に文字列値を指定しているが、基礎となるデータベース列が数値列である場合、Rails は文字列値を適切な数値型に変換しようとします。したがって、基になる列が整数型の場合、文字列入力は を使用して整数として解釈されString#to_iます。文字列が数字で始まらない場合は、0 に変換されます。

Rails コンソール( ) は、このrails cような問題をデバッグするための便利なツールです。この場合、コンソールで実行WantsToLearn.columns_hash['language_id'].typeして、Rails がその属性に使用する必要があると判断した型を確認できます。もちろん、移行を簡単に確認することもできます。

于 2013-07-23T15:17:52.223 に答える
0

以前はこのような問題があり、accepts_attributes_forすべての関連付けとアクセス可能な属性が宣言された後、呼び出しを一番下に分離することで解決しました。(私もattr_accesible1 つの呼び出しにマージしました。このビデオで、呼び出しの順序について ryanb が何か言っていると思います。http://railscasts.com/episodes/196-nested-model-form-revised?view=asciicast .

理にかなっていますか?いいえ、しかし、それは私にとってはうまくいきました。

于 2013-07-22T02:04:08.337 に答える