Rails has_many:通过关联:将实例保存到连接表

时间:2015-07-02 00:13:12

标签: ruby-on-rails ruby-on-rails-4 model-view-controller orm relational-database

在我们的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.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

我们已按如下方式创建calendar#create操作:

def create
    @calendar = current_user.calendars.build(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 "Post", class: "btn btn-primary" %>
<% end %>

这个“有效”,因为当我们通过表单创建新日历时,当我们输入Calendar.all时,它确实会显示在Rails控制台中。

但是,似乎没有创建新的@administration并且管理表未更新,因为当我们在控制台中键入Administration.all时没有任何内容返回。

我们认为管理表是User表和Calendar表之间的连接表,分别包含user_idcalendar_id列,在创建新日历时会自动更新。

我们怎样才能做到这一点?我们是否需要创建特定的administration#create操作?

更新:根据评论和答案,我们实施了以下CalendarsController

class CalendarsController < ApplicationController

  def create
    @calendar = current_user.calendars.build(calendar_params)
    if @calendar.save
      current_user.administrations << @calendar
      @calendar.administration.role = 'creator'
      flash[:success] = "Calendar created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end

...

但是,这会返回以下错误:

ActiveRecord::AssociationTypeMismatch in CalendarsController#create

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:6:in `create'

我们错过了什么吗?

2 个答案:

答案 0 :(得分:3)

如果你使用current_user.calendars.build(calendar_params),你将只获得新的日历,而不是管理。

如果您使用current_user.calendars.create(calendar_params),您将获得管理和日历保存到数据库中。

如果您想先检查日历是否成功保存,我会使用这种方法:

def create
  @calendar = Calendar.build(calendar_params)
  if @calendar.save
    current_user.administrations << @calendar
    flash[:success] = "Calendar created!"
    redirect_to root_url
  else
    render 'static_pages/home'
  end
end

更新:

将日历与用户关联时出错。这应该是正确的:

def create
  @calendar = current_user.calendars.build(calendar_params)
  if @calendar.save
    current_user.calendars << @calendar
    flash[:success] = "Calendar created!"
    redirect_to root_url
  else
    render 'static_pages/home'
  end
end

答案 1 :(得分:2)

是的,你绝对这样做!您没有收到新的Administration对象,因为您并没有要求系统提供一个。

如果你确实真的需要在每次创建Administration时创建一个新的Calendar对象,你可以这样做:

class Calendar < ActiveRecord::Base
  attr_reader :administration

  def initialize(params)
    @administration = Administration.new(params[:id])
  end
end

class Administration < ActiveRecord::Base
  def initialize(id_param_from_calendar)
    calendar_id = id_param_from_calendar
  end
end

显然,这不是最佳做法,因为它会在代码中注入依赖项。现在,无论如何,您的Calendar类都知道Administration类不仅存在,而且还使用其id列进行初始化。这只是回答你问题的一个例子。

另一个示例是在after_initialize类中包含Calendar挂钩(请注意autosave: true,这对确保自动保存子对象很有用):

class Calendar < ActiveRecord::Base
  has_many :administrations, autosave: true, dependent: :destroy

  after_initialize :init_admins

  def init_admins
    # you only wish to add admins on the newly instantiated Calendar instances
    if new_record?
      3.times { Administration.new self }
    end
  end

end

除此之外,您还可以在after_create挂钩中包含上述逻辑。唯一的区别是在创建对象后发生after_create,在实例化或从数据库加载对象后发生after_initialize