创建带联接表元数据的Rails 4表单 [英] Creating Rails 4 Form with Join Table Metadata
问题描述
我无法获取参数来实际发送通过我选择的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屋!