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