一个带完整的RBAC授权系统的rails应用(第二部分)

这里先给出我们的rails应用的最终形态!(图片都是比较大,请下载回来仔细看!)

第一部分已经大体完成了授权系统,但即使这样项目依然离我们心目中的微缩版维基差很远,我们必须增加更多的模块,完善其功能。

新建Lamme模块,其中point属性是用来奖励的(积分系统)

 
ruby script/generate scaffold Lemma  title:string body:text point:integer

打开迁移任务,默认每个词条的奖励分是5。

 
class CreateLemmas < ActiveRecord::Migration
  def self.up
    create_table :lemmas do |t|
      t.string :title
      t.text :body
      t.integer :point,:default => 5

      t.timestamps
    end
  end

  def self.down
    drop_table :lemmas
  end
end

新建迁移任务,继续完成积分系统。

 
ruby script/generate migration AddPointToUser point:integer

修改迁移任务,每个维客的初始积分为0。

 
class AddPointToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :point, :integer,:default => 0
  end

  def self.down
    remove_column :users, :point
  end
end

新建Coauthor模块,它是维基的共笔系统的基础。从数据库角度来看,它是作为User与Lamme的中间表的存在,User与Lamme通过它成多对多关系。也就是,一个词条可以有许多作者,一个作者能编写许多词条。在rails中实现多对多关系有两种方式has_and_belongs_to_many与has_many :through,后者更为强大,拥有给连接表使用的模型类与更多属性,命名也更加灵活直观准确。

 
ruby script/generate scaffold Coauthor user:belongs_to lemma:belongs_to activion:boolean

属性activion默认是false,它是我们为维客添加积分的一个判断标准。

 
class CreateCoauthors < ActiveRecord::Migration
  def self.up
    create_table :coauthors do |t|
      t.belongs_to :user
      t.belongs_to :lemma
      t.boolean :activion,:default => false

      t.timestamps
    end
  end

  def self.down
    drop_table :coauthors
  end
end

我们先修改路由规则

 
ActionController::Routing::Routes.draw do |map|

  map.resources :lemmas do |lemma|
    lemma.resources :coauthors
  end

  map.logout '/logout', :controller => 'sessions', :action => 'destroy'
  map.login '/login', :controller => 'sessions', :action => 'new'
  map.register '/register', :controller => 'users', :action => 'create'
  map.signup '/signup', :controller => 'users', :action => 'new'
  map.resources :users
  map.resource :session
  map.root :lemmas
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end

修改Coauthor模型,添加一个方法。

 
class Coauthor < ActiveRecord::Base
  belongs_to :user
  belongs_to :lemma
  validates_presence_of :user, :lemma
  validates_associated :user, :lemma
  def active?
    activion
  end
end

修改Lemma模型,完善多对多关系,并添加两个辅助方法,用来判断当前用户是否该词条的共同创作者。

 
class Lemma < ActiveRecord::Base
  has_many :coauthors
  has_many :users ,:through => :coauthors
  
  def contains?(user)
    not contains(user).nil?
  end

  def contains(user)
    coauthors.find_by_user_id(user)
  end
end

为User模型添加以下代码,完成多对多关系。

 
has_many :coauthors
has_many :lemmas ,:through => :coauthors

修改lemmas_controller,完成积分系统。

 
class LemmasController < ApplicationController
  before_filter :load_lemma, :only => [:show, :edit, :update, :destroy]
  before_filter :new_lemma, :only => :new

  def index
    @lemmas = Lemma.all
  end

  def show;end

  def new;end

  def edit;end
  
  def create
    @lemma = Lemma.new params[:lemma]
    if @lemma.save
      #为词条的创建者添加积分。
      coauthor =  Coauthor.create!(:lemma => @lemma,:user => current_user,:activion => true)
      coauthor.user.increment!(:point,@lemma.point) if coauthor.active?
      flash[:notice] = '创建词条成功!'
      redirect_to @lemma
    else
      render :action => "new"
    end
  end

  def update
    if @lemma.update_attributes(params[:lemma])
      coauthor = Coauthor.find_by_user_id_and_lemma_id current_user.id,@lemma.id
      #只为后来的编辑者添加积分。
      #activation 为 true,表示在这词条上,该作者已被奖励过了!
      coauthor.user.increment!(:point,@lemma.point) unless coauthor.active?
      coauthor.update_attribute(:activion,true) unless coauthor.active?
      flash[:notice] = '更新词条成功!'
      redirect_to @lemma
    else
      render :action => "edit"
    end
  end

  def destroy
    @lemma.destroy
    flash[:notice] = '删除词条成功!'
    redirect_to lemmas_url
  end

  protected
  def load_lemma
    @lemma = Lemma.find params[:id]
  end

  def new_lemma
    @lemma  = Lemma.new
  end
