- 浏览: 2678366 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
80后的童年2:
深入浅出MongoDB应用实战开发网盘地址:https://p ...
MongoDB入门教程 -
shliujing:
楼主在不是精通java和php的前提下,请不要妄下结论。
PHP、CakePHP哪凉快哪呆着去 -
安静听歌:
希望可以一给一点点注释
MySQL存储过程之代码块、条件控制、迭代 -
qq287767957:
PHP是全宇宙最强的语言!
PHP、CakePHP哪凉快哪呆着去 -
rryymmoK:
深入浅出MongoDB应用实战开发百度网盘下载:链接:http ...
MongoDB入门教程
今天学习一下ActiveRecord的Associations相关的源码,即了解一下我们常用的has_many、has_one、belongs_to、has_and_belongs_to_many的原理
1,activerecord-1.15.3\lib\active_record\associations.rb:
该文件主要定义了has_many、has_one、belongs_to、has_and_belongs_to_many这四种方法并将它们分别代理到各个具体的关联类
2,activerecord-1.15.3\lib\active_record\associations\association_collection.rb:
AssociationCollection是HasOneAssociation、HasManyAssociation、HasManyThroughAssociation、BelongsToAssociation、HasAndBelongsToManyAssociation、
BelongsToPolymorphicAssociation这六种关联类的父类,其中定义了关联的<<、create、delete、clear等方法
3,activerecord-1.15.3\lib\active_record\reflection.rb:
该文件定义了reflection变量以及AggregateReflection、AssociationReflection类,我们在关联类的build方法里可以看到record = @reflection.klass.new(attributes)的调用
4,activerecord-1.15.3\lib\active_record\associations\has_many_association.rb:
HasManyAssociation等具体的关联类里面就定义了一些自己的find、count、build等方法
总体说来,ActiveRecord首先定义has_many、belongs_to等方法,并利用反射得到这些方法的参数所代表的类,然后把对这些类的操作代理到具体的HasManyAssociation等关联类
了解了ActiveRecord的associations,我们可以轻松定制自己的关联方法和关联类
1,activerecord-1.15.3\lib\active_record\associations.rb:
require 'active_record/associations/association_proxy' require 'active_record/associations/association_collection' require 'active_record/associations/belongs_to_association' require 'active_record/associations/belongs_to_polymorphic_association' require 'active_record/associations/has_one_association' require 'active_record/associations/has_many_association' require 'active_record/associations/has_many_through_association' require 'active_record/associations/has_and_belongs_to_many_association' require 'active_record/deprecated_associations' module ActiveRecord module Associations module ClassMethods def has_many(association_id, options = {}, &extension) reflection = create_has_many_reflection(association_id, options, &extension) configure_dependency_for_has_many(reflection) if options[:through] collection_reader_method(reflection, HasManyThroughAssociation) else add_multiple_associated_save_callbacks(reflection.name) add_association_callbacks(reflection.name, reflection.options) collection_accessor_methods(reflection, HasManyAssociation) end add_deprecated_api_for_has_many(reflection.name) end def has_one(association_id, options = {}) reflection = create_has_one_reflection(association_id, options) module_eval do after_save <<-EOF association = instance_variable_get("@#{reflection.name}") if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id) association["#{reflection.primary_key_name}"] = id association.save(true) end EOF end association_accessor_methods(reflection, HasOneAssociation) association_constructor_method(:build, reflection, HasOneAssociation) association_constructor_method(:create, reflection, HasOneAssociation) configure_dependency_for_has_one(reflection) # deprecated api deprecated_has_association_method(reflection.name) deprecated_association_comparison_method(reflection.name, reflection.class_name) end def belongs_to(association_id, options = {}) if options.include?(:class_name) && !options.include?(:foreign_key) ::ActiveSupport::Deprecation.warn( "The inferred foreign_key name will change in Rails 2.0 to use the association name instead of its class name when they differ. When using :class_name in belongs_to, use the :foreign_key option to explicitly set the key name to avoid problems in the transition.", caller) end reflection = create_belongs_to_reflection(association_id, options) if reflection.options[:polymorphic] association_accessor_methods(reflection, BelongsToPolymorphicAssociation) module_eval do before_save <<-EOF association = instance_variable_get("@#{reflection.name}") if association && association.target if association.new_record? association.save(true) end if association.updated? self["#{reflection.primary_key_name}"] = association.id self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s end end EOF end else association_accessor_methods(reflection, BelongsToAssociation) association_constructor_method(:build, reflection, BelongsToAssociation) association_constructor_method(:create, reflection, BelongsToAssociation) module_eval do before_save <<-EOF association = instance_variable_get("@#{reflection.name}") if !association.nil? if association.new_record? association.save(true) end if association.updated? self["#{reflection.primary_key_name}"] = association.id end end EOF end # deprecated api deprecated_has_association_method(reflection.name) deprecated_association_comparison_method(reflection.name, reflection.class_name) end if options[:counter_cache] cache_column = options[:counter_cache] == true ? "#{self.to_s.underscore.pluralize}_count" : options[:counter_cache] module_eval( "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" + " unless #{reflection.name}.nil?'" ) module_eval( "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" + " unless #{reflection.name}.nil?'" ) end end def has_and_belongs_to_many(association_id, options = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) add_multiple_associated_save_callbacks(reflection.name) collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) old_method = "destroy_without_habtm_shim_for_#{reflection.name}" class_eval <<-end_eval alias_method :#{old_method}, :destroy_without_callbacks def destroy_without_callbacks #{reflection.name}.clear #{old_method} end end_eval add_association_callbacks(reflection.name, options) # deprecated api deprecated_collection_count_method(reflection.name) deprecated_add_association_relation(reflection.name) deprecated_remove_association_relation(reflection.name) deprecated_has_collection_method(reflection.name) end private def association_accessor_methods(reflection, association_proxy_class) define_method(reflection.name) do |*params| force_reload = params.first unless params.empty? association = instance_variable_get("@#{reflection.name}") if association.nil? || force_reload association = association_proxy_class.new(self, reflection) retval = association.reload if retval.nil? and association_proxy_class == BelongsToAssociation instance_variable_set("@#{reflection.name}", nil) return nil end instance_variable_set("@#{reflection.name}", association) end association.target.nil? ? nil : association end define_method("#{reflection.name}=") do |new_value| association = instance_variable_get("@#{reflection.name}") if association.nil? association = association_proxy_class.new(self, reflection) end association.replace(new_value) unless new_value.nil? instance_variable_set("@#{reflection.name}", association) else instance_variable_set("@#{reflection.name}", nil) return nil end association end define_method("set_#{reflection.name}_target") do |target| return if target.nil? and association_proxy_class == BelongsToAssociation association = association_proxy_class.new(self, reflection) association.target = target instance_variable_set("@#{reflection.name}", association) end end def collection_reader_method(reflection, association_proxy_class) define_method(reflection.name) do |*params| force_reload = params.first unless params.empty? association = instance_variable_get("@#{reflection.name}") unless association.respond_to?(:loaded?) association = association_proxy_class.new(self, reflection) instance_variable_set("@#{reflection.name}", association) end association.reload if force_reload association end end def collection_accessor_methods(reflection, association_proxy_class) collection_reader_method(reflection, association_proxy_class) define_method("#{reflection.name}=") do |new_value| # Loads proxy class instance (defined in collection_reader_method) if not already loaded association = send(reflection.name) association.replace(new_value) association end define_method("#{reflection.name.to_s.singularize}_ids") do send(reflection.name).map(&:id) end define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| ids = (new_value || []).reject { |nid| nid.blank? } send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) end end def association_constructor_method(constructor, reflection, association_proxy_class) define_method("#{constructor}_#{reflection.name}") do |*params| attributees = params.first unless params.empty? replace_existing = params[1].nil? ? true : params[1] association = instance_variable_get("@#{reflection.name}") if association.nil? association = association_proxy_class.new(self, reflection) instance_variable_set("@#{reflection.name}", association) end if association_proxy_class == HasOneAssociation association.send(constructor, attributees, replace_existing) else association.send(constructor, attributees) end end end def create_has_many_reflection(association_id, options, &extension) options.assert_valid_keys( :class_name, :table_name, :foreign_key, :exclusively_dependent, :dependent, :select, :conditions, :include, :order, :group, :limit, :offset, :as, :through, :source, :source_type, :uniq, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove, :extend ) options[:extend] = create_extension_module(association_id, extension) if block_given? create_reflection(:has_many, association_id, options, self) end def create_has_one_reflection(association_id, options) options.assert_valid_keys( :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as ) create_reflection(:has_one, association_id, options, self) end def create_belongs_to_reflection(association_id, options) options.assert_valid_keys( :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :polymorphic ) reflection = create_reflection(:belongs_to, association_id, options, self) if options[:polymorphic] reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type" end reflection end def create_has_and_belongs_to_many_reflection(association_id, options, &extension) options.assert_valid_keys( :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, :select, :conditions, :include, :order, :group, :limit, :offset, :uniq, :finder_sql, :delete_sql, :insert_sql, :before_add, :after_add, :before_remove, :after_remove, :extend ) options[:extend] = create_extension_module(association_id, extension) if block_given? reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) reflection end end end end
该文件主要定义了has_many、has_one、belongs_to、has_and_belongs_to_many这四种方法并将它们分别代理到各个具体的关联类
2,activerecord-1.15.3\lib\active_record\associations\association_collection.rb:
module ActiveRecord module Associations class AssociationCollection < AssociationProxy def <<(*records) result = true load_target @owner.transaction do flatten_deeper(records).each do |record| raise_on_type_mismatch(record) callback(:before_add, record) result &&= insert_record(record) unless @owner.new_record? @target << record callback(:after_add, record) end end result && self end alias_method :push, :<< alias_method :concat, :<< def delete_all load_target delete(@target) reset_target! end def delete(*records) records = flatten_deeper(records) records.each { |record| raise_on_type_mismatch(record) } records.reject! { |record| @target.delete(record) if record.new_record? } return if records.empty? @owner.transaction do records.each { |record| callback(:before_remove, record) } delete_records(records) records.each do |record| @target.delete(record) callback(:after_remove, record) end end end def clear return self if length.zero? # forces load_target if hasn't happened already if @reflection.options[:dependent] && @reflection.options[:dependent] == :delete_all destroy_all else delete_all end self end def destroy_all @owner.transaction do each { |record| record.destroy } end reset_target! end def create(attributes = {}) if attributes.is_a?(Array) attributes.collect { |attr| create(attr) } else record = build(attributes) record.save unless @owner.new_record? record end end protected def find_target records = if @reflection.options[:finder_sql] @reflection.klass.find_by_sql(@finder_sql) else find(:all) end @reflection.options[:uniq] ? uniq(records) : records end end end end
AssociationCollection是HasOneAssociation、HasManyAssociation、HasManyThroughAssociation、BelongsToAssociation、HasAndBelongsToManyAssociation、
BelongsToPolymorphicAssociation这六种关联类的父类,其中定义了关联的<<、create、delete、clear等方法
3,activerecord-1.15.3\lib\active_record\reflection.rb:
module ActiveRecord module Reflection module ClassMethods def create_reflection(macro, name, options, active_record) case macro when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many reflection = AssociationReflection.new(macro, name, options, active_record) when :composed_of reflection = AggregateReflection.new(macro, name, options, active_record) end write_inheritable_hash :reflections, name => reflection reflection end end class MacroReflection attr_reader :active_record def initialize(macro, name, options, active_record) @macro, @name, @options, @active_record = macro, name, options, active_record end end class AggregateReflection < MacroReflection #:nodoc: def klass @klass ||= Object.const_get(options[:class_name] || class_name) end end class AssociationReflection < MacroReflection #:nodoc: def klass @klass ||= active_record.send(:compute_type, class_name) end end end end
该文件定义了reflection变量以及AggregateReflection、AssociationReflection类,我们在关联类的build方法里可以看到record = @reflection.klass.new(attributes)的调用
4,activerecord-1.15.3\lib\active_record\associations\has_many_association.rb:
module ActiveRecord module Associations class HasManyAssociation < AssociationCollection def build(attributes = {}) if attributes.is_a?(Array) attributes.collect { |attr| build(attr) } else record = @reflection.klass.new(attributes) set_belongs_to_association_for(record) @target ||= [] unless loaded? @target << record record end end def count(*args) if @reflection.options[:counter_sql] @reflection.klass.count_by_sql(@counter_sql) elsif @reflection.options[:finder_sql] @reflection.klass.count_by_sql(@finder_sql) else column_name, options = @reflection.klass.send(:construct_count_options_from_legacy_args, *args) options[:conditions] = options[:conditions].nil? ? @finder_sql : @finder_sql + " AND (#{sanitize_sql(options[:conditions])})" options[:include] = @reflection.options[:include] @reflection.klass.count(column_name, options) end end def find(*args) options = Base.send(:extract_options_from_args!, args) if @reflection.options[:finder_sql] expects_array = args.first.kind_of?(Array) ids = args.flatten.compact.uniq if ids.size == 1 id = ids.first record = load_target.detect { |record| id == record.id } expects_array ? [ record ] : record else load_target.select { |record| ids.include?(record.id) } end else conditions = "#{@finder_sql}" if sanitized_conditions = sanitize_sql(options[:conditions]) conditions << " AND (#{sanitized_conditions})" end options[:conditions] = conditions if options[:order] && @reflection.options[:order] options[:order] = "#{options[:order]}, #{@reflection.options[:order]}" elsif @reflection.options[:order] options[:order] = @reflection.options[:order] end merge_options_from_reflection!(options) # Pass through args exactly as we received them. args << options @reflection.klass.find(*args) end end protected def method_missing(method, *args, &block) if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) super else create_scoping = {} set_belongs_to_association_for(create_scoping) @reflection.klass.with_scope( :create => create_scoping, :find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false } ) do @reflection.klass.send(method, *args, &block) end end end end end end
HasManyAssociation等具体的关联类里面就定义了一些自己的find、count、build等方法
总体说来,ActiveRecord首先定义has_many、belongs_to等方法,并利用反射得到这些方法的参数所代表的类,然后把对这些类的操作代理到具体的HasManyAssociation等关联类
了解了ActiveRecord的associations,我们可以轻松定制自己的关联方法和关联类
发表评论
-
用了TextMate才知道什么叫神级Editor
2011-03-09 04:51 57961一直用Eclipse作为开发Ruby和Java项目的IDE,但 ... -
Ruby使用OAuth登录新浪微博和豆瓣
2011-01-09 12:49 4433首先需要安装oauth这个gem包 gem install ... -
使用Passenger+nginx部署Rails
2010-12-28 15:12 50101. Install Passender gem instal ... -
markItUp+rdiscount搭建Rails下可视化Markdown编辑器
2010-12-21 17:48 5447markItUp是基于jQuery的可视化编辑器,支持Html ... -
Rails3 and MongoDB Quick Guide
2010-12-10 14:13 2753Install MongoDB Download: http: ... -
基于ruby-protobuf的rpc示例
2009-08-11 11:51 41481, 安装ruby-protobuf gem instal ... -
Ruby导出xls和csv的utf-8问题的解决
2009-02-04 15:05 6839数据库数据为utf-8格式,包括中文和拉丁文等等 导出文件xl ... -
URL/HTML/JavaScript的encode/escape
2009-01-04 13:03 9324最近经常被URL、HTML、JavaScript的encode ... -
各种排序的Ruby实现
2008-11-27 14:51 3994Θ(n^2) 1, Bubble sort def bu ... -
12月5日北京RoR活动!
2008-11-26 18:38 3017又是一年过去了,Rails在国内的发展势态良好,很多使用RoR ... -
Rails程序开发的最大问题是代码规范
2008-08-28 11:56 5518使用Rails开发大型复杂B2B应用一年了,这个项目目前开发人 ... -
Web开发大全:ROR版——推荐序
2008-07-09 00:39 2416来自http://www.beyondrails.com/bl ... -
深入ActionMailer,使用Sendmail发邮件
2008-07-03 11:41 3396来自: http://www.beyondrails.com/ ... -
Rails里如何结合ExceptionNotification配置gmail账户发邮件
2008-06-19 19:56 30821,安装ExceptionNotification rub ... -
使用coderay和railscasts样式进行代码高亮
2008-06-17 00:16 2395CodeRay是一个语法高亮的Ruby库,效率很不错。 Cod ... -
Capistrano试用
2008-06-16 19:05 19581,客户端机器安装Capistrano gem insta ... -
lighttpd真垃圾啊
2008-06-04 18:38 2533使用lighttpd+fcgi跑Rails程序,文件上传会si ... -
将gem变成plugin
2008-06-04 11:27 1801有什么样的需求就有什么样的对策 当vhost上的帐号没有ge ... -
在Rails里使用ReCaptcha添加验证码
2008-06-03 15:51 42661,去http://recaptcha.net/sign up ... -
Rails里给文件上传添加progress_bar
2008-05-27 17:00 2087文件上传很慢时,UI没有什么用户提示,这样让人很费解,所以我们 ...
相关推荐
RailsAsyncMigrations ActiveRecord::Migration扩展程序以一种简单直接的方式使您的迁移异步。动机创建该库的目的是为了帮助在技术水平上难以扩展的小型公司。 小型项目不需要异步迁移队列,大公司在遇到扩展问题时...
Rails::API 是 Rails 的精简版本,针对不需要使用完整 Rails 功能的开发者。 Rails::API 移除了 ActionView 和其他一些渲染功能,不关心Web前端的开发者可更容易、快速地开发应用程序,因此运行速度比正常的 Rails ...
userstamp, 这个 Rails 插件扩展ActiveRecord Userstamp插件( v-2.0 )概述Userstamp插件扩展了 ActiveRecord::Base,以添加对'创建者','更新程序'和'deleter'属性的自动更新。 它是基于 ActiveRecord::Timesta
数组元素支持对象:ActiveRecord,Mongid,哈希。 在您的Gemfile中: gem 'to_xls-rails' # Last officially released gem # gem "to_xls-rails", :git => "git://github....
Rails::Rack::Logger ActionDispatch::ShowExceptions ActionDispatch::DebugExceptions ActionDispatch::RemoteIp ActionDispatch::Reloader ActionDispatch::Callbacks ActiveRecord::ConnectionAdapters::...
在Ruby on Rails框架中,ActiveRecord是一个至关重要的组件,它负责模型(Model)与数据库之间的交互。本实例将深入探讨ActiveRecord的基本用法,帮助理解如何在实际开发中有效地运用这个强大的工具。 首先,让我们...
没有Rails的ActiveRecord 只是在没有Rails的情况下使用ActiveRecord迁移的简单示例您可以执行的任务: rake db:create rake db:migrate rake db:dropRails 5+的注意事项请注意,即使使用Rails 5,您也需要rake db:...
stateful_enum是建立在ActiveRecord的内置ActiveRecord :: Enum之上的状态机gem。 安装 将此行添加到您的Rails应用程序的Gemfile中: gem 'stateful_enum' 和捆绑。 动机 您不需要抽象 stateful_enum取决于...
Ruby on Rails,简称Rails,是基于Ruby语言的一个开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在使Web开发过程更加高效、简洁。本压缩包中的"Ruby on Rails入门经典代码"提供了新手学习...
GraphQL :: QueryResolver GraphQL :: QueryResolver是一个附加,它可使... 然后,每个匹配的选择都将传递到ActiveRecord::Associations::Preloader.new因此您的查询现在仅对GraphQL AST的每个级别发出一个SELECT语句。
这个Rails项目提供了学习和研究Web开发的机会,特别是对于Ruby on Rails新手,可以通过阅读和理解源代码来提升技能,了解实际应用中Rails的用法。同时,对于有经验的开发者,这个项目也可以作为一个起点,进行二次...
### Ruby on Rails中的ActiveRecord编程指南 #### 一、引言 在Ruby on Rails框架中,ActiveRecord是一种用于实现数据库抽象层的对象关系映射(ORM)工具。它为开发人员提供了一种简单而强大的方式来处理数据库记录...
Rails 4 定义了ActiveRecord::Base#destroy! 所以Paranoid2 gem 使用force: true arg 来强制销毁。 安装 将此行添加到应用程序的 Gemfile 中: gem 'paranoid2' 然后执行: $ bundle 用法 将deleted_at: ...
《Rails Exporter 源码解析》 Rails Exporter 是一个用于 Rails 应用程序的开源工具,主要用于数据导出功能。源码分析将帮助我们深入理解其内部工作原理,以便更好地利用它来优化我们的应用。 一、Rails 框架基础 ...
在要启用哈希值的ActiveRecord模型中包括Hashid Rails。 class Model < ActiveRecord :: Base include Hashid :: Rails end 继续使用Model#find输入hashid或常规的'ol id。 @person = Person . find ( params...
Rails 3.1 及以后版本的 ActiveRecord 查询API发生了重大变化,主要目的是为了提供更清晰、更可维护的代码,并且提高性能。在 Rails 2.x 中,许多使用哈希参数的查询方法如 `:conditions`, `:include`, `:joins` 等...
工作流库的 ActiveRecord/Rails 集成 工作流活动记录的主要+次要版本基于最旧的兼容 ActiveRecord API。 要在 Rails/ActiveRecord 4.1、4.2、5.0、5.1、5.2、6.0、6.1 中使用 ,请使用: gem 'workflow-...
Flowdock::Rails 这个 gem 添加了一个类方法来向特定流发送通知,以在启用的资源上创建和更新事件安装将此行添加到应用程序的 Gemfile 中: gem 'flowdock-rails'然后执行: $ bundle或者自己安装: $ gem install ...
- **ActiveRecord查询接口(Query Interface)**:ActiveRecord提供了丰富的查询API,如`User.find(1)`, `Post.where(title: 'Hello')`,用于从数据库检索数据。 - ** erb语法**:在视图文件中,我们可以使用erb...
1. **快速开发**:Rails内置了许多实用的功能和库,如ActiveRecord ORM、MVC架构等,这些都能够极大地加快开发进度。 2. **代码简洁**:Rails遵循“约定优于配置”的原则,这意味着开发者无需编写大量重复代码就能...