Rails源码研究之ActiveRecord:六,Acts

ActiveRecord自带了三种数据结构关系:acts_as_tree、acts_as_list、acts_as_nested_set

1,tree.rb
[code]
module ActiveRecord
module Acts
module Tree

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def acts_as_tree(options = {})
configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil }
configuration.update(options) if options.is_a?(Hash)

belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy

class_eval <<-EOV
include ActiveRecord::Acts::Tree::InstanceMethods

def self.roots
find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
end

def self.root
find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
end
EOV
end
end

module InstanceMethods
def ancestors
node, nodes = self, []
nodes << node = node.parent while node.parent
nodes
end

def root
node = self
node = node.parent while node.parent
node
end

def siblings
self_and_siblings - [self]
end

def self_and_siblings
parent ? parent.children : self.class.roots
end
end
end
end
end
[/code]
从上面的代码我们学习到如下几点:
1) acts_as_tree实际上定义了[b]belongs_to :parent[/b]和[b]has_many :children[/b],也就是说我们可以通过@model.parent和@model.children得到父子对象
2) acts_as_tree的配置参数有:foreign_key、:order => nil和:counter_cache
3) 其中定义了roots和root这两个Class Methods
4) 其中还定义了ancestors、root、sliblings、self_and_siblings这些Instance Methods

2,list.rb
[code]
module ActiveRecord
module Acts
module List

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def acts_as_list(options = {})
configuration = { :column => "position", :scope => "1 = 1" }
configuration.update(options) if options.is_a?(Hash)

configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/

if configuration[:scope].is_a?(Symbol)
scope_condition_method = %(
def scope_condition
if #{configuration[:scope].to_s}.nil?
"#{configuration[:scope].to_s} IS NULL"
else
"#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
end
end
)
else
scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
end

class_eval <<-EOV
include ActiveRecord::Acts::List::InstanceMethods

def acts_as_list_class
::#{self.name}
end

def position_column
'#{configuration[:column]}'
end

#{scope_condition_method}

after_destroy :remove_from_list
before_create :add_to_list_bottom
EOV
end
end

module InstanceMethods
def insert_at(position = 1)
insert_at_position(position)
end

def move_lower
return unless lower_item

acts_as_list_class.transaction do
lower_item.decrement_position
increment_position
end
end

def move_higher
return unless higher_item

acts_as_list_class.transaction do
higher_item.increment_position
decrement_position
end
end

def move_to_bottom
return unless in_list?
acts_as_list_class.transaction do
decrement_positions_on_lower_items
assume_bottom_position
end
end

def move_to_top
return unless in_list?
acts_as_list_class.transaction do
increment_positions_on_higher_items
assume_top_position
end
end

def remove_from_list
decrement_positions_on_lower_items if in_list?
end

def increment_position
return unless in_list?
update_attribute position_column, self.send(position_column).to_i + 1
end

def decrement_position
return unless in_list?
update_attribute position_column, self.send(position_column).to_i - 1
end

def first?
return false unless in_list?
self.send(position_column) == 1
end

def last?
return false unless in_list?
self.send(position_column) == bottom_position_in_list
end

def higher_item
return nil unless in_list?
acts_as_list_class.find(:first, :conditions =>
"#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
)
end

def lower_item
return nil unless in_list?
acts_as_list_class.find(:first, :conditions =>
"#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
)
end

def in_list?
!send(position_column).nil?
end

private
def add_to_list_top
increment_positions_on_all_items
end

def add_to_list_bottom
self[position_column] = bottom_position_in_list.to_i + 1
end

def scope_condition() "1" end

def bottom_position_in_list(except = nil)
item = bottom_item(except)
item ? item.send(position_column) : 0
end

def bottom_item(except = nil)
conditions = scope_condition
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
end

def assume_bottom_position
update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
end

def assume_top_position
update_attribute(position_column, 1)
end

def decrement_positions_on_higher_items(position)
acts_as_list_class.update_all(
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
)
end

def decrement_positions_on_lower_items
return unless in_list?
acts_as_list_class.update_all(
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
)
end

def increment_positions_on_higher_items
return unless in_list?
acts_as_list_class.update_all(
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
)
end

def increment_positions_on_lower_items(position)
acts_as_list_class.update_all(
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
)
end

def increment_positions_on_all_items
acts_as_list_class.update_all(
"#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
)
end