end

现在lemmas#index是网站的首页,由于还缺乏足够的材料,因此我们还是不要动它。打开lemmas#show,让我们添加链接,让后来的维客也可以申请成为该词条的作者。

 
<h3><%=h @lemma.title %></h3>

<%=simple_format @lemma.body %>

<% unless @lemma.contains?(current_user) %>
  <% form_for [@lemma,Coauthor.new] do |f| %>
    <%= f.submit "申请成为此词条的共同创作者" %>
  <% end %>
<% end %>
<hr />
<p>
  <b>共同创作者:</b><br/>
  <% @lemma.coauthors.each do |coauthor| %>
    <%= coauthor.user.login if coauthor.active? %>
  <% end %>
</p>
<% if @lemma.contains?(current_user) %>
  <%= link_to '编辑该词条', [:edit,@lemma] %>
<% end %>
<%= link_to '回到首页', lemmas_path %>

删除Coauthor的所有视图,我们不需要用到它们。修改coauthors_controller,删除多余action。

 
class CoauthorsController < ApplicationController
  before_filter :load_coauthor
  before_filter :new_coauthor_from_params, :only => :create
  filter_access_to :all, :attribute_check => true
  def create
    if @coauthor.save
      flash[:notice] = '成功加入该词条的共同创作者!'
      redirect_to @lemma
    else
      flash[:notice] = '试图加入该词条的共同创作者失败!'
      redirect_to @lemma
    end
  end

  def destroy
    @coauthor = Coauthor.find(params[:id])
    @coauthor.destroy
    flash[:notice] = "成功退出该词条的共同创作者!"
    redirect_to @lemma
  end

  protected
  def load_coauthor
    @lemma = Lemma.find(params[:lemma_id])
  end

  def new_coauthor_from_params
    @coauthor = @lemma.coauthors.new(:user => current_user)
  end
end

修改lemmas#new,让我们创建一个词条看看(注意删除app/views下的多余全局模板)。

 
<% title "新建词条" %>
<fieldset>
  <% form_for(@lemma) do |f| %>
    <%= f.error_messages %>

    <p>
      <%= f.label :title,"标题" %><br />
      <%= f.text_field :title %>
    </p>
    <p>
      <%= f.label :body,"正文" %><br />
      <%= f.text_area :body %>
    </p>
    <p>
      <%= f.submit '创建' %>
    </p>
  <% end %>
</fieldset>
<%= link_to '返回首页', url_for(:back) %>

新建词条

21lemmas#show

用另一个帐号登录,加入成为共同作者,就可以编辑别人创建的词条。

22lemmas#show

由于现在的模块还是比较少,我们稍后再对它们进行授权控制。在第一部分中,你们也看到利用declarative authorization插件实现授权控制是何等轻松的,所以不用着急。

现在我们将实现标签系统。随着词条的增加,我们很有必要对词条进行分类。标签在web1.0时代可能只是网页的装饰,内容的摆设,技术的鸡肋。但在web2.0时代,标签将是整个网站内容关联体系最重要的一环。

