最近做的项目有这样一个需求:需要对页面的title,desc,keywods做SEO.根据页面的不同,其内容各、规则和格式都不尽相同,大多数情况下是根据view中的部分实例变量的某些字段按照特定规则态生成的,并且在不同时期,SEO的格式和规则也会有所不同(比如:会在title中加入最近热门的搜索关键字,页面不同搜索关键)。但项目中,所有的页面都共用一个layout,这样就遇到一个问题:怎么取到当前view的实例变量,得到实例变量后需要知道当前view的SEO生成规则,然后生成的SEO插入到当前View中。
脑海里的第一个方案是在ApplicationHelper里加一个方法,并在每个action里生命一个@seo_obj变量,根据@seo的类型来决定怎样生成SEO内容。用这个方案实现的代码丑陋繁杂,判断分支又多,难以维护。代码类似下面:
def seo_title
    case @seo_obj
    when "latest"
      return "latest coupon codes - #{SITE_NAME}"
    when "featured"
      return "featured coupon codes - #{SITE_NAME}"
    when "stores"
      return "coupon code of thousands of stores - #{SITE_NAME}"
    when "tags"
      return "tags coupons - #{SITE_NAME}"
    else
      if @seo_obj.kind_of?(Merchant)
           ....
        if @seo_obj.html_title && !(@seo_obj.html_title.blank?)
          ....
        elsif @seo_obj.printable == YES
          #printable
               ...
        else
                ....
        end
      elsif @seo_obj.kind_of?(Tag)

      else
       ...
      end
    end
  end

   def seo_keywords
    case @seo_obj
       ...
     end
     ...
   end

在同事的启发下,又经过自己的一番探索,决定采用instance_eval去处理。instance_eval可以根据当前对象上下文得到此对象的实例变量以及可以动态地为当前对象生成实例变量的特性刚好可以解决这个需求的问题.
解决方案:
第一步:分为title,keywords,desc创建个第一个hash,key由controller_name.action_name组成,value是SEO的内容,其中动态部分用'?'占位符。例如:
  Seo_Title = {'sodeal.index' => "hot deal 50% of",
    'sodeal.merchant_deals' => "IBM Deal,HP Deal,merchant  ?1, coupon  ?2",
    'index.merchants' => 'hot deals and coupons for many online stores'
    }
   Seo_Desc ={...}
   Seo_Keywords = {...}


第二步:对那些规则和格式相对简单的,比如知只有一个占位符或者不需要占位符的写一个通用的默认方法,而对那些比较复杂的有多个占位符的写专门的方法,方法名按照controller_name_action_name的方式i。比如像index.merchants'的格式就比较简单,像sodeal.merchant_deals'的格式就稍微发杂一点。根据这个原则就得到如下方法:
def index_merchants_seo
    merchant_name = instance_eval {@merchant.merchant_name}
    coupon_name = instance_eval{@coupon.name}
    seo_title = Seo_Title["#{params[:controller]}.#{params[:action]}"]
    seo_title.gsub('?1',merchant_name).gsub('?2',coupon_name)
    set_seo_title seo_title
  end
  
  def default_seo
    set_seo_title Seo_Title["#{params[:controller]}.#{params[:action]}"]
    set_seo_desc Seo_Description["#{params[:controller]}.#{params[:action]}"]
    set_seo_keywords Seo_Keywords["#{params[:controller]}.#{params[:action]}"]
  end
  
  private
  def set_seo_title(title)
    instance_eval { @seo_title = title}
  end
  def set_seo_keywords(keywords)
     instance_eval { @seo_keywords = keywords}
  end
  def set_seo_desc(desc)
    instance_eval { @seo_desc = desc}
  end

第三步:现在该解决怎么调用的问题。第二步定义的SEO方法有两种,特定的和通用的;并且特定方法的方法名是按照controller_name_action_name+'seo'后缀定义的。那么就可以根据respond_to?("#{params[:controller_name]}_#{params[:action]}_seo".to_sym)(注:此modul会mixin到helper里)来判断是否有特定的方法,如果没有就调用方法default_seo,由此思路,便有了下面的方法:
def seo
    if respond_to?("#{params[:controller_name]}_#{params[:action]}_seo".to_sym)
       send("#{params[:action]}_seo") 
    else
      default_seo
    end
  end  


全部代码如下:
module Seo
  
  def self.included(helper)
    
  end
  
  Seo_Title = {'sodeal.index' => "hot deal 50% of",
    'sodeal.merchant_deals' => "IBM Deal,HP Deal,merchant  ?1, coupon  ?2",
    'index.merchants' => 'hot deals and coupons for many online stores'
    }
  
  Seo_Description = {'sodeal.index' => 'index desc'}
  Seo_Keywords = {'sodeal.index' => 'index keywords'}
  
  protected

  #later can add contoller_name here
  def seo
    if respond_to?("#{params[:controller_name]}_#{params[:action]}_seo".to_sym)
       send("#{params[:action]}_seo") 
    else
      default_seo
    end
  end  
   
  def index_merchants_seo
    merchant_name = instance_eval {@merchant.merchant_name}
    coupon_name = instance_eval{@coupon.name}
    seo_title = "24*7 hot deals and coupons for ?1".gsub('?1',merchant_name)
    seo_title = Seo_Title["#{params[:controller]}.#{params[:action]}"].gsub('?1',merchant_name).gsub('?2',coupon_name)
    set_seo_title seo_title
  end
  
  def default_seo
    set_seo_title Seo_Title["#{params[:controller]}.#{params[:action]}"]
    set_seo_desc Seo_Description["#{params[:controller]}.#{params[:action]}"]
    set_seo_keywords Seo_Keywords["#{params[:controller]}.#{params[:action]}"]
  end
  
  private
  def set_seo_title(title)
    instance_eval { @seo_title = title}
  end
  def set_seo_keywords(keywords)
     instance_eval { @seo_keywords = keywords}
  end
  def set_seo_desc(desc)
    instance_eval { @seo_desc = desc}
  end
end

在AppliactionHepler里include Seo.然后在layout里调用方法seo.代码如下:
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/2001/REC-xhtml11-20010531/DTD/xhtml11-flat.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<% seo %>
<title><%= @seo_title %></title>
<meta http-equiv="Content-Language" content="en-gb" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<META content="<%= @seo_desc %>" name="description">
<META content="<%= @seo_keywords %>" name="keywords">

总结:在实际项目中,可以利用ruby的自省和modul_eval,class_eval,evainstance_eval等动态方法,实现业务的横切。把一些和业务没有直接关系的模块从业务代码里剥离出来。
评论
发表评论

您还没有登录,请登录后发表评论

shaquan6776
搜索本博客
最近加入圈子
存档
最新评论