使用连接表元数据创建Rails 4表单

非常新的Rails 4开发人员。 我有一个用户正在创建练习的表单。 练习可以有很多设备,而设备可以是可选的(想想俯卧撑是做俯卧撑)。 我将此“可选”字段存储在连接表exercise_equipment中。

我无法获取参数来实际发送我选择的集合元素的值。 请参阅下面的模型,视图,控制器和参数。

以下是我的模型的属性/关系:

# id :integer # name :string # is_public :boolean Exercise has_many :exercise_equipment has_many :equipment, :through => :exercise_equipment accepts_nested_attributes_for :exercise_equipment # id :integer # exercise_id :integer # equipment_id :integer # optional :boolean ExerciseEquipment belongs_to :exercise belongs_to :equipment accepts_nested_attributes_for :equipment # id :integer # name :string Equipment has_many :exercise_equipment has_many :exercises, :through => :exercise_equipment 

以下是一些(可能)相关的控制器方法:

 def new @exercise = Exercise.new @exercise.exercise_equipment.build end def create @exercise = Exercise.new( exercise_params ) if @exercise.save redirect_to @exercises else render 'new' end end def edit @exercise = Exercise.find( params[:id] ) end def update @exercise = Exercise.find( params[:id] ) if @exercise.update_attributes( exercise_params ) redirect_to @exercises else render 'edit' end end def exercise_params params.require( :exercise ).permit( :name, :is_public, exercise_equipment_attributes: [ :id, :optional, equipment_attributes: [ :id, :name ], ] ) end 

这是我创建一个视图来做我想要的事情:

演习/ new.html.erb

     

演习/ _form.html.erb

 
Public <%= f.fields_for( :exercise_equipment ) do |eef| Optional

当我将所有这些放在一起并向已经存在的练习提交更新时,这些值都会通过params哈希值,但不会更改为我选择的新值…

 Parameters: { "utf8"=>"[checkbox]", "authenticity_token"=>"[token]", "exercise"=>{ "name"=>"Test", "is_public"=>"1", "exercise_equipment_attributes"=>{ "0"=>{ "equipment_attributes"=>{ "id"=>"1" }, "optional"=>"1", "id"=>"2" } } }, "commit"=>"Save Exercise", "id"=>"1" } 

如果你可以帮助我,我会非常感激。 如果您需要更多信息,请告诉我,我可以提供。

编辑

以下是更新前数据库的状态:

 postgres@=>db=# select id, name, is_public from exercises; id | name | is_public ----+------+----------- 2 | Test | t (1 row) Time: 61.279 ms postgres@=>db=# select id, exercise_id, equipment_id, optional from exercise_equipment; id | exercise_id | equipment_id | optional ----+-------------+--------------+---------- 2 | 2 | 1 | t (1 row) Time: 58.819 ms postgres@=>db=# select id, name from equipment where id = 1; id | name ----+------------- 1 | Freeweights (1 row) 

然后我转到该练习的更新路线,从集合中选择不同的设备,然后提交表单。 我得到以下Rails控制台结果:

 Started PATCH "/exercises/system-test" for 127.0.0.1 at 2014-08-12 23:48:18 -0400 Processing by ExercisesController#update as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"PsbbUPSCiIew2Fd22Swn+K4PmLjwNDCrDdwXf9YBcm8=", "exercise"=>{"name"=>"Test", "is_public"=>"1", "exercise_equipment_attributes"=>{"0"=>{"equipment_attributes"=>{"id"=>"1"}, "optional"=>"1", "id"=>"2"}}}, "commit"=>"Save Exercise", "id"=>"system-test"} Exercise Load (60.5ms) SELECT "exercises".* FROM "exercises" WHERE "exercises"."slug" = 'system-test' ORDER BY "exercises"."id" ASC LIMIT 1 (57.3ms) BEGIN ExerciseEquipment Load (76.2ms) SELECT "exercise_equipment".* FROM "exercise_equipment" WHERE "exercise_equipment"."exercise_id" = $1 AND "exercise_equipment"."id" IN (2) [["exercise_id", 2]] Equipment Load (59.1ms) SELECT "equipment".* FROM "equipment" WHERE "equipment"."id" = $1 LIMIT 1 [["id", 1]] User Load (60.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 10]] Exercise Exists (60.5ms) SELECT 1 AS one FROM "exercises" WHERE ("exercises"."name" = 'Test' AND "exercises"."id" != 2 AND "exercises"."user_id" = 10) LIMIT 1 (64.8ms) COMMIT Redirected to http://localhost:3000/exercises/system-test Completed 302 Found in 590ms (ActiveRecord: 580.0ms) Started GET "/exercises/system-test" for 127.0.0.1 at 2014-08-12 23:48:19 -0400 Processing by ExercisesController#show as HTML Parameters: {"id"=>"system-test"} Exercise Load (64.1ms) SELECT "exercises".* FROM "exercises" WHERE "exercises"."slug" = 'system-test' ORDER BY "exercises"."id" ASC LIMIT 1 Equipment Load (58.7ms) SELECT "equipment".* FROM "equipment" INNER JOIN "exercise_equipment" ON "equipment"."id" = "exercise_equipment"."equipment_id" WHERE "exercise_equipment"."exercise_id" = $1 [["exercise_id", 2]] Rendered exercises/show.html.erb within layouts/application (122.7ms) User Load (60.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 10 ORDER BY "users"."id" ASC LIMIT 1 Rendered shared/_header.html.erb (61.9ms) Rendered shared/_alerts.html.erb (0.1ms) Completed 200 OK in 264ms (Views: 21.3ms | ActiveRecord: 240.8ms) 

