一: 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
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
has_many :authorities ,:dependent => :destroy
has_many :users,:through => :authorities
end
app/models/authority.rb
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
@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语句可以写成
"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' ")