`
sungly
  • 浏览: 5682 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

理解ActiveRecord的关联

阅读更多
理解ActiveRecord的关联
初学Rails时,觉得ActiveRecord很神奇,只要在model类中写上has_many, belongs_to等声明,就可方便地引用关联对象.这些关联声明还有很多选项,但开始并不理解(尤其对从未使用过ruby语言的人),只知道模仿着教程中的例子(以下的教程都指那本经典书:Agile web development with Rails)使用默认的选项.但是做实际项目时会发现默认选项不够用了,该怎么办?通过下面一个实际项目开发中的例子,就能真正理解model类的关联是怎么回事了.

场景:一个活动发布网站,要实现计划的发布和用户报名功能.报名还要分为已批准和待批准两种状态.
建模:(为表述方便对各表只列举最重要和有代表性的几个字段)很自然地首先会建立两个数据库表:users,plans.
users包含字段 id, name, password
plans包含字段 id, content, created_at (该记录创建时间,按 Rails 的约定该字段能自动维护), user_id (关联到users表,表示该计划的作者)

为了建立最基本的关联,只需要在模型文件中增加一个has_many和belongs_to:
# user.rb
class User < ActiveRecord::Base
  has_many :plans
end
# plan.rb
class Plan < ActiveRecord::Base
  belongs_to :user
end
增加这些声明的好处是什么?就是将来我得到一个plan对象时,可以用plan.user访问它的作者的信息;得到一个user对象时,可以用user.plans访问他发布的所有计划.多么简单!现在我要实现报名的功能了.
所谓报名,就是在plan和user之间再建立一种关联.这次是典型的多对多关联,因为一个计划可以有多个用户报名,而一个用户也可以报名参加多个计划.
多对多关联?教程上不是有介绍吗?马上模仿着建立一个关联表plan_user.包含字段 plan_id, user_id.
再按教程修改user.rb和plan.rb,增加相应的has_many:
# plan.rb
class Plan < ActiveRecord::Base
  belongs_to :user
  has_and_belongs_to_many : users
end
# user.rb
class User < ActiveRecord::Base
  has_many :plans
  has_and_belongs_to_many : plans
end
等等,User.rb里面怎么出现两个plans了,以后使用user.plans时到底是哪一个?静下来想一想,原来第一个has_many :plans表示一个用户发布的多个计划,第二个表示一个用户参加的多个计划.它们关联的对象虽然相同,但在业务逻辑上的意义是不同的.可是在代码中如何表示这种不同呢?我开始查关联的选项,发现有这么几个可用的选项:
:class_name, :join_table. 也就是说我可以改变关联的名称,但仍然关联到同一个表,修改如下:
# user.rb
class User < ActiveRecord::Base
  has_many :plans
 has_and_belongs_to_many :plans_participated, :class_name => "lan"
end
这样一来,我就可以用user.plans来访问用户发布的计划,用user.plans_participated来访问用户参加的计划了.类似地,我修改了plan.rb中关联的名称以便我可以用plan.participants而不是plan.users来访问一个计划的所有参与者:
# plan.rb
class Plan < ActiveRecord::Base
  belongs_to :user
  has_and_belongs_to_many :participants, :class_name => "User"
end
做到这里,我们稍微总结回味一下就不难发现,其实关于关联的各种声明就相当于给你的模型类动态增加属性,而has_many,belongs_to等声明其实是来自于ActiveRecord基类的一些方法,后面的选项是传给这些方法的参数.这些方法一被执行,就会给你的模型类动态增加一些属性.(在 Ruby的类定义里可以直接执行一些语句,就象HTML中写在<script>中的脚本一样,每当页面被装载就会被执行)
当我们写下  has__many :plans"这句话,就是在调用has_many方法,给User类增加了一个名为plans,类型为集合的一个属性.这个属性的返回值是来自于数据库表的,Rails的底层有足够信息知道该生成一条什么样的sql语句来返回相应的结果.甚至你还可以利用:finder_sql选项指定一条sql语句来替换默认的sql.这就是所谓关联声明的实质,也就是一些来自于基类的方法.一个方法调用是为了给类增加属性,动态地改变一个对象的行为?听起来有点陌生,其实在非脚本语言如C#里面事件机制也是类似的,button.clicked += new EventHandler(OnClick);这句话不就是通过对一个属性赋值而给button对象动态增加了一个方法供调用吗?
当然,Rails替你做的还不止是增加了属性,它还给你的模型类增加了一些操作关联表的方法,包括push,delete,find等(详见教程),省得你再去为plan_user表单独建立一个模型类了.

现在我的任务还没完成.我发现报名功能仅记录plan_id和user_id还不够,我还需要记录报名的时间,报名是否被批准,用户留言等信息.这些信息当然应该记录在报名表中.于是给plan_user表增加了几个字段created_at,confirmed,memo. 但是为了读写这几个字段,我还是得建立一个单独的模型类.不然更新这些信息时比较麻烦.加上这么些报名信息,其实报名表就应该是一个单独的数据实体了,在教程里David也提到:when a Join wants to be a model.说得就是这个意思,当一个多对多关联有很多属性时就应该做为一个model对待.于是我打算建立一个模型类.并且按模型类的约定把表名改成 joins,类名就叫Join (这里的join我是表示报名,而不是关联的意思):
#join.rb
class Join < ActiveRecord::Base
  belongs_to :plan
  belongs_to :user
end
这样我就有很多关于报名的方法可用了!而且将来要增加的批准方法也有地方放了.可是plan和user模型中的声明已经不再符合默认规则了,不要紧,利用一下:join_table选项:
class Plan < ActiveRecord::Base
  belongs_to :user
  has_many :joins
  has_and_belongs_to_many :participants, :class_name => "User", :join_table => "joins"
end
class User < ActiveRecord::Base
  has_many :plans 
  has_many :joins
  has_and_belongs_to_many :plans_participated, :class_name => "lan", :join_table => "joins"
end 
现在我想增加一个方法,得到所有已批准的报名.也就是所有confirmed = true的记录.难道要自己写一个方法,执行一个sql吗?不用.只要修改关联的声明,加一个 :condition => "confirmed = true"即可.但是我还想同时保留访问所有报名的功能怎么办?没问题,既然一个关联只不过是一个方法调用来增加一个属性,那为什么不可以调用多次,增加多个属性?
class Plan < ActiveRecord::Base
  belongs_to :user
  has_many :joins
  has_many :confirmed_joins, :conditions => "confirmed = true"
  has_many :unconfirmed_joins, :conditions => "confirmed = false"
  has_and_belongs_to_many :participants, :class_name => "User", :join_table => "joins"
end
以后的用法举例如下:
用plan.user得到计划的发布者对象.
用plan.participants得到一个计划的所有参与者对象,进而访问参与者的姓名等信息.
用plan.confirmed_joins得到所有已批准的报名对象,进而访问报名时间,留言等信息.

到现在为止,我要的操作都通过关联声明来实现了,没有自己编写一个方法.而且对于关联表join,同时利用了单独模型类的好处和多对多关联的好处.真是鱼和熊掌兼得呀.
最后的结论就是,一个关联实际上就是基类的一个方法,你可以多次调用,任意组合.
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics