在 Rails 表单中使用关联表中的字段



我正在使用Rails 5.0。 我已经设置了一个应用程序,每个profile都有许多locations。 我在获取要实际保存到数据库的位置时遇到问题。 每个用户都应该能够将多个邮政编码保存到他们的个人资料中。 我已经将profilelocations相关联,zip_codeslocations表中的一个字段。 我的代码如下。

更多上下文 - 我希望每个用户/配置文件都与许多邮政编码相关联。 目标是允许用户根据邮政编码搜索配置文件。

更新的代码我已经按照此处的建议更新了我的代码 - 如何使用单个表单在 ActiveRecord 中保存嵌套资源(Ruby on Rails 5)

更新Profile.rb模型以包含accepts_nested_attributes_for :locations后,我仍然遇到问题,但我仍然看到Unpermitted paramter: location,如下面的控制台日志所示。

模式.rb

create_table "locations", force: :cascade do |t|
t.integer "user_id"
t.string  "zip_codes"
t.integer "profile_id"
t.index ["profile_id"], name: "index_locations_on_profile_id"
t.index ["user_id"], name: "index_locations_on_user_id"
end
create_table "profiles", force: :cascade do |t|
t.integer  "user_id"
t.string   "first_name"
t.string   "last_name"
t.string   "services_offered"
t.string   "phone_number"
t.string   "contact_email"
t.string   "description"
t.datetime "created_at",          null: false
t.datetime "updated_at",          null: false
t.string   "avatar_file_name"
t.string   "avatar_content_type"
t.integer  "avatar_file_size"
t.datetime "avatar_updated_at"
t.string   "business_name"
t.string   "short_term"
t.string   "long_term"
end
create_table "users", force: :cascade do |t|
t.string   "email",                  default: "", null: false
t.string   "encrypted_password",     default: "", null: false
t.string   "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer  "sign_in_count",          default: 0,  null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string   "current_sign_in_ip"
t.string   "last_sign_in_ip"
t.datetime "created_at",                          null: false
t.datetime "updated_at",                          null: false
t.integer  "plan_id"
t.string   "stripe_customer_token"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end

用户.rb

class User < ApplicationRecord

belongs_to :plan
has_one :profile 
has_many :locations, through: :profile
accepts_nested_attributes_for :locations

profile.rb

class Profile < ActiveRecord::Base
belongs_to :user
has_many :locations, inverse_of: :profile
accepts_nested_attributes_for :locations
end

位置.rb

class Location < ActiveRecord::Base
belongs_to :user, optional: true
belongs_to :profile, optional: true
end

profiles_controller.rb

class ProfilesController < ApplicationController
#POST to /users/:user_id/profile
def create
# Ensure we have the user who is filling out the form
@user = User.find( params[:user_id] ) 
# Create profile linked to this specific user
@profile = @user.build_profile( profile_params )
if @profile.save
flash[:success] = "Profile saved!"
redirect_to user_path( params[:user_id] )
else
render action: :new
end
end
#PUT to /users/:user_id/profile
def update
# Retrieve user from the database
@user = User.find( params[:user_id] )
# Retrieve that user's profile
@profile = @user.profile
# Mass assign edited profile attributes and save (update)
if @profile.update_attributes(profile_params)
flash[:success] = "Profile updated!"
# Redirect user to profile page
redirect_to user_path(id: params[:user_id])
else
render action: :edit
end
end
private
def profile_params
params.require(:profile).permit(:first_name, :last_name, :avatar, :job_title, :phone_number, :contact_email, :description, :services_offered, :long_term, :short_term, locations_attributes: [:id, :zip_codes])
end
end

这是我尝试插入zip_codes字段并允许用户将许多内容保存到其个人资料中的视图。 显示该字段,我可以成功提交,但是当我返回编辑它时,邮政编码不会显示。我也不确定他们是否在保存。

_form.html.erb

<%= form_for @profile, url: user_profile_path, :html => { :multipart => true } do |f| %>
<div class="form-group">
<%= f.label :first_name %>
<%= f.text_field :first_name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :last_name %>
<%= f.text_field :last_name, class: 'form-control' %>
</div>
</div>
<div class="form-group">
<%= f.fields_for :locations do |f| %>
<%= f.label :zip_codes, "Zip codes served" %></br>
<%= f.text_field :zip_codes, 'data-role'=>'tagsinput' %>
<% end %>
</div>
<% end %>

更新* 下面是控制台补丁请求。 我编辑了其他一些字段,因此很明显没有运行任何内容来更新zip_codes字段。