标签web2.0网站中的作用如下:

  1. 内容与用户的交互:用户可以让内容赋予自己的个性属性,通过加标签的操作,让内容变得可循环,可梳理。大量的用户-标签的交互会产生化学反应,使整个网站的内容形成一个环状,把每个内容孤岛都可以串连起来。
  2. 内容的关联:通过人工的标注,内容本身带有了过滤后的符号意义,可以作为一个关键字来关联,也可以作为用户群的共性来关联,更可以两者结合,把用户-标签-用户的整个系统循环利用起来。
  3. 标签的展示:标签本身作为一个浓缩的符号,具有丰富的代表意义和广泛的影响力,通过标签群的展示,让用户找到目标流量,让网站流量的输入和导出变得更加紧凑,丰富而有节奏。
  4. 标签的使命:在web2.0架构中,特别是针对UGC的网站,标签不仅是内容的,还是用户的。标签站在用户和内容之间,它可以产生各种各样的功能和后台关联。它的使命就是把内容和用户连起来,而怎么个连法,需要产品设计者充分的创意。

标签在web2.0网站最伟大的应用就是标签云(Tag Cloud),也是我们标签系统的重点。看起来很美,但也很复杂,但由于是在rails上实现,一切都变得很简单,一个插件足矣!

安装插件

 
ruby script/plugin install git://github.com/jviney/acts_as_taggable_on_steroids.git
ruby script/generate acts_as_taggable_migration
rake db:migrate

我们这个微缩版维基能应用标签的地方不多,也只有Lemma这个模块。如果是大型应用,你尽可以在贴子、新闻、图片、附件、博客等等都贴上标签……

 
class Lemma < ActiveRecord::Base
  acts_as_taggable
  has_many :coauthors
  has_many :users ,:through => :coauthors
  
  def contains?(user)
    not contains(user).nil?
  end

  def contains(user)
    coauthors.find_by_user_id(user)
  end
end

这样它就为Lemma添加一系列方法,常用的有:

实例方法tag_list,返回该对象的所有标签的名字的数组,它的to_s经过改写,默认是返回用英文逗号分开的字符串。

实例方法tag_counts,返回该对象的所有标签对象的数组(很囧的命名,我还以为是返回一个数字。)

类方法find_tagged_with,返回的是被标签模型的对象数组,具体用法见下面例子,

 
Post.find_tagged_with("web-development, tutorial", :match_all => true)

类方法tag_counts,这个也是实例方法,不过这次是返回这个模块所关联的所有标签对象的数组。

此外,要使用tag_cloud这个帮助方法,必须在application_help这个全局帮助模块中包含TagsHelper。不过,github给出的那个标签云的例子功能太弱,没有字体与颜色的变化,基本无法突出它们的热门程度,仅仅是超链接的堆砌,不要也罢。我一会儿提供一个功能更强大的同名方法来实现标签云。

更详细的内容可以翻看其源码,那么让我们开始吧。

我们打算在lemmas#show视图中列出该词条的所有标签,因此得修改lemmas#show action。

 
def show
     @tags = Lemma.tag_counts
end

我们还想在视图中增加两个链接,用来动态增加与删除标签,这得在控制器添加两个action——add_tag与remove_tag。

此外,当我们点击该词条的标签云的集合时,我们希望该链接将带我们到拥有同一个标签的词条列表中,从而使所有词条有机地联结在一起。这就又要增加一个action与视图了。修改后的控制器为:

 
class LemmasController < ApplicationController
  before_filter :load_lemma, :only => [:show, :edit, :update, :add_tag,:remove_tag,:destroy]
  before_filter :new_lemma, :only => :new
 # filter_access_to :all
 # filter_access_to [:new,:create, :edit, :update], :attribute_check => true

  def tag
    @lemmas = Lemma.find_tagged_with params[:id]
  end

  def show
    @tags = @lemma.tag_counts
  end


  def add_tag
    @lemma.tag_list.add params[:tag]
    @lemma.save_tags
    id = dom_id(@lemma) + "_tags"
    render :update do |page|
      page.replace_html id, tag_cloud(@lemma.tag_counts)
      page << %{
         new Effect.Highlight('#{id}',{startcolor:'#80FF00',duration: 3.0 });
      }
    end
  end

  def remove_tag
    @lemma.tag_list.remove params[:tag]
    @lemma.save_tags
    id = dom_id(@lemma) + "_tags"
    render :update do |page|
      page.replace_html id, tag_cloud(@lemma.tag_counts)
      page << %{
         new Effect.Highlight('#{id}',{startcolor:'#80FF00',duration: 3.0 });
      }
    end
  end

