创建带联接表元数据的Rails 4表单 [英] Creating Rails 4 Form with Join Table Metadata

查看:133
本文介绍了创建带联接表元数据的Rails 4表单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这里有非常新的Rails 4开发人员。我有一个用户正在创建练习的表单。练习可以有许多设备,而设备可以是可选的(认为俯卧撑代表做俯卧撑)。我在连接表exercise_equipment上存储了这个可选字段。



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



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

 #id:integer 
#name:string
#is_public:boolean
练习
has_many:exercise_equipment
has_many:设备,:through => :exercise_equipment
accep_nested_attributes_for:exercise_equipment


#id:整数
#exercise_id:整数
#equipment_id:整数
#可选:布尔
ExerciseEquipment
belongs_to:练习
belongs_to:设备
accep_nested_attributes_for:设备


#id:integer
#name:string
设备
has_many:exercise_equipment
has_many:练习,:through => :exercise_equipment

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

  def new 
@exercise = Exercise.new
@ exercise.exercise_equipment.build
end

def创建
@exercise = Exercise.new(exercise_params)
if @ exercise.save
redirect_to @exercises
else
render'new'
end
结束

def编辑
@exercise = Exercise.find(params [:id])
结束

def更新
@exercise = Exercise.find(params [:id])
if @ exercise.update_attributes(exercise_params)
redirect_to @exercises
else
render'edit'
end
结束

def exercise_params
params.require(:exercise).permit(
:name,
:is_public,
exercise_equipment_attributes:[
:id,
:可选,
equipment_attributes:[
:id,
:名称
],
]

end

这是我创建一个视图来执行我想要的操作:



exercise / new.html.erb

 <%= form_for @exercise do | f | %GT; 
<%=呈现'表格',f:f%>
<%= f.submit新练习%>
<%end%>

练习/ _form.html.erb $ b

 <%= f.label:name%>< br /> 
<%= f.text_field:name%>

<%= f.check_box:is_public%>公共

<%= f.fields_for(:exercise_equipment)do | eef |
<%= eef.fields_for(:equipment)do | ef |
ef.collection_select:id,Equipment.all,:id,:name%>
<%end%>
<%= eef.check_box:is_optional%>可选
<%end%>

当我把所有这些放在一起并提交一个已经存在练习的更新时,通过params散列,但不会更改为我选择的新值...

 参数:{
utf8=>[checkbox],
authenticity_token=>[token],
exercise=> {
name
is_public=>1,
exercise_equipment_attributes=> {
0=> {
equipment_attributes= > {
id=>1
},
可选=>1,
id=>2
}
}
},
commit=>保存练习,
id=>1
}

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

b

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

  postgres @ => db =#select id,name ,从练习中获得公共利益; 
id |名称| is_public
---- + ------ + -----------
2 |测试| t
(1 row)

时间:61.279 ms
postgres @ => db =#select id,exercise_id,equipment_id,可选来自exercise_equipment;
id | exercise_id | equipment_id |可选
---- + ------------- + -------------- + ----------
2 | 2 | 1 | t
(1 row)

时间:58.819 ms
postgres @ => db =#选择id,id = 1的设备名称;
id |名称
---- + -------------
1 | Freeweights
(1 row)

然后我进入该练习的更新路线,select与收集品不同的设备,并提交表格。我得到以下Rails控制台结果:

 在2014-08-08对127.0.0.1启动了PATCH/ Exercises / system-test -12 23:48:18 -0400 
通过ExercisesController处理#更新为HTML
参数:{utf8=>✓,authenticity_token=>PsbbUPSCiIw2Fd22Swn + K4PmLjwNDCrDdwXf9YBcm8 =, exercise=> {name=>Test,is_public=>1,exercise_equipment_attributes=> {0=> {equipment_attributes=> {id =>1},可选=>1,id=>2}}},commit=>Save Exercise,id=>系统测试}
练习加载(60.5ms)SELECTexercise。* FROMExercisesWHEREExercises。slug='system-test'ORDER BYexercises。idASC LIMIT 1
(57.3ms)BEGIN
ExerciseEquipment Load(76.2ms)SELECTexercise_equipment。* FROMexercise_equipmentWHEREexercise_equipment。exercise_id= $ 1 ANDexercise_equipment。idIN 2)[[exercise_id,2]]
设备负载(59.1ms)选择设备。*从设备entWHEREequipment。id= $ 1 LIMIT 1 [[id,1]]
用户载入(60.3ms)SELECTusers。* FROMusersWHEREusers。id = $ 1 LIMIT 1 [[id,10]]
练习存在(60.5ms)SELECT 1 AS one FROMexerciseWHERE(exercise。name='Test'ANDexercises。 id!= 2 ANDexercises。user_id= 10)限制1
(64.8ms)COMMIT
重定向到http:// localhost:3000 / Exercises / system-test
完成302发现于590ms(ActiveRecord:580.0ms)