def insert_at_position(position)
remove_from_list
increment_positions_on_lower_items(position)
self.update_attribute(position_column, position)
end
end
end
end
end
[/code]
从上面的代码我们学习到,acts_as_list的配置参数有:column和:scope
1) :column默认名为position
2) :scope后的参数为symbol时,如果没有_id则加上_id后缀
[code]
class TodoList < ActiveRecord::Base
has_many :todo_items, :order => "position"
end

class TodoItem < ActiveRecord::Base
belongs_to :todo_list
acts_as_list :scope => :todo_list
end
[/code]
这样生成的scope_condition为"todo_list_id = #{todo_list_id}"
3) :scope后的参数为string时可以进行细粒度的限制
[code]
class TodoList < ActiveRecord::Base
has_many :todo_items, :order => "position"
end

class TodoItem < ActiveRecord::Base
belongs_to :todo_list
acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'
end
[/code]
4) 实例方法很多,简单看看即可,不一一介绍了

3,nested_set.rb
[code]
module ActiveRecord
module Acts
module NestedSet

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def acts_as_nested_set(options = {})
configuration = { :parent_column => "parent_id", :left_column => "lft", :right_column => "rgt", :scope => "1 = 1" }

configuration.update(options) if options.is_a?(Hash)

configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/

if configuration[:scope].is_a?(Symbol)
scope_condition_method = %(
def scope_condition
if #{configuration[:scope].to_s}.nil?
"#{configuration[:scope].to_s} IS NULL"
else
"#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
end
end
)
else
scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
end

class_eval <<-EOV
include ActiveRecord::Acts::NestedSet::InstanceMethods

#{scope_condition_method}

def left_col_name() "#{configuration[:left_column]}" end

def right_col_name() "#{configuration[:right_column]}" end

def parent_column() "#{configuration[:parent_column]}" end

EOV
end
end

module InstanceMethods
def root?
parent_id = self[parent_column]
(parent_id == 0 || parent_id.nil?) && (self[left_col_name] == 1) && (self[right_col_name] > self[left_col_name])
end

def child?
parent_id = self[parent_column]
!(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
end

def unknown?
!root? && !child?
end

def add_child( child )
self.reload
child.reload

if child.root?
raise "Adding sub-tree isn\'t currently supported"
else
if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) )
self[left_col_name] = 1
self[right_col_name] = 4

return nil unless self.save

child[parent_column] = self.id
child[left_col_name] = 2
child[right_col_name]= 3
return child.save
else
child[parent_column] = self.id
right_bound = self[right_col_name]
child[left_col_name] = right_bound
child[right_col_name] = right_bound + 1
self[right_col_name] += 2
self.class.base_class.transaction {
self.class.base_class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
self.class.base_class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
self.save
child.save
}
end
end
end

def children_count
return (self[right_col_name] - self[left_col_name] - 1)/2
end

def full_set
self.class.base_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} and #{self[right_col_name]})" )
end

def all_children
self.class.base_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
end

def direct_children
self.class.base_class.find(:all, :conditions => "#{scope_condition} and #{parent_column} = #{self.id}")
end

def before_destroy
return if self[right_col_name].nil? || self[left_col_name].nil?
dif = self[right_col_name] - self[left_col_name] + 1

self.class.base_class.transaction {
self.class.base_class.delete_all( "#{scope_condition} and #{left_col_name} > #{self[left_col_name]} and #{right_col_name} < #{self[right_col_name]}" )
self.class.base_class.update_all( "#{left_col_name} = (#{left_col_name} - #{dif})", "#{scope_condition} AND #{left_col_name} >= #{self[right_col_name]}" )
self.class.base_class.update_all( "#{right_col_name} = (#{right_col_name} - #{dif} )", "#{scope_condition} AND #{right_col_name} >= #{self[right_col_name]}" )
}
end
end
end
end
end
[/code]
我们可以得到如下几点:
1) acts_as_nested_set和acts_as_tree很类似,但是多一个特性是可以一条语句查询出所有的children
[code]
def all_children
self.class.base_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
end
[/code]
以及高效率的计算children_count
[code]
def children_count
return (self[right_col_name] - self[left_col_name] - 1)/2
end
[/code]
2) acts_as_nested_set的参数为:parent_column、:left_column、:right_column和:scope
3) 这种数据结构常用在threaded post system等场景

看了上述代码,相信大家可以自己动手写acts_as_xx数据结构了

BTW:对ActiveRecord源码的研究告一段落,还有query_cache/observer/xml_serialization/timestamp/calculations/migration等留待大家自己研究
接下来看看ActionController吧
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值