更新嵌套表单时,如何处理新记录?

When updating a nested form, how do I do something with new records?

class Photo < ActiveRecord::Base 
  has_many :item_photos
  has_many :items, through: :item_photos
end 

class Item < ActiveRecord::Base 
  has_many :item_photos
  has_many :photos, through: :item_photos
  accepts_nested_attributes_for :photos
end 

class ItemPhotos < ActiveRecord::Base
  belongs_to :photo
  belongs_to :item
end 

当我编辑或创建项目时,我还会上传或删除照片。但是,不止一个用户可以查看一个项目。这些用户应该只能查看自己的照片。

项目 #1 有三张照片。艾米可以使用一个。巴里可以使用两个。 Barry 加载 /items/1 并进行编辑。他删除了一张照片,忽略另一张照片,并添加了两张新照片。

class ItemsController < ApplicationController
  def update
    if @item.update(item_params)
      # Give Barry access to the Photo he just made. 
      @item.only_the_photos_barry_just_made.each do |p|
        current_user.add_role :viewer, p
      end 
    end 
  end 
end 

我不想用访问会话信息的方法(如 current_user)污染 models/photo.rb。有没有一种惯用的方法来获取这些记录?如果没有,是否有一种干净的方法来获取这些记录?

感谢您的帮助。

一个简单的解决方案是为照片添加一个 :creator 关系。

rails g migration AddCreatorToPhotos creator:references

class Photo < ActiveRecord::Base
  belongs_to :creator, class: User
  # ...
end

然后您只需在 Abilty class:

中添加一条规则
class Ability
  include CanCan::Ability
  # Define abilities for the passed in user here.
  # @see https://github.com/bryanrite/cancancan/wiki/Defining-Abilities
  def initialize(user = nil)
    user ||= User.new # guest user (not logged in)
    # ...
    can :read, Photo do |photo|
      photo.creator.id == user.id
    end
  end
end

然后您可以通过以下方式获取当前用户可以阅读的照片:

@photos = @item.photos.accessible_by(current_ability)

编辑:

如果您想通过角色授权,您只需更改授权规则中的条件:

class Ability
  include CanCan::Ability
  # Define abilities for the passed in user here.
  # @see https://github.com/bryanrite/cancancan/wiki/Defining-Abilities
  def initialize(user = nil)
    user ||= User.new # guest user (not logged in)
    # ...
    can :read, Photo do |photo|
      user.has_role? :viewer, photo
    end
  end
end

编辑 2:

创建角色的方法可以是 add a callbackPhoto。但是正如您已经推测的那样,从模型通过会话访问用户并不是一个好的方法。

相反,您可以在实例化时将 Photo 传递给用户。您可以设置 belongs_to :creator, class: User 关系或创建 虚拟属性 :

class Photo < ActiveRecord::Base
  attr_accessor: :creator_id
end

然后您可以通过隐藏字段传递用户(记得将其列入白名单!):

# GET /items/new
def new 
  @item = Item.new
  @item.photos.build(creator: current_user) # or creator_id: current_user.id
end

<%= fields_for(:photos) do %>
  <%= f.hidden_field :creator_id %>
<% end %>

那么,我们如何创建回调?

class Photo < ActiveRecord::Base
  attr_accessor: :creator_id

  after_commit :add_viewer_role_to_creator!, on: :create

  def add_viewer_role_to_creator!
    creator.add_role(:viewer, self)
    true # We must return true or we break the callback chain
  end
end

不过有一个问题:

我们不想让恶意用户在更新时将他们的 ID 分配给现有照片。

我们可以通过设置一些自定义参数白名单来做到这一点:

def item_params
  whitelist = params.require(:item).permit(:foo, photos_attributes: [:a,:b, :creator_id]
  current_user_photos = whitelist[:photos_attributes].select do |attrs|
   attrs[:id].nil? && attrs[:creator_id] == current_user.id
  end
  whitelist.merge(photos_attributes: current_user_photos)
end