`
hideto
  • 浏览: 2666373 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Rails源码研究之ActiveRecord:六,Acts

    博客分类:
  • Ruby
阅读更多
ActiveRecord自带了三种数据结构关系:acts_as_tree、acts_as_list、acts_as_nested_set

1,tree.rb
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

从上面的代码我们学习到如下几点:
1) acts_as_tree实际上定义了belongs_to :parenthas_many :children,也就是说我们可以通过@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
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

从上面的代码我们学习到,acts_as_list的配置参数有:column和:scope
1) :column默认名为position
2) :scope后的参数为symbol时,如果没有_id则加上_id后缀
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

这样生成的scope_condition为"todo_list_id = #{todo_list_id}"
3) :scope后的参数为string时可以进行细粒度的限制
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

4) 实例方法很多,简单看看即可,不一一介绍了

3,nested_set.rb
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

我们可以得到如下几点:
1) acts_as_nested_set和acts_as_tree很类似,但是多一个特性是可以一条语句查询出所有的children
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

以及高效率的计算children_count
def children_count
  return (self[right_col_name] - self[left_col_name] - 1)/2
end

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吧
分享到:
评论
4 楼 blackanger 2007-06-24  
rails2.0什么时候发布呢?有说日期吗?
3 楼 hideto 2007-06-24  
migration的核心方法:
def migrate(direction)
  return unless respond_to?(direction)

  case direction
    when :up   then announce "migrating"
    when :down then announce "reverting"
  end
  
  result = nil
  time = Benchmark.measure { result = send("real_#{direction}") }

  case direction
    when :up   then announce "migrated (%.4fs)" % time.real; write
    when :down then announce "reverted (%.4fs)" % time.real; write
  end
  
  result
end

DHH在RailsConf2007上介绍Rails 2.0的Speech中的形容的是“Sexy Migrations”:
create_table :people do |t|
  t.account_id :type => :integer
end

或者更cool的
create_table :people do |t|
  t.integer :account_id
end

再也不用
create_table :people do |t|
  t.column :account_id, :integer
end
2 楼 netfishx 2007-06-23  
migration还是很有意思的
1 楼 hideto 2007-06-22  
javaeye的表情替代符号真是让人伤心

相关推荐

    rails_async_migrations:对ActiveRecord :: Migration的异步支持

    RailsAsyncMigrations ActiveRecord::Migration扩展程序以一种简单直接的方式使您的迁移异步。动机创建该库的目的是为了帮助在技术水平上难以扩展的小型公司。 小型项目不需要异步迁移队列,大公司在遇到扩展问题时...

    Rails的精简版本Rails::API.zip

    Rails::API 是 Rails 的精简版本,针对不需要使用完整 Rails 功能的开发者。 Rails::API 移除了 ActionView 和其他一些渲染功能,不关心Web前端的开发者可更容易、快速地开发应用程序,因此运行速度比正常的 Rails ...

    userstamp, 这个 Rails 插件扩展ActiveRecord.zip

    userstamp, 这个 Rails 插件扩展ActiveRecord Userstamp插件( v-2.0 )概述Userstamp插件扩展了 ActiveRecord::Base,以添加对'创建者','更新程序'和'deleter'属性的自动更新。 它是基于 ActiveRecord::Timesta

    acts_as_aliased:扩展 ActiveRecord

    rails generate acts_as_aliased:install rake db:migrate 这将创建一个新表aliases 。 用法 假设您有一个需要别名的模型Company ,因为公司名称有不同的版本。 使用acts_as_aliased在模型中启用别名: model ...

    to_xls-rails:将Rails ActiveRecord或Mongid数据导出到Excel文件

    数组元素支持对象:ActiveRecord,Mongid,哈希。 在您的Gemfile中: gem 'to_xls-rails' # Last officially released gem # gem "to_xls-rails", :git =&gt; "git://github....

    ROR绿色最新环境(2013/3/10)

    Rails::Rack::Logger ActionDispatch::ShowExceptions ActionDispatch::DebugExceptions ActionDispatch::RemoteIp ActionDispatch::Reloader ActionDispatch::Callbacks ActiveRecord::ConnectionAdapters::...

    ActiveRecord简单实例_activerecord.zip

    在Ruby on Rails框架中,ActiveRecord是一个至关重要的组件,它负责模型(Model)与数据库之间的交互。本实例将深入探讨ActiveRecord的基本用法,帮助理解如何在实际开发中有效地运用这个强大的工具。 首先,让我们...

    Secode_level_cache.zip

    现在我们将这个插件从Rails2.x的版本升级到了3.x版本,并且抽取成了一个通用插件,开始应用于新的Rails3.2的项目之上。有志于AR对象缓存优化的ruby程序员不容错过。 使用方法: class User &lt; ActiveRecord::Base ...

    acts_as_category:想想acts_as_tree +权限

    #ActsAsCategory acts_as_category (Version 2.0 beta)acts_as_category,是acts_as插件在acts_as_tree风格的Ruby on Rails的ActiveRecord的模式,但有一些额外的功能,以及多种便捷视图助手。例子(有关实例方法和...

    ActiveRecord-Without-Rails:只是在没有Rails的情况下使用ActiveRecord迁移的简单示例

    没有Rails的ActiveRecord 只是在没有Rails的情况下使用ActiveRecord迁移的简单示例您可以执行的任务: rake db:create rake db:migrate rake db:dropRails 5+的注意事项请注意,即使使用Rails 5,您也需要rake db:...

    stateful_enum:基于ActiveRecord :: Enum构建的非常简单的状态机插件

    stateful_enum是建立在ActiveRecord的内置ActiveRecord :: Enum之上的状态机gem。 安装 将此行添加到您的Rails应用程序的Gemfile中: gem 'stateful_enum' 和捆绑。 动机 您不需要抽象 stateful_enum取决于...

    枚举:具有I18n和ActiveRecordMongoid支持的枚举属性

    Rails 5.2+用法基本: class User extend Enumerize enumerize :sex , in : [ :male , :female ]end 请注意,枚举值仅是标识符,因此,如果要使用多字等值,则应使用I18n功能。 ActiveRecord: class CreateUsers &...

    Ruby on Rails中的ActiveRecord编程指南

    ### Ruby on Rails中的ActiveRecord编程指南 #### 一、引言 在Ruby on Rails框架中,ActiveRecord是一种用于实现数据库抽象层的对象关系映射(ORM)工具。它为开发人员提供了一种简单而强大的方式来处理数据库记录...

    emotions:允许ActiveRecord记录表达(并希望存储)关于其他记录的情绪(例如,“快乐”,“悲伤”,“惊奇”等)

    安装将此行添加到您的应用程序的Gemfile中: gem 'emotions' 然后执行: $ bundle 运行迁移以添加emotions表和Emotion模型: $ rails generate emotions:install用法配置允许的情绪。 Emotions . configure do | ...

    Rails项目源代码

    这个Rails项目提供了学习和研究Web开发的机会,特别是对于Ruby on Rails新手,可以通过阅读和理解源代码来提升技能,了解实际应用中Rails的用法。同时,对于有经验的开发者,这个项目也可以作为一个起点,进行二次...

    paranoid2:Rails 4 的偏执模型

    Rails 4 定义了ActiveRecord::Base#destroy! 所以Paranoid2 gem 使用force: true arg 来强制销毁。 安装 将此行添加到应用程序的 Gemfile 中: gem 'paranoid2' 然后执行: $ bundle 用法 将deleted_at: ...

    hashid-rails:在Rails应用程序ActiveRecord模型中使用Hashids(http:hashids.orgruby)

    在要启用哈希值的ActiveRecord模型中包括Hashid Rails。 class Model &lt; ActiveRecord :: Base include Hashid :: Rails end 继续使用Model#find输入hashid或常规的'ol id。 @person = Person . find ( params...

    acts_as_liked:向任何 Active Record 模型添加类似功能

    $ rails generate acts_as_liked 并且不要忘记迁移您的数据库 $ rake db:migrate 用法 可爱的模特 将acts_as_likeable添加到任何模型,它的实例可以被其他模型喜欢。 class Post &lt; ActiveRecord :: Base ...

    rails-exporter-源码.rar

    《Rails Exporter 源码解析》 Rails Exporter 是一个用于 Rails 应用程序的开源工具,主要用于数据导出功能。源码分析将帮助我们深入理解其内部工作原理,以便更好地利用它来优化我们的应用。 一、Rails 框架基础 ...

    workflow-activerecord:工作流库的 ActiveRecordRails 集成

    工作流库的 ActiveRecord/Rails 集成 工作流活动记录的主要+次要版本基于最旧的兼容 ActiveRecord API。 要在 Rails/ActiveRecord 4.1、4.2、5.0、5.1、5.2、6.0、6.1 中使用 ,请使用: gem 'workflow-...

Global site tag (gtag.js) - Google Analytics