在2014-08-12 23:48:19开始GET/ exercises / system-test为127.0.0.1 - 0400
通过ExercisesController处理#show as HTML
参数:{id=>system-test}
练习加载(64.1ms)选择Exercises。 WHERE练习。slug='system-test'ORDER BYexercise。idASC LIMIT 1
设备负载(58.7ms)SELECTequipment。* FROMequipmentINNER JOIN exercise_equipmentONequipment。id=exercise_equipment。equipment_idWHEREexerci (b)使用者负载(60.1ms)用户负载(60.1ms)用户负载(60.1ms)用户负载(60.1ms)用户负载。* FROMusersWHEREusers。id= 10 ORDER BYusers。idASC LIMIT 1
渲染共享/ _header.html.erb(61.9ms)
渲染共享/ _alerts.html.erb(0.1ms)
在264ms内完成200行(查看:21.3ms | ActiveRecord:240.8ms)


解决方案

首先,您需要确定你正确地定义你的关联。


$ b 任何 has_many 关联都应该使用复数名称来定义 -

 #app / models / exercise.rb 
课堂练习< ActiveRecord :: Base
has_many:exercise_equipments
has_many:equipments,:through => :exercise_equipments

accep_nested_attributes_for:exercise_equipments
end

#app / models / exercise_equipment.rb
课程练习设备< ActiveRecord :: Base
belongs_to:练习
belongs_to:设备
结束

#app / models / equipment.rb
Class Equipment< ActiveRecord :: Base
has_many:exercise_equipments
has_many:通过以下练习::exercise_equipments
end

如果您已经开始使用它,并对所获得的结果感到满意,那么我建议您保留当前的设置。不过,您可能希望采用上述内容来满足公约的要求。



Edit 我从已删除的答案中看到, Beartech 调查了这一点,结果Rails将Equipment / Equipments视为相同。将值得忽略以上内容,但我会留下以供将来参考






Params


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

我想我明白你的意思 - 重新寻找更新记录,但它不会通过更新的参数发送到您的控制器,从而防止更新。



虽然我看不到任何眩光问题,我会建议的问题是,您正试图填充练习对象的 exercise_id 。您需要为 exercise_equipment 对象定义它:

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

这将填充您的 exercise_equipment 表格这里:

 时间:61.279 ms 
postgres @ => db =#select id,exercise_id,equipment_id,可选从exercise_equipment;
id | exercise_id | equipment_id |可选
---- + ------------- + -------------- + ----------
2 | 2 | 1 | t
(1 row)

目前,您正在填充设备模型与 equipment_id - 这是行不通的。以这种方式填充模型将服务器创建新记录,而不会更新已创建的记录。




Extra Field


我想要一个链接来添加额外的设备字段,当它是
被点击,类似于Ryan Bates在RailsCast中的做法,但他写的
帮助器方法(如果您没有订阅
来查看源代码,请参阅显示注释选项卡)似乎当
处理下面代码中显示的嵌套视图时,它变得更加复杂。
中的任何帮助处理这个问题?

这个棘手的山峰需要克服

Ryan在这个过程中使用了相当过时的方法(预先填充链接,然后让JS追加字段)。 正确的方法是建立一个新的目标和方法。从ajax追加 fields_for 。听起来很难?这是因为它是:)



以下是您的操作方法:

 #config / routes.rb 