#……………………………………

end

注意:现在先关闭lemmas的授权控制,否则无法访问。

为了,提高性能,我们通常还要用tag caching,也就是在被标签的模型的表添加一个字段cache_tag_list,那么当我们查询标签时就不再找tags表的麻烦了,直接问cache_tag_list要!

 
script/generate migration AddCacheTagListToLemma cache_tag_list:string
rake db:migrate

但有利必有弊,这样我们更新删除标签时,rails都直接与lemmas与taggings打交道,对tags表不闻不问,而tag表对于tag_counts(无论是实例方法还是类方法)都非常重要,tag_counts返回的对象数组拥有一个 count属性,它是统计某个标签在模型中出现的次数(热门程度的别名)。而save_cached_tag_list只对cache_tag_list处理,连save也不如(当我们删除某标签后,save会修改cache_tag_list里的字段,并删除taggings里tag_id为我们删除了的标签的ID的记录),这时只有让save_tags出马了,它会同时修正这三个表!

修改lemmas#show视图。

 
<h3><%=h @lemma.title %></h3>

<%=simple_format @lemma.body %>

<% unless @lemma.contains?(current_user) %>
  <% form_for [@lemma,Coauthor.new] do |f| %>
    <%= f.submit "申请成为此词条的共同创作者" %>
  <% end %>
<% end %>
<hr />
<p>
  <span style="font-weight: bold">共同创作者:</span><br/>
  <% @lemma.coauthors.each do |coauthor| %>
    <%= coauthor.user.login if coauthor.active? %>
  <% end %>
</p>
<%= javascript_include_tag :defaults %> 
<div>
  <p style="font-weight: bold">开放分类:</p>
  <p>
    <span id="<%= dom_id @lemma %>_tags"><%= tag_cloud @tags %></span>
    <%= link_to_remote "添加标签", :url => add_tag_lemma_url(@lemma),
      :method => 'put',
      :before => "$tag = prompt('输入要添加的标签,多个标签请用英文逗号隔开!')",
      :with => "'tag=' + $tag",
      :html => {:class => "adjust_tag"}
  %>
    <%= link_to_remote "删除标签", :url => remove_tag_lemma_url(@lemma),
      :method => 'delete',
      :before => "$tag = prompt('输入要删除的标签,多个标签请用英文逗号隔开!')",
      :with => "'tag=' + $tag",
      :html => {:class => "adjust_tag"}
  %>
  </p>
</div>

<% if @lemma.contains?(current_user) %>
  <%= link_to '编辑该词条', [:edit,@lemma] %>
<% end %>
<%= link_to '回到首页', lemmas_path %>

添加lemmas#tag视图。

 
<% @lemmas.each do |lemma| %>
  <h3><%= lemma.title %></h3>
  <%= truncate lemma.body ,:length => 300 %>
<% end %>
<p>
  <%= link_to "返回",url_for(:back) %>|<%= link_to "回到首页",root_url %>
</p>

修改路由规则,保证我们上面的链接生效。

 
ActionController::Routing::Routes.draw do |map|

  map.resources :lemmas,:member => {:add_tag => :put,:remove_tag =>:delete},:collection => {:tag => :get}  do |lemma|
    lemma.resources :coauthors
  end

 #……………………
end

最后是tag_cloud帮助方法

现在是时候为项目加上分页功能了,安装will_paginate

 
git clone git://github.com/mislav/will_paginate.git vendor/plugins/will_paginate

修改lemmas#tag action

 
  def tag
    options = Lemma.find_options_for_find_tagged_with(params[:id],\
      :order => "updated_at DESC").merge(:page => params[:page] ||1,:per_page =>3 )
    @lemmas = Lemma.paginate(options)
  end