首先,您需要确保正确定义关联。

任何has_many关联都应该用复数名称定义 –

 #app/models/exercise.rb Class Exercise < ActiveRecord::Base has_many :exercise_equipments has_many :equipments, :through => :exercise_equipments accepts_nested_attributes_for :exercise_equipments end #app/models/exercise_equipment.rb Class ExerciseEquipment < ActiveRecord::Base belongs_to :exercise belongs_to :equipment end #app/models/equipment.rb Class Equipment < ActiveRecord::Base has_many :exercise_equipments has_many :exercises, through: :exercise_equipments end 

如果您已经使用它并且对您所拥有的内容感到满意,那么我建议您保持当前的设置。 但是,为了惯例 ,您可能希望采用上述方法

编辑我从删除的答案中看到Beartech进行了调查,结果表明Rails将设备/设备视为一样。 值得忽视以上内容,但我会留待以后参考


PARAMS

我无法获取参数来实际发送我选择的集合元素的值。 请参阅下面的模型,视图,控制器和参数。

我想我明白了你的意思 - 你正在寻找更新记录,但它没有通过更新的参数发送到你的控制器,因此阻止它被更新。

虽然我看不出任何明显的问题,但我建议问题是你正在尝试填充一个Exercise对象的exercise_id 。 您需要为exercise_equipment对象定义它:

 <%= f.fields_for :exercise_equipment do |eef| %> <%= eef.collection_select :equipment_id, Equipment.all, :id, :name %> <%= eef.check_box :is_optional %> <% end %> 

这将填充您的exercise_equipment表,如下所述:

 Time: 61.279 ms postgres@=>db=# select id, exercise_id, equipment_id, optional from exercise_equipment; id | exercise_id | equipment_id | optional ----+-------------+--------------+---------- 2 | 2 | 1 | t (1 row) 

目前,您正在使用equipment_id填充Equipment模型 - 这将无法正常工作。 以这种方式填充模型将服务器创建新记录,而不是更新已创建的记录


额外的领域

我希望有一个链接可以在点击时添加一个额外的设备字段,类似于Ryan Bates在这个RailsCast中的做法,但是他写的帮助方法(如果你没有订阅,请参阅“显示注释”选项卡)在处理下面代码中显示的嵌套视图时,源代码似乎变得更加复杂。 处理这个有什么帮助吗?

这是一个难以克服的山

Ryan在这个过程中使用了一个过时的方法(预先填充链接,然后让JS追加该字段)。 “正确”的方法是构建一个新对象并从ajax中追加fields_for 。 听起来很难? 那是因为它是:)

这是你如何做到的:

 #config/routes.rb resources :exercises do collection do get :ajax_update #-> domain.com/exercises/ajax_update end end #app/models/exercise.rb Class Exercise < ActiveRecord::Base def self.build exercise = self.new exercise.exercise_equipment.build end end #app/controllers/exercises_controller.rb Class ExercisesController < ApplicationController def new @exercise = Exercise.build end def ajax_update @exercise = Exercise.build render "add_exercise", layout: false #> renders form with fields_for end end #app/views/exercises/add_exercise.html.erb <%= form_for @exercise do |f| %> <%= render partial: "fields_for", locals: { form: f } %> <% end %> #app/views/exercises/_fields_for.html.erb <%= f.fields_for :exercise_equipment, child_index: Time.now.to_i do |eef| %> <%= eef.collection_select :equipment_id, Equipment.all, :id, :name %> <%= eef.check_box :is_optional %> <% end %> #app/views/exercises/edit.html.erb <%= form_for @exercise do |f| %> <%= render partial: "fields_for", locals: { form: f } %> <%= link_to "Add Field", "#", id: "add_field" %> <% end %> #app/assets/javascripts/application.js $(document).on("click", "#add_field", function() { $.ajax({ url: "exercises/ajax_update", success: function(data) { el_to_add = $(data).html() $('#your_id').append(el_to_add) } }); });