Rails has_many :through association: 同时更新所有 3 个模型
Rails has_many :through association: Updating all 3 models at the same time
这个问题是 的后续问题,为了更清楚起见,我在这里重申一下。
在我们的 Rails 应用程序中,有 3 个模型:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
这里是相应的迁移:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
这是我们正在努力实现的目标:
当登录用户(current_user
)创建日历时,我们应该:
- 创建新的
@calendar
并将其保存到日历 table
- 通过管理 table
中的角色列为这个新创建的日历分配 "Creator" 角色给用户 (current_user)
- 增加用户 table
的 total_calendar_count
和 owner_calendar_count
列
为了做到这一点,我们认为我们需要处理日历#create。
在CalendarsController中,我们已经有了如下代码:
def create
@calendar = current_user.calendars.create(calendar_params)
if @calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
我们通过以下 _calendar_form.html.erb
形式从用户那里收集数据:
<%= form_for(@calendar) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Your new calendar name" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
我们正在考虑如下更新控制器:
def create
@calendar = current_user.calendars.create(calendar_params)
@current_user.total_calendar_count += 1
@current_user.owned_calendar_count += 1
current_user.administrations << @calendar.id
@calendar.administration.role = 'Creator'
if @calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
ActiveRecord::AssociationTypeMismatch in CalendarsController#create
Administration(#70307724710480) expected, got Fixnum(#70307679752800)
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
app/controllers/calendars_controller.rb:7:in `create'
我们怎样才能让它发挥作用?
这一行实际上导致了错误:current_user.administrations << @calendar.id
。
current.administrations
需要一个 Administration
类型的对象,而您将 Fixnum
传递给它。
您可以通过以下方式实现相同的功能:
current_user.administrations.create(calendar_id: @calendar.id)
编辑:
正如 OP 在评论中所问的那样,这是否是一种好的做法。看,有条规则说 controller should be skinny,models should be fatty。 好吧,这意味着你应该尽量写最少的代码,所有的逻辑和对象的获取都应该出现在模型中。但在您的代码场景中情况并非如此。您应该将您的代码移动到模型中,然后将其调用到您的控制器中。
方法如下:
class User < ActiveRecord::Base
def add_calendar_and_role(calendar_id, role)
self.administrations.find_by(calendar_id: calendar_id).update(role: role)
end
end
这样,您的代码将减少为:
current_user.add_calendar_and_role(@calendar.id, 'Creator')
同样,您可以进一步重构您的控制器代码。
这个问题是
在我们的 Rails 应用程序中,有 3 个模型:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
这里是相应的迁移:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
这是我们正在努力实现的目标:
当登录用户(current_user
)创建日历时,我们应该:
- 创建新的
@calendar
并将其保存到日历 table - 通过管理 table 中的角色列为这个新创建的日历分配 "Creator" 角色给用户 (current_user)
- 增加用户 table 的
total_calendar_count
和 owner_calendar_count
列
为了做到这一点,我们认为我们需要处理日历#create。
在CalendarsController中,我们已经有了如下代码:
def create
@calendar = current_user.calendars.create(calendar_params)
if @calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
我们通过以下 _calendar_form.html.erb
形式从用户那里收集数据:
<%= form_for(@calendar) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Your new calendar name" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
我们正在考虑如下更新控制器:
def create
@calendar = current_user.calendars.create(calendar_params)
@current_user.total_calendar_count += 1
@current_user.owned_calendar_count += 1
current_user.administrations << @calendar.id
@calendar.administration.role = 'Creator'
if @calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
ActiveRecord::AssociationTypeMismatch in CalendarsController#create
Administration(#70307724710480) expected, got Fixnum(#70307679752800)
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
app/controllers/calendars_controller.rb:7:in `create'
我们怎样才能让它发挥作用?
这一行实际上导致了错误:current_user.administrations << @calendar.id
。
current.administrations
需要一个 Administration
类型的对象,而您将 Fixnum
传递给它。
您可以通过以下方式实现相同的功能:
current_user.administrations.create(calendar_id: @calendar.id)
编辑:
正如 OP 在评论中所问的那样,这是否是一种好的做法。看,有条规则说 controller should be skinny,models should be fatty。 好吧,这意味着你应该尽量写最少的代码,所有的逻辑和对象的获取都应该出现在模型中。但在您的代码场景中情况并非如此。您应该将您的代码移动到模型中,然后将其调用到您的控制器中。
方法如下:
class User < ActiveRecord::Base
def add_calendar_and_role(calendar_id, role)
self.administrations.find_by(calendar_id: calendar_id).update(role: role)
end
end
这样,您的代码将减少为:
current_user.add_calendar_and_role(@calendar.id, 'Creator')
同样,您可以进一步重构您的控制器代码。