163
:自引用关联(
查看原视频
)
查看英文原版
下面的页面来自一个简单的社交网络程序。用户可以注册,登陆,然后和其他用户进行交流。这个页面显示的是一个用户列表,通过每个用户旁边的链接你可以把他们加为好友。
不过这些链接现在还都不起作用。这一集,我们打算加一些代码让用户能够加其他用户为好友。为此,我们需要创建自引用关联:建立用户和用户之间的关系,这是建立在同一数据模型的两个实体之间的关系,而不是不同数据模型之间的关系。
生成正确的动作
用户页面中的“添加朋友”链接还没有指向任何其它地方。
<%= link_to "Add Friend" %>
在一些地方,我们需要把这个链接与某个控制器中的某个动作关联起来。那么,当它被点击的时候,它会调用哪个控制器的哪个动作呢,这个值得琢磨琢磨。我们其实已经有了一个
UsersController
控制器,所以在这里来处理朋友相关的东西看上去理所当然,因为朋友不就是其它一些用户吗。我们可以在
UsersController
中来添加和删除朋友。
def add_friend
end
def remove_friend
end
但是,考虑到各种因素,这种做法其实是很有问题的。当你创建像这样的控制器方法的时侯,在你脑海中要敲响的第一个警钟就是,这些方法不在七个标准的
RESTful
方法之内(索引,显示,新建,创建,编辑,更新和删除)。另外一个要注意的事情是这些方法都有一个
_friend
后缀。这就告诉我们应该把这些方法放到他们自己的命名空间中去。最后一个说明这不是个好方法的苗头是:
add
和
remove
表明我们其实是要创建和删除一个资源。所有这些,让我们想到我们应该创建一个新的控制器来处理朋友相关的东西。
创建朋友关系
我们新创建一个叫
FriendshipsController
的控制器。控制器的命名非常重要。我们也可以把它叫做
FriendsController
,但是在我们的程序中一个朋友其实也就是一个用户,我们要用这个新的控制器来创建和删除用户间的关系,而不是创建和删除用户。
一个用户可以有很多个朋友,同时也可以是很多其他人的朋友,
所以我们需要创建多对多的关系。在
Rails
中,我们有两种方法来定义这种关系:
has_and_belongs_to_many
和
has_many
:though
。(你可以看看
47
集的视频
,里面讲了很多这两种方法的内容)。目前来说,大家更多地是在用
has_many :though
,那么我们也将用它来定义我们的朋友关系。
为了使用
has_many
:though
,我们需要创建一个连接数据模型,我们把它命名为
Friendship
。这个模型有两个字段,
user_id
代表当前要加朋友的用户,
friend_id
代表被加为朋友的用户。
我们象往常一样来创建我们新的数据模型,
script/generate model Friendship user_id:integer friend_id:integer
然后运行数据迁移任务。
rake db:migrate
随着数据模型的创建,我们需要生成之前说的
FriendshipController
控制器。
script/generate controller Friendships
因为我们是把
Friendship
作为一种资源,所以我们需要在
/config/routes.rb
中加入下面一行代码。
map.resources :friendships
搞定了数据模型和控制器,我们可以来定义一下
Friendship
如何工作了。在我们的数据模型类中,让我们先来定义
User
和
Friendship
之间的关系。
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => 'User'
end
一个
Friendship
从属于一个用户,也就是添加朋友的一方。它同时还从属于一个
Friend
,也是一个用户,是被加做朋友的一方。对于
friend
关系,我们需要显式地指明它的类名,因为
ActiveRecord
没法通过关联中的名字来找出对应的数据模型。
同样,我们需要在
User
数据模型中定义另外一半数据关系。
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :through => :friendships
# rest of class omitted.
end
这里定义的关系和通常的多对多关系一样,我们不需要指定一个特定的名字,因为从关系名字中我们都可以推导出来。
接通“添加朋友”链接
定义好了数据模型间的关系,我们也有了
FriendshipsController
控制器,我们可以在用户的索引视图上给“添加朋友”链接做点什么了。
<% for user in @users %>
<div class="user">
<p>
<strong><%=h user.username %></strong>
<%= link_to "Add Friend", friendships_path(:friend_id => user), :method => :post %>
<div class="clear"></div>
</p>
</div>
<% end %>
这个链接需要调用
FriendshipsController
控制器中的
create
方法。为此我们让这个链接以
POST
方式指向
friendships_path
。此外,我们还得传入我们要加为朋友的用户,把它作为参数传入
friendships_path
当中。这样这个用户的
id
就会被传入
create
动作中,从而让这个动作知道我们要加哪个用户为朋友。
写好了链接的代码,我们继续在
FriendshipsController
控制器中来写我们的
create
动作。
def create
@friendship = current_user.friendships.build(:friend_id => params[:friend_id])
if @friendship.save
flash[:notice] = "Added friend."
redirect_to root_url
else
flash[:notice] = "Unable to add friend."
redirect_to root_url
end
end
通过当前用户,我们可以创建一个新的朋友关系,传入的参数是我们要加为朋友的用户
id
。使用
build
方法意味着新的
Friendship
中的
user_id
属性会被自动填上,再加上我们以参数传入的
friend_id
,这个朋友关系就完全建好了。保存好这个朋友关系,我们可以重定向回主页面,给用户显示一条提示信息说朋友已经添加了。如果因为某种原因,这个朋友关系不能保存,我们就要显示另外一个提示信息。当然,如果这是一个要上线的程序,我们需要告诉用户更多具体的信息让他们知道请求为什么失败了。
查看我们的朋友
一个登陆用户现在可以点击“添加朋友”链接来把另一个用户加为朋友了。但是他们还没法看到所有朋友的列表。让我们改一下用户的基本信息页面,使他们看到他们都把谁加为朋友了。基本信息页面也就是一个用户的显示页。当前,这个页面上只有用户的名字,和一个用来找到其他朋友的链接。
<% title "My Profile" %>
<p>Username: <%=h @user.username %></p>
<p><%= link_to "Find Friends", users_path %></p>
为了显示朋友列表,我们可以循环遍历用户的朋友集合来显示每一个朋友。
<h2>Your Friends</h2>
<ul>
<% for user in @user.friends %>
<li><%= h. user.username %></li>
<% end %>
</ul>
现在,在基本信息页面上可以看到我们的朋友列表了。
删除朋友
如果我们能在朋友列表的每个朋友旁加一个链接,让我们能把一个用户从我们的朋友中踢出去,会是一个很有用的功能。实现这个功能,我们要加一个链接来调用
FriendshipController
控制器的
destroy
动作,传入的参数是朋友关系的
id
。这里让人稍稍有点晕,我们是在循环遍历所有的用户,没法获得
Friendship
数据模型,那我们怎么拿到朋友关系的
id
呢?对于
Rails
菜鸟来说,他们在使用多对多关系的时候,经常会遇到这种问题,他们可能忘了,其实他们可以直接访问那个连接数据模型,而不是直接从用户就跳到朋友那里去。
现在,我们循环遍历用户的朋友关系,而不是他的朋友来创建这个列表。视图中的代码修改后如下所示:
<h2>Your Friends</h2>
<ul>
<% for friendship in @user.friendships %>
<li>
<%= h friendship.friend.username %>
(<%= link_to "remove", friendship, :method => :delete %>)
</li>
<% end %>
</ul>
现在我们通过
friendship.friend.username
来得到每个朋友的名字,然后传入朋友关系作为参数来创建这个删除链接,我们还要声明使用
DELETE
方法,让它去调用
destroy
动作。
说到
destroy
动作,让我们现在就来在
FriendshipsController
控制器中实现它。
def destroy
@friendship = current_user.friendships.find(params[:id])
@friendship.destroy
flash[:notice] = "Removed friendship."
redirect_to current_user
end
注意,这个动作的第一行代码中我们只是在当前用户的朋友列表中进行查找。如果我们调用
Friendship.find(params[:id])
,那么一个恶意用户就有可能删除任何两个其他用户间的朋友关系,用户应该被限制只能删除它们自己创建的朋友关系。剩下的部分,这个朋友关系被删除,然后页面重定向到用户的基本信息页。
基本信息中现在可以看到这些链接,我们可以点每个朋友旁边的链接来删除和他们的朋友关系。
反向关系
当创建自引用关系的时候,需要特别记住的是我们仅仅创建的一个单向的关系。虽然在上面的页面中
paul
被列为
eifion
的朋友,如果我们打开
paul
的基本信息页面,我们看不到
eifion
在他的朋友列表中,除非
paul
也加他为朋友。
创建一个双向的朋友关系,我们需要有两条
Friendship
记录。
结束这一集之前,我们还要给基本信息页加一个列表,让用户可以看到都有谁把他们加为了朋友,这样我们也可以从另一方向来看朋友关系。为此,我们需要在
User
数据模型中新加两个关系。
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :users
#rest of class omitted.
end
不容易想到一个合适的名字来命名反向的朋友关系,所以我们就加上“
inverse
”前缀来定义
inverse_friendships
和
inverse_friends
。我们还要加上其他一些选项来实现这些关系。对于
inverse_friendships
,我们还需要指明数据模型的名字,因为没法从关系名中推导出来。另外我们还需要定义
friend_id
作为外键。对于
inverse_friends
关系,我们需要指明
users
作为
source
,因为这个也不能从关系名字中推导出来。
回到基本信息页,我们想显示把我们加为好友的用户的列表。为此我们只需要循环遍历我们的
inverse_friends
,然后显示那些用户的名字。
<h2>Users Who Have Befriended You</h2>
<ul>
<% for user in @user.inverse_friends %>
<li><%= h user.username %></li>
<% end %>
</ul>
如果我们以用户
paul
登陆,并且添加
eifion
做为他的一个朋友,然后再以
eifion
登陆,我们就能看到列表中
paul
添加了
eifion
作为朋友了。
本集就这么多内容。我们还没有重造出一个
Facebook
,但是希望你能够对于如何在
Ruby on Rails
中使用自引用的关系有所体会。
- 大小: 42 KB
- 大小: 43.6 KB
- 大小: 48.7 KB
- 大小: 52.8 KB
分享到:
相关推荐
`name`字段指定了攻击模式的名称(在这里是"SpearPhishing"),而`external_references`字段则关联了外部定义的分类体系,即CAPEC(Common Attack Pattern Enumeration and Classification)中的编号CAPEC-163,表示...
自1995年以来,在国内外重要学术刊物和会议上发表8篇论文,其中2篇论文被IEEE国际会议录用。已出版3本有关网络的译作。目前从事软件需求工程、网络协议验证形式化方法以及函数式语言等方面的研究。 译者序: 我们...
14.3 抽象与密封 .163 14.4 继承中关于属性的一些问题.169 14.5 小 结 .172 第四部分 深入了解 C#.174 第十五章 接 口 .174 15.1 组件编程技术 .174 15.2 接 口 定 义 .177 15.3 接口的成员 .178 ...
4.2.9 将程序与源代码相关联..... 123 4.2.10 热代码替换..... 123 4.3 远程调试...... 124 4.4 练习概述...... 125 4.5 本章小结...... 126 4.6 参考文献...... 127 第5章 协同使用Eclipse 129 5.1 ...
4.2.9 将程序与源代码相关联..... 123 4.2.10 热代码替换..... 123 4.3 远程调试...... 124 4.4 练习概述...... 125 4.5 本章小结...... 126 4.6 参考文献...... 127 第5章 协同使用Eclipse 129 5.1 ...
4.2.9 将程序与源代码相关联..... 123 4.2.10 热代码替换..... 123 4.3 远程调试...... 124 4.4 练习概述...... 125 4.5 本章小结...... 126 4.6 参考文献...... 127 第5章 协同使用Eclipse 129 5.1 Eclipse对CVS的...