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)创建日历时,我们应该:

为了做到这一点,我们认为我们需要处理日历#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')

同样,您可以进一步重构您的控制器代码。