更新嵌套表单时,如何处理新记录?
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 callback 到 Photo
。但是正如您已经推测的那样,从模型通过会话访问用户并不是一个好的方法。
相反,您可以在实例化时将 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
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 callback 到 Photo
。但是正如您已经推测的那样,从模型通过会话访问用户并不是一个好的方法。
相反,您可以在实例化时将 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