新手问题 如何将两个 Model 里面的类似的两个 callback 函数提出来复用?

alphaliu · 2013年09月01日 · 最后由 AlphaLiu 回复于 2013年09月02日 · 2269 次阅读

比如我有两个model,里面都有一个叫做generate的方法,是before_save的callback,但这两个generate的方法里面的内容只是类似,如下

#model1的generate
before_save :generate
def generate
  self.slug = self.title.gsub(' ', '-').downcase
end
#model2的generate
before_save :generate
def generate
  self.slug = self.name.gsub(' ', '-').downcase
end

现在我想把这两个方法弄成一个module,问题是我想要还是能够用before_save来调用,但方法里面的内容又不大一样,不知道要怎么做才好,请教一下各位

共收到 14 条回复

ActiveSupport::Concern

#1楼 @aptx4869 不是很明白这个要如何使用

module Slug
  module ClassMethods
    def generate_slug field
      before_save generate_slug_field(field)
    end
  end

  module InstanceMethods
    def generate_slug_field field
      self.slug = self.send(field).gsub(' ', '-').downcase
    end
  end

  def self.included(receiver)
    receiver.extend         ClassMethods
    receiver.send :include, InstanceMethods
  end
end

试试行不行,随手写的

#3楼 @kingwkb 因为before_save这类callback好像是不能带参数的,所以我才来提问的,不过我还是试试看吧。你的例子里面,这个参数要在哪里传进去呢???

before_save 也可以是个 block

before_save { |r|  r.slug = r.generate_slug_field(field) }

#5楼 @kingwkb 谢谢,我了解了。

class UtilObserver < ActiveRecord::Observer
  observe :model1, :model2

  def before_save(record)
    record.slug = record.slug_format
  end
end

module Model1
  def slug_format
    self.title.gsub(' ', '-').downcase
  end
end

module Model2
  def slug_format
    self.name.gsub(' ', '-').downcase
  end
end

你的场景非常类似 Ruby China 的 Markdown 处理动作,可以参考这个文件: https://github.com/ruby-china/ruby-china/blob/master/app/models/mongoid/markdown_body.rb

我觉得可以考虑将它抽取成类:

class SlugGenerator
  def self.generate(slug_base)
     slug_base.gsub(' ', '-').downcase
  end
end

module Model1
  before_save :generate
  def generate
    self.slug = SlugGenerator.generate(self.title)
  end
end

module Model2
  before_save :generate
  def generate
    self.slug = SlugGenerator.generate(self.name)
  end
end

抽取主要逻辑到类中,解耦并且便于测试。

#8楼 @huacnlee 挺类似,但是没看懂scope :without_body, without(:body)这句在做什么

#10楼 @AlphaLiu without 是 Mongoid 的语法,意思是排除 body 这个字段

#11楼 @huacnlee 哦。我了解了。ruby china 的这段代码,处理的字段都是叫做self.body,但因为我想处理的几个model,里面需要的字段都不一样,比如model1是title, mode2是name, model3是username等等,不知道要如何处理。在stackoverflow上有人给我的答案是代码跟你这段类似,然后在model里面用alias_attribute 来把需要的字段转成跟module里使用的一致。我试过是可以,不知道这样是否可行?

#12楼 @AlphaLiu 这个就有点复杂了

以下代码手写,没实际跑过,你可以看看这个写法和思路

module MarkdownBody
  extend ActiveSupport::Concern

  included do
  end

  def markdown_content_with_field(field)
    self.body_html = MarkdownTopicConverter.format(self.send(field)) if self.send("#{field}_changed?")
  end

  module ClassMethods
    def markdown_body(body_field)
      class_eval %(
        before_save do
          markdown_content_with_field('#{body_field}')
        end
      )
    end
  end
end


class Post
  include MarkdownBody

  field :content

  markdown_body :content
end

class Comment
  include MarkdownBody

  field :comment

  markdown_body :comment
end

还有你可以看看我之前写的 redis-search 的某个文件: https://github.com/huacnlee/redis-search/blob/master/lib/redis/search/base.rb

#13楼 @huacnlee 这个思路真不错,我按照来做了,能用,也比用alias_attribute改掉字段要好。多谢

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册