resources:exercise do
collection do
get:ajax_update# - > domain.com/exercises/ajax_update
end
end

#app / models / exercise.rb
课堂练习< ActiveRecord :: Base
def self.build
exercise = self.new
exercise.exercise_equipment.build
end
end

#app /controllers/exercises_controller.rb
类ExercisesController< ApplicationController
def new
@exercise = Exercise.build
end
$ b $ def ajax_update
@exercise = Exercise.build
renderadd_exercise ,layout:false#>使用fields_for
结尾呈现表单
结束

#app / views / Exercises / add_exercise.html.erb
<%= form_for @exercise do | f | %GT;
<%=渲染部分:fields_for,本地人:{form:f}%>
<%end%>

#app / views / exercises / _fields_for.html.erb
<%= f.fields_for:exercise_equipment,child_index:Time.now.to_i do | eef | %GT;
<%= 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 | %GT;
<%=渲染部分:fields_for,本地人:{form:f}%>
<%= link_toAdd Field,#,id:add_field%>
<%end%>
$ b#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)
}
});
});


Very new Rails 4 developer here. I've got a form where a user is creating Exercises. Exercises can have many Equipment, and Equipment can be optional( think push-up stands for doing push-ups ). I store this "optional" field on the join table exercise_equipment.

I cannot get the parameters to actually send through the values of the collection element that I pick. See below for the model, view, controller, and parameters.

Here are the attributes/relationships of my models:

# 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

Here are some (maybe) relevant controller methods:

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

This is my shot at creating a view to do what I want:

exercises/new.html.erb

<%= form_for @exercise do |f| %>
  <%= render 'form', f: f %>
  <%= f.submit "New Exercise" %>
<% end %>

exercises/_form.html.erb

<%= f.label :name %><br />
<%= f.text_field :name %>

<%= f.check_box :is_public %> Public

<%= f.fields_for( :exercise_equipment ) do |eef|
  <%= eef.fields_for( :equipment ) do |ef|
    ef.collection_select :id, Equipment.all, :id, :name %>
  <% end %>
  <%= eef.check_box :is_optional %> Optional
<% end %>

When I put all of this together and submit an update to an already-existing exercise, the values all go through the params hash, but aren't changed to the new values I've selected...

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" 
}

If you can help me out, I'd be super appreciative. Just let me know if you need any more information and I can provide it.

EDIT

Here is the state of the database before updating:

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)

I then go to the update route for that exercise, select a different equipment from the collection, and submit the form. I get the following Rails Console results:

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)

解决方案

Firstly, you need to make sure you define your associations correctly.

Any has_many association should be defined with a plural name -

#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

If you've already got it working, and are happy with what you've got, then I'd recommend keeping your current setup. However, you may wish to adopt the above for convention's sake

Edit I see from the deleted answer that Beartech investigated this, and turns out Rails treats Equipment / Equipments as the same. Will be worth ignoring the above, but I'll leave it for future reference


Params

I cannot get the parameters to actually send through the values of the collection element that I pick. See below for the model, view, controller, and parameters.

I think I get what you mean - you're looking to update the record, but it does not send through the updated parameters to your controller, hence preventing it from being updated.

Although I can't see any glaring problems, I would recommend the issue is that you're trying to populate the exercise_id of an Exercise object. You need to define it for the exercise_equipment object:

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

This will populate your exercise_equipment table as described here:

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)

Currently, you're populating the Equipment model with equipment_id - which won't work. Populating the model in that way will server to create a new record, not update the ones already created


Extra Field

I want to have a link to add an additional equipment field when it is clicked, similar to how Ryan Bates did it in this RailsCast, but the helper method he writes( see "Show Notes" tab if you're not subscribed to see the source ) seems to become substantially more complex when dealing with the nested views shown in my code below. Any help in dealing with this?

This a trickier mountain to overcome

Ryan uses quite an outdated method in this process (to pre-populate the link and then just let JS append the field). The "right" way is to build a new object & append the fields_for from ajax. Sounds tough? That's because it is :)

Here's how you do it:

#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)
       }
    });
 });

这篇关于创建带联接表元数据的Rails 4表单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