Ancestry with Rails
Rails本身提供了self-join方案,你可以采用此方法,然后查询全部,然后将数据解析为树。 你也可以按照递归查询的方式追加数据然后返回。 但是这里我采用第三方插件Ancestry。
【1】Install Ancestry
在Gemfile中配置Ancestry插件
gem 'ancestry'
然后执行命令
$ bundle install
这样Ancestry插件就安装成功了。
【2】Add column to your table
接下来你需要向自关联的表中加入Ancestry自动维护的字段,假设这里我们用的表名是messages
$ rails g migration add_ancestry_to_messages ancestry:string
这是Rails会自动创建一个migration文件,在migration文件中添加add_index
add_index :messages, :ancestry
最终代码如下:
class AddAncestryToMessage < ActiveRecord::Migration
def change
add_column :messages, :ancestry, :string
add_index :messages, :ancestry
end
end
执行命令迁移数据库
$ rake db:migrate
同时在models中添加has_ancestry
class Message < ActiveRecord::Base
has_ancestry
end
【3】Final Work
剩下的就是在views和controller中做一些必要的代码处理了。 以新建一个回复作为例子说明,首先在列表页添加回复按钮,传递参数名称为parent_id
<%= link_to 'Reply', new_message_path(:parent_id => message) %>
在messages_controller中接收parent_id并存储。
def new
@message = Message.new(:parent_id => params[:parent_id])
end
同时这里需要说明一下,messages表中本身不存在parten_id字段,Ancestry插件本身会对parent_id进行维护并对messages表中ancestry字段进行存储修改。但是Rails保护机制中permit字段还是设置为parent_id
def message_params
params.require(:message).permit(:title, :content, :parent_id)
end
因为new方法中需要一个parent_id参数,在新增message时也需要这个参数,所以要再_form页面中添加一个隐藏字段
<%= f.hidden_field :parent_id %>
这样我们在index页面传递参数到new页面,并将参数以隐藏表单的方式传递给create方法,Ancestry会自动维护’parten-id’ 这个字段并存储一个父及id们拼装而成的字符串到ancestry字段(比如:14/3/4)。 这里还有一点,要为new页面显示parent-id所对应的信息内容,同时index页面有所有信息显示的列表,这样的话可以把信息部分抽象到一个_message页面。在这个页面中显示message实体的全部字段。然后对index和new页面进行一定调整。 new.html.erb中添加如下语句就可以显示其对应parten-id的信息。
<%= render @message.parent if @message.parent %>
在index.html.erb中需要通过ancestry插件的提供的方法组织元素顺序,具体代码如下:
<%= nested_messages @messages.arrange(:order => :created_at) %>
这里我们将nested_messages抽取出来定义在message.helper.rb中按nested顺序render到页面中,具体代码如下:
module MessagesHelper
def nested_messages(messages)
messages.map do |message, sub_messages|
render(message)+content_tag(:div, nested_messages(sub_messages), :style => "margin-left:30px;")
end.join.html_safe
end
end