2

我正在构建我的第一个Rails应用程序,其中的一个重要功能是让用户说出和/或想要学习语言。在用户编辑个人资料页,我允许他/她选择他/她讲什么语言和/或希望从列表中学习(我使用ryanb的nested_form宝石):未更新Rails嵌套属性

enter image description here

有3款车型参与了这一点:UserSpeaksLanguage

languages表仅仅是一个世界的语言表,它不会改变。它基本上由语言及其名称的ISO代码组成。我通过运行从我下载的官方文件读取的脚本来填充它。仍然我只是使用Rails的默认值,所以表中有一个ID列,它都工作正常。

然后我决定进行更改并删除ID列,因为它无论如何没有任何意义。我希望我的应用程序能够与ISO列表保持同步。我想要ISO代码来识别语言,而不是无意义的ID。我想用

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

,而不是

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

我知道这是不可能的ISO名单会改变,但是,如果这样做,我想简单地再次与新的文件,而不是运行我的脚本担心id列是否仍然与以前一样匹配相同的ISO代码。所以我做了改变。现在我可以按照我想要的方式使用user.speaks.create,并且该关联在控制台中完美工作。问题是我的表单根本无法工作了。数据已发送,但我不理解日志。他们展示了一堆选择,但没有插入或更新,我不明白为什么。有人有任何想法吗?

这里是我的模型:

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 "https://stackoverflow.com/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('[email protected]') 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('[email protected]') 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) 

希望有人有一个洞察力的原因,我一直在过去两天找遍了整个互联网没有运气。提前致谢!

编辑1

我把accepts_nested_attributes_for线后关联作了,通过ovatsug25的建议,但它似乎并没有做任何改变。不过,我记得在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'。这个查询没有意义,根本不应该发生!

EDIT 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('[email protected]') 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_tokenupdated_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('[email protected]') 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次吗?似乎毫无意义和浪费。

+0

对于初学者,您的'primary_key'选项位于关联的错误一侧。您需要在父项上指定 - 请参阅[此处](http://guides.rubyonrails.org/association_basics.html#options-for-has-many-primary-key)以供参考。另外,由于您不再希望在'Speaks'模型中使用'language_id'作为外键,您应该设置一个新字段以更好地反映与您的两个模型相关联的字段 - ISO。注意你还需要在你的'belongs_to'中指定一个'foreign_key'约束来使它工作,并且使新的字段'attr_accessible'也一样。 – Noz

+0

感谢您的快速反馈!但是我已经有了一个新字段,它是'iso_639_1_code',它是'attr_accessible'。至于'primary_key',我不知道我明白了。这是一个多对多的关系,但我想你是指用户,因为这是父表单。我对吗?在任何情况下,我都指定'primary_key'在'User'模型('iso_639_1_code')中用于连接。此外,我不确定我的问题是否清楚,但协会在控制台中工作正常。只是表单不起作用。这就是让我对所有事情更加困惑的原因。 – Ariel

+0

SomeTo与WantsToLearn,介意发布该类的来源? –

回答

1

最大的线索是这样的这引起你的眼睛错误:

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 consolerails c)可以成为调试类似问题的有用工具。在这种情况下,在控制台上,您可以运行WantsToLearn.columns_hash['language_id'].type来查看Rails认为它应该用于该属性的类型。当然,您也可以轻松检查迁移。

0

我曾经有过这样的问题,并且在声明所有关联和可访问属性之后,通过将accepts_attributes_for调用分隔到最底部来解决它。 (我还合并attr_accesible到一个电话。我觉得ryanb说,在这部影片中对呼叫的顺序什么的。http://railscasts.com/episodes/196-nested-model-form-revised?view=asciicast

有道理?不可以,但它为我工作。

+0

对我来说真的没有把戏= / – Ariel