修改对应视图

 
<% @lemmas.each do |lemma| %>
  <%= link_to lemma.title,lemma,:class => "lemma_title",:hidefocus=>"true" %>
  <%= truncate simple_format(lemma.body) ,:length => 250,:omission => "……#{link_to '全文',lemma}"%>
<% end %>
<%= will_paginate @lemmas %>
<p>
  <%= link_to "返回",url_for(:back) %>|<%= link_to "回到首页",root_url %>
</p>

好了,接着下来我们打算统计一下每个词条的点击率,既然有热门标签(通过Tag.count属性),当然有人气词条。

 
ruby script/generate migration AddHitsToLemma hits:integer

修改迁移任务

 
class AddHitsToLemma < ActiveRecord::Migration
  def self.up
    add_column :lemmas, :hits, :integer,:default => 0
  end

  def self.down
    remove_column :lemmas, :hits
  end
end

执行!

 
rake db:migrate

我们在lemmas#show action中进行统计,并且不得本人刷屏作弊!首先我们在模型中添加一个方法

 
  def hit!
    self.class.increment_counter :hits, id
  end

然后修改action

 
  def show
    @lemma.hit! unless logged_in? && @lemma.users.include?(current_user)
    @tags = @lemma.tag_counts
  end

如果我们翻看百度百科,就会发现一个叫“相关词条”的链接。它是基于搜索引擎实现,计算两个词条中被搜索的关键字群的重合程度。很显然,我们单用JOIN与LIKE去实现是非常不明智的,但acts_as_taggable_on_steroids提供了一个find_related_tags方法,返回关系紧密的标签,我们可以用它来搞个相关标签。可能还有些人对关系紧密的标签糊里糊涂,我举个例子。比如,我们有三个词条:a9vg,levelup,tgfc。a9vg有4个标签:游戏,神机,高贵饭,下限。levelup有三个标签:游戏,小白,下限。tgfc有5个标签:游戏,下限,小白,脑残,虚拟内存。我们就可以说游戏与下限的关系非常紧密,总是一起出现,如果放宽一点,发现游戏与小白也经常搭配在一起……这有点找近义词的意味。如果我们最近注册一些邮箱,它可能要你填写你的兴趣或职业,然后自作聪明发一些与这些字眼搭边的垃圾邮件过来……嘛,扯远了,让我们完成这功能吧。>

我们先搞出一个帮助方法,用来计算这个词条的一些标签哪一个最热门。

 
module LemmasHelper
  
  def hot_tag tags
    a = 0
    result = nil
    tags.each do |tag|
      if a < tag.count
        a = tag.count
        result = tag
      end
    end
    return result
  end

#……………………
end

>最找出这个热门标签通常和哪些标签经常一起出现,选出排名最前的五个。

 
def show  
    @lemma.hit! unless logged_in? && @lemma.users.include?(current_user)
    @tags = @lemma.tag_counts
    tag = hot_tag @tags
    @related_tags =  Lemma.find_related_tags tag.name,:limit => 5
  end

当然这样是无法运行,之前我们的tag_cloud是放在inline RJS里面,怎么说还是视图的范畴,但我们这个很明显是控制器的东西,因此我们得想个办法。其实 也不太难、include它就是!

 
 class ApplicationController < ActionController::Base
   include LemmasHelper

  #………………
 end

视图中添加

 
<div style="border-bottom:1px solid #66EC20">
  <span style="font-weight: bold">你可以感兴趣的标签:</span><br/>
  <% @related_tags.each do |tag| %>
    <%= link_to tag.name, { :action => :tag, :id => tag.name },\
      :style => tag_style(@related_tags,tag.name) %>
  <% end %>
</div>

看到这些多标签,我想是否要做些SEO优化呢。现在它们都是放在普通的链接中,威力太小,放在keywords的meta标签中效果应该会增加两三倍。同样,我们先做帮助方法,不宜在视图中直接写逻辑。

 
module LayoutHelper
  #……………………

  def keywords *args
    content_for(:keywords){ args }
  end

  def description *args
    content_for(:description){ args }
  end

