rails中多对多表关联时处理方法总结

一: ER图
 举一个简单的例子,用户对文档的访问权限的管理。
 共三个表,用户 权限 文档。表关系如下。
  users               authorities           documents
 +---------+ 1        +-----------+        1 +---------+
 | id      | ----+    | id        |     +----| id      |
 +---------+     | n  +-----------+     |    +---------+
 | name    |     +--->|user_id    | n   |    |name     |
 +---------+          |document_id| <---+    |location |
                      |authnum    |          +---------+
                      +-----------+
注意:关于主键id
  migration建表时自动生成主键id,可以修改建表的rb文件来指定不生成id。
  如:create_table :tablenames,:id=>false do |t|
  如果不使用默认的id为主键需要自己在model定义中指定主键。
  在多对多的表关联中,如果中间表仅仅连接作用,除外键之外自身并没有多余的信息,则应该把id去掉。
  此例中,中间表 authorities除两个外键(user_id,document_id)以外还有一个字段(authnum),为此为了
  使用ActiveRecord访问此表的数据需要保留id为主键。
  当然如果十分不想以自动生成的id为主键的话,可以删除id,以user_id + document_id 作为复合主键来使用。
  遗憾的是ActiveRecord自身不支持复合主键,需要下载插件(composite_primary_keys)。

※如果权限种类比较容易发生变化,可以建立一个专门的权限种类表。
  在此假设文档访问权限固定,保存在哈希表中(见Authority类代码)。

二:model
各表对应model中添加描述指明关系。
app/models/user.rb

class User < ActiveRecord::Base
  has_many :authorities ,:dependent => :destroy
  has_many :documents,:through => :authorities

 
  #返回该用户对某文档的访问权限
  def get_authnum_by_document_id(did)
    auth = authorities.find_by_document_id(did)
    auth.nil?? nil : auth.authnum
  end
end


指定:dependent => :destroy 时,主表数据被删除时自动删除关联表的数据。
   此例中如果用户被删除了,自然该用户的
文档访问权限记录就没有意义了,所有删除用户时
   自动删除他的权限信息。

app/models/document.rb

class Document < ActiveRecord::Base
  has_many :authorities ,:dependent => :destroy
  has_many :users,:through => :authorities

end


app/models/authority.rb

class Authority < ActiveRecord::Base
  belongs_to :user
  belongs_to :document


  validates_presence_of :document_id
  validates_presence_of :user_id

  AUTH_TYPES = { "执行" => 1,"写入"=> 2,"读取"=> 4, "完全" => 7  ,"无"=> nil }
  validates_inclusion_of :authnum , :in =>
    AUTH_TYPES.map { |disp,value| value  }

  def authtype
    AUTH_TYPES.index(self.authnum )
  end
  def authtype=(atype)
    self.authnum = AUTH_TYPES[atype]
  end
end


※1.Authority 中定义了权限种类的哈希表AUTH_TYPES,以及假想属性authtype
    假想属性authtype用于在页面显示和修改用。然后要将其修改反映到对应的DB属性(authnum)里去。
    定义假想属性的访问方法(authtype,authtype=),使程序中有读写authtype属性时,
    自动更新其对应的DB属性(authnum)。本例中列出的代码里没有对authtype的读写操作。
     
  2.AUTH_TYPES: 以 显示值 => 存取值 的对应关系来定义。
  如果在view中用下拉框来显示权限的话,可写成如下形式:
  <%=h select_tag "authnum" , options_for_select( Authority::AUTH_TYPES ,authority.authnum) %>
  options_for_select 的第一参数指定option集合,第二参数指定当前选中项的值。

三:view
以某文档的权限设定页面为例。
页面中列出各用户对此文档的权限以供修改。
外观:
查看更多精彩图片

代码:
app/views/documents/edit.html.erb


<h4>文档权限设定</h4>
<h1>Editing document</h1>

<% form_for(@document) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :location %><br />
    <%= f.text_field :location %>
  </p>
  <table>
  <tr>
    <th>NO</th>
    <th>用户</th>
    <th>权限</th>
  </tr>
  <% User.all.each_with_index do |user,num| -%>
    <tr>
      <td><%=h num +=1 %></td>
      <td><%=h user.name %></td>
      <%= hidden_field_tag "auths[][user_id]",user.id %>
      <td>
        <%= select_tag "auths[][authnum]" ,  options_for_select( Authority::AUTH_TYPES,
                                               user.get_authnum_by_document_id(@document.id))%>
      </td>

    </tr>
  <% end -%>