Started PATCH "/users/1/profile" for 24.9.150.43 at 2020-11-23 23:35:53 +0000
Cannot render console from 24.9.150.43! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by ProfilesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"QPHsRWYDY5hxHr4zEL/XW8h2qzDqvhqIPvU/mBlc4exOyqHSxD9A9vSzShNF+afllkDFejqsSP0DiVwGa/9jsQ==", "profile"=>{"first_name"=>"Person", "last_name"=>"Test", "long_term"=>"true", "short_term"=>"true", "location"=>{"zip_codes"=>"80212"}, "phone_number"=>"507-222-2222", "contact_email"=>"kyle@example.com", "description"=>"Test account with test description."}, "commit"=>"Update Profile", "user_id"=>"1"}
User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
Profile Load (0.1ms)  SELECT  "profiles".* FROM "profiles" WHERE "profiles"."user_id" = ? LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
Unpermitted parameter: location
(0.1ms)  begin transaction
SQL (7.2ms)  UPDATE "profiles" SET "first_name" = ?, "short_term" = ?, "updated_at" = ? WHERE "profiles"."id" = ?  [["first_name", "Person"], ["short_term", "true"], ["updated_at", 2020-11-23 23:35:53 UTC], ["id", 1]]
(7.8ms)  commit transaction
Redirected to https://a434446a3d264614901296378175dad9.vfs.cloud9.us-west-2.amazonaws.com/users/1
Completed 302 Found in 28ms (ActiveRecord: 15.3ms)

好的,由于您希望能够通过邮政编码搜索配置文件,因此您需要将邮政编码存储在位置表中。但是,如果您有 100 个用户将 98057 列为他们的邮政编码,那么您不需要 100 个唯一位置。您需要一个包含 zip 98057 的位置,以及一个条目为 profile_id 和 location_id 的连接表。

因此,配置文件模型如下所示:

class Profile < ActiveRecord::Base
belongs_to: :user
has_many: :profile_locations inverse_of: :profile
has_many: locations through: profile_locations
accepts_nested_attributes_for: :profile_location
end

和位置模型:

class Location < ActiveRecord::Base
has_many: profile_locations
has_many: profiles through: profile_locations
end

添加配置文件位置模型:

class ProfileLocation < ActiveRecord::Base
belongs_to: :profile
belongs_to: :location
validates_presence_of :profile
validates_presence_of :location
accepts_nested_attributes_for :location
end

位置表应为:

create_table "locations", force: :cascade do |t|
t.string  "zip_code"  #note SINGULAR
end

profile_locations表将是:

create_table :profile_locations do |t|
t.belongs_to :location
t.belongs_to :profile
t.timestamps
end

这是容易的部分。现在,您必须使用表单将邮政编码传入和传出数据库:

<div class="form-group">
<%= f.label :zip_codes, "Zip codes served" %></br>
<%= f.text_field :zip_codes, value: @profile.locations.pluck(:zip_code).join(' ,'),'data-role'=>'tagsinput' %>
</div>

我们使用value: @profile.locations.pluck(:zip_code).join(' ,')在文本字段中使用当前邮政编码填写表单的edit版本。当它处于new形式时,它将为零。

所以你会得到一个参数

profile: {zip_codes: '99019, 98057, 06850'}

您需要将它们转换为数组,因此将方法添加到配置文件控制器:

class ProfilesController < ApplicationController
before_action: :zip_array only:[:create, :update]
....

然后创建一个私有方法,例如:

def zip_array
profile_params[:zip_codes] = profile_params[:zip_codes].scan(/d{5}/)
#replaces the string '99019, 98057, 06850' with the array ['99019', '98057', '06850']
end

createupdate方法中,要构建关联的邮政编码条目。但是update您想摆脱任何已经存在的更新,以防该人删除某些邮政编码并添加其他邮政编码。

因此,在"创建"操作中:

def create
# Ensure we have the user who is filling out the form
@user = User.find( params[:user_id] ) 
# Create profile linked to this specific user
@profile = @user.build_profile( profile_params )
profile_params[:zip_codes].each do |zip|
@profile.locations.build(zip_code: zip)
end
if @profile.save
flash[:success] = "Profile saved!"
redirect_to user_path( params[:user_id] )
else
render action: :new
end
end

在"更新"操作中:

def update
# Retrieve user from the database
@user = User.find( params[:user_id] )
@profile = user.profile
# we wrap this in a rescue block in case the transaction fails
begin
Profile.transaction do 
@profile.profile_locations.destroy_all  #get rid of all of them
profile_params[:zip_codes].each do |zip|
@profile.locations.build(zip_code: zip) #build the new locations
end
@profile.update_attributes(profile_params) #saving the profile saves the locations we built
flash[:success] = "Profile updated!"
redirect_to user_path(id: params[:user_id])
end  
rescue  #if the transaction fails it will roll back the destroy_all
render action: :edit
end
end

相关内容

最新更新