end

在lemmas#show 视图中添加

 
<% keywords h(@lemma.tag_list.to_s)  %>
<% description h(@lemma.title + "——"+ truncate(@lemma.body ,:length => 120)) %>

然后在全局模板的头部添加对应代码。

 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <meta name="keywords" content="<%= yield(:keywords) %>" />
    <meta name="description" content="<%= yield(:description) %>" />
    <title><%= h(yield(:title) || controller.action_name ) %></title>
#……………………
#……………………

翻看网页的源码,发现成功了!

但我们还是觉得在视图上写得太多逻辑了,再对它们做些封装。

 
module LemmasHelper
 def seo_meta lemma
    unless lemma.blank?
      keywords h(lemma.tag_list.to_s)
      description h(lemma.title + "——"+ truncate(lemma.body ,:length => 120))
    end
  end
#………………
end

这样在视图中就可以很简洁搞定了!

 
<% seo_meta @lemma %>

现在我们动一动首页吧,那里还是一片处女地。通常首页上查询非常多,而且很多排行,我们的应用也是如此。我们得在模型中添加一些方法,以达到Skinny Controller, Fat Model的功效。

在User模型中添加

 
named_scope :worthy,:limit => 10,:select => "id,login,point",:order =>"point DESC"

在Lemma模型中添加

 
 named_scope :reward, :select => "id,title,point",:limit=> 10,:order =>"point DESC"
 named_scope :latest, :select => "id,title,updated_at",:limit=> 10,:order =>"updated_at DESC"
 named_scope :hot, :select => "id,title,hits",:limit=> 10,:order =>"hits DESC"

修改首页

 
<% title "我的维基" %>
<table class="columns" >
  <tr>
    <td>
      <h3>最新词条</h3>
      <ul>
        <% @latest_lemmas.each do |lemma| %>
          <li><%= link_to lemma.title,lemma %></li>
        <% end %>
      </ul>
    </td>
    <td>
      <h3>热门词条</h3>
      <ul>
        <% @hot_lemmas.each do |lemma| %>
          <li><%= link_to lemma.title+ "(点击数:#{lemma.hits})",lemma %></li>
        <% end %>
      </ul>
    </td>
  </tr>
  <tr>
    <td>
      <h3>高分词条</h3>
      <ul>
        <% @reward_lemmas.each do |lemma| %>
          <li><%= link_to lemma.title+ "(悬赏分:#{lemma.point})" ,lemma %></li>
        <% end %>
      </ul>
    </td>
    <td>
      <h3>贡献榜</h3>
      <ul>
        <% @worthy_user.each do |wikier| %>
          <li><%= link_to wikier.login+ "(悬赏分:#{wikier.point})" ,wikier %></li>
        <% end %>
      </ul>
    </td>
  </tr>
</table>
<hr />

<%= link_to '新建词条', new_lemma_path %>
<div>
  <%= tag_cloud @tags%>
</div>

好了,网站的功能基本就完成了。我们可以专注搞lemma模块的授权控制了。修改授权规则。

 
authorization do
  role :guest do
    has_permission_on :users, :to => [:read_index,:create]
    has_permission_on :lemmas, :to => :read
  end

  role :wikier do
    includes :guest
    has_permission_on :users, :to => [:read_show,:update] do
      if_attribute :id => is {user.id}
    end
    has_permission_on  :coauthors, :to => :create
    has_permission_on  :lemmas, :to => [:create,:add_tags,:remove_tags]
    has_permission_on  :lemmas, :to => :update do
      if_attribute :users => contains {user}
    end   
  end

  role :peace_maker do
    includes :wikier
    has_permission_on :users, :to => :read_show
    has_permission_on :lemmas, :to => :update
  end

  role :providence_breaker do
    has_permission_on [:users,:lemmas,:coauthors], :to =>:manage
  end
end