</table>
  <p>
    <%= f.submit 'Update' %>
  </p>
<% end %>

<%= link_to 'Show', @document %> |
<%= link_to 'Back', documents_path %>


※因为提交的权限记录是多条的,这里用了数组auths[] 。
※点击update时提交的数据如下:
Parameters: {"auths"=>[{"user_id"=>"1", "authnum"=>"2"}, {"user_id"=>"2", "authnum"=>"4"},
                       {"user_id"=>"3", "authnum"=>""},  {"user_id"=>"4", "authnum"=>"7"}],
       "commit"=>"Update", "authenticity_token"=>"V3H9uJ0tBorKh59sSMHUS5DqQ06EaR9Ze4Kjm5Bzhwo=",
        id"=>"1", "document"=>{"name"=>"test", "location"=>"/home/test"}}


四:controller/action
下面是documents_controller中的update action示例代码。
app/controllers/documents_controller.rb

  def update
    @document = Document.find(params[:id])
    respond_to do |format|
      begin
        ActiveRecord::Base.transaction do
          #更新Document表
          @document.update_attributes!(params[:document])
          #更新当前文档相关的authorities表数据
          authorities = Array.new
          params[:auths].each{ |auth|
            authorities << @document.authorities.new(auth) unless auth[:authnum].empty?
          }
          @document.authorities.replace(authorities)
        end
        flash[:notice] = '文档更新完毕.'
        format.html { redirect_to(@document) }
        format.xml  { head :ok }
      rescue Exception => e
        flash.now[:notice] = '更新失败:'+ e
        format.html { render :action => "edit" }
        format.xml  { render :xml => @document.errors, :status => :unprocessable_entity }
      end
    end
  end


※1.这里同时更新多个表的数据,为了保证document表和authorities表都更新成功这里使用了transaction,
    如果某次更新失败这个transaction中的全部更新操作都会被rollback.
  2.使用带!的方法。
    ActiveRecord的一些更新方法分为带两种,带!的还不带的。。
    如save ,save!, update_attributes ,update_attributes!
   带!的方法在出错时抛出异常。所以在transaction中为了捕捉异常尽量使用带!的方法。
   否则只能根据方法的返回值或其他方法来能判断更新处理是否正常。
   要注意的是,update_attribute方法只有一个版本,没有带!版,所以即使只对一个属性进行更新
   也尽量使用update_attribute!方法。

  3.model对象之间的访问方法
    model定义中实现了多对多的对应,所以访问表记录的关联数据时比较灵活,参考下面。
    user.authorities     (has_many :authorities)
    user.documents       (has_many :documents)
    document.authorities (has_many :authorities)
    document.users       (has_many :users)
    authority.user        (belongs_to :user)
    authrity.document     (belongs_to :document)

由于has_many的声明会自动追加一些对关联表对象访问的方法,比如本例中
由于Document类中的has_many :authorities 声明,会自动产生一些方法,下面列一些常用的,
详细可参照rails文档。
document.authorities.create(attribute,...)  新建管理记录
document.authorities << authority           追加关联记录
document.authorities.push(authority1,.....) 追加关联记录(多条)
document.authorities.replace(authorities)   替换(将现有关联数据集替换成参数中指定的数据集)
                                                (※需要插入、删除、更新的各种情况会自动判断)

document.authorities.delete(authority1,.....)  关联记录删除(多条)
document.authorities.delete_all                关联记录删除(全部)
document.authorities.destroy_all               关联记录删除(全部)
document.authorities.clear                     关系切断(全部)(如不指定dependent仅将关联表数据的外键变为nil)
document.authorities.find(options...)          从关联数据中进行查询
document.authorities.count(options...)         关联数据条数

  4.SQL语句执行方法
    进行复杂处理时可能需要写SQL语句,updateSQL句写法:ActiveRecord::Base.connection.update(updatesql)
    比如本例中 auth.update_attributes!({:authnum => authnums[i]}) 用SQL语句可以写成

    updatesql= "update authorities set authnum= #{authnums[i]} ,updated_at = '#{Time.now}'" +
                      "where user_id=#{uid} and document_id = #{@document.id}"
    ActiveRecord::Base.connection.update(updatesql)
    ※另外,下面的写法也可以
    Authority.update_all("authid = '#{authnums[i]}'", "user_id = #{uid} and document_id = #{@document.id}")
    ※查询语句 User.find_by_sql("select * from users where name = 'li' ")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值