privileges do
  privilege :manage, :includes => [:create, :read, :update, :delete,:add_tags,:remove_tags]
  privilege :read, :includes => [:index, :show, :tag]
  privilege :read_index, :includes => :index
  privilege :read_show, :includes => :show
  privilege :create, :includes => :new
  privilege :add_tags,:includes => :add_tag
  privilege :remove_tags,:includes => :remove_tag
  privilege :update, :includes => :edit
  privilege :delete, :includes => :destroy
end

这个有点像视图中的层叠样式表(CSS),能继承能覆盖。

和其他授权插件一样,在视图中都是对链接下手。

 
<% title "我的维基" %>
<table class="columns" >
  <tr>
    <td>
      <h3>♠最新词条</h3>
      <ul class="poker">
        <% @latest_lemmas.each do |lemma| %>
          <li><%= link_to lemma.title,lemma,:title => "创建于:#{lemma.created_at.to_s(:db)}" %></li>
        <% end %>
      </ul>
    </td>
    <td>
      <h3>♣热门词条</h3>
      <ul class="poker">
        <% @hot_lemmas.each do |lemma| %>
          <li><%= link_to lemma.title,lemma,:title => "点击数:#{lemma.hits}" %></li>
        <% end %>
      </ul>
    </td>
  </tr>
  <tr>
    <td>
      <h3>♥待完善词条</h3>
      <ul class="poker">
        <% @reward_lemmas.each do |lemma| %>
          <li><%= link_to lemma.title ,lemma,:title => "悬赏分:#{lemma.point}"  %></li>
        <% end %>
      </ul>
    </td>
    <td>
      <h3>♦贡献排行榜</h3>
      <ul class="poker">
        <% @worthy_user.each do |wikier| %>
          <li>
            <span title="得分:<%= wikier.point %>">
              <%=link_to_if((permitted_to? :read_show, wikier ), wikier.login,wikier,:class => "myself") %>
            </span>
          </li>
        <% end %>
      </ul>
    </td>
  </tr>
</table>
<div>
  <%= tag_cloud @tags %>
</div>
<hr />
<%= link_to '新建词条', new_lemma_path if permitted_to? :create, :lemmas %>
<% keywords @tags.to_sentence  %>
<% description "Ruby's Louvre, rails版的微型维基" %>

修改lemmas#show。

 
#……………………
<% if !@lemma.contains?(current_user) && permitted_to?(:create,Coauthor.new) %>
  <% form_for [@lemma,Coauthor.new] do |f| %>
    <%= f.submit "申请成为此词条的共同创作者" %>
  <% end %>
<% end %>
#………………………………
<%= link_to_remote "添加标签", :url => add_tag_lemma_url(@lemma),
      :method => 'put',
      :before => "$tag = prompt('输入要添加的标签,多个标签请用中文逗号隔开!')",
      :with => "'tag=' + $tag",
      :html => {:class => "adjust_tag"} \
      if permitted_to? :add_tags,:lemmas
    %>
      <%= link_to_remote "删除标签", :url => remove_tag_lemma_url(@lemma),
        :method => 'delete',
        :before => "$tag = prompt('输入要删除的标签,多个标签请用中文逗号隔开!')",
        :with => "'tag=' + $tag",
        :html => {:class => "adjust_tag"} \
        if permitted_to? :remove_tags,:lemmas
      %>
#…………………………………………
   <%# 注释掉不要 if @lemma.contains?(current_user) %>
      <%= link_to '编辑该词条', [:edit,@lemma] if permitted_to?:update,@lemma %>
    <%# end %>
#……………………………………………………

这里讲一些细节(请对照授权规则),对于那些自定义actions(即非restful actions),它们只能一个action对应一个特权,如:

 
privilege :add_tags,:includes => :add_tag
privilege :remove_tags,:includes => :remove_tag  

不能够像其他restful 特权那样对应两个或多个action。下面这个是错误的:

 
privilege :abjust_tag,:includes => [:add_tag,:remove_tag]  

另,这些自定义特权向上组合(即与其他特权组成一个范围更广的特权),除了取名为create,read,update,delete,manage,否则统统无效!下面代码的最后一行是错误的。

 
privilege :add_tags,:includes => :add_tag
privilege :remove_tags,:includes => :remove_tag
privilege :inoperative,:includes => [:add_tags,:remove_tags]

再次,如果这些链接的授权访问,如果不用进行属性检查,我们直接permitted_to? :add_tags,:lemmas就可以了,表示对Lamme这种资源,而不特定到某个个体。我们在第一部分提过了,这叫做粗颗粒的(授权)访问控制。像要编辑词条特定到某个具体的对象,就要用细颗粒的访问控制。

最后一个,为了不用在输入中文标签的时候切换英文逗号,我们修改了其间隔号,在environment.rb的最下边添加:

 
TagList.delimiter = ","

接着是new与edit视图了,为了Don't Repeat Yourself!我们添加一个_form.html.erb。

 
<% form_for(@lemma) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :title,"标题" %><br />
    <%= f.text_field :title %>
  </p>
  <p>
    <%= f.label :body,"正文" %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.label :tag_list,"标签(用中文逗号隔开)" %><br />
    <%= f.text_area :tag_list,:rows=> 2 %>
  </p>
  <% role = Authorization.current_user.role_symbols.to_s  %>
  <% if action_name == "edit" &&  @lemma.contains?(current_user) && (role == "peace_maker" || role == "providence_breaker") %>
    <p>
      <%= f.label :point,"修改悬赏分" %><br />
      <%= f.text_field :point %>
    </p>
  <% end %>
  <p>
    <%= f.submit button_name %>
  </p>
<% end %>

好了,现在修改new视图:

 
<% title "新建词条" %>
<fieldset>
  <%=render :partial => "lemmas/form",:locals => {:button_name => "创建"}   %>
</fieldset>
<%= link_to '返回首页', url_for(:back) %>

修改update视图:

<% title "修改词条" %> <fieldset> <%=render :partial => "lemmas/form",:locals => {:button_name => "更新"} %> </fieldset> <%= link_to '返回', url_for(:back) %> | <%= link_to '回到首页', root_path %>

修改tag视图,增加SEO支持

 
<% content = [] %>
<% @lemmas.each do |lemma| %>
  <% content << lemma.title  %>
  <%= link_to lemma.title,lemma,:class => "lemma_title",:hidefocus=>"true" %>
  <%= truncate simple_format(lemma.body) ,:length => 250,:omission => "……#{link_to '全文',lemma}"%>
<% end %>
<%= will_paginate @lemmas %>
<p>
  <%= link_to "返回",url_for(:back) %>|<%= link_to "回到首页",root_url %>
</p>
<% keywords @tag %>
<% description content.to_sentence %>

修改_user_bar.html.erb

 
#………………………………
    <%= link_to "用户中心",current_user if permitted_to? :read_show, current_user %>
#………………………………

修改users#edit视图,让只有权限最高的人才能修改别人的角色

 
#………………………… 
   <% if Authorization.current_user.role_symbols.to_s  == "providence_breaker" %>
      

<%= f.label :roles,"角色" %>
<%= f.select :roles, [['维客','wikier'],['秩序守护者','peace_maker'], ['违逆天意之人','providence_breaker']], {:include_blank=>"请选择",:selected => 0} %>

<% end %> #………………………………

最后让我们见识一下此插件引以为荣的图形化界面吧。添加授权规则。

 
 role :providence_breaker do
    has_permission_on [:users,:lemmas,:coauthors], :to =>:manage
    has_permission_on :authorization_rules, :to => :manage
    has_permission_on :authorization_usages, :to => :manage
  end

_user_bar.html.erb增加链接

 
 <%= link_to "查看授权情况",authorization_usages_url if permitted_to? :manage,:authorization_usages   %>

不过BUG太多了,每个游览器都不一样,IE死得最惨!或许是rails升级太快,declarative authorization插件跟不上吧。等插件作者更新吧!

这是插件作者 Steffen Bartsch 给出的效果图

原文地址:https://www.cnblogs.com/rubylouvre/p/1511559.html