如何在Rails中使一个方法对控制器和模型都可用?

10

我在我的Rails应用程序中有一个私有方法,用于连接到Amazon S3,执行传递的代码块,然后关闭与S3的连接。它看起来像这样;

def S3
  AWS::S3::Base.establish_connection!(
    :access_key_id     => 'Not telling',
    :secret_access_key => 'Really not telling'
  )
  data = yield
  AWS::S3::Base.disconnect
  data
end

这个被称为(举例):
send_data(S3 {AWS::S3::S3Object.value("#{@upload_file.name}",'bucket')}, :filename => @upload_file.name)

我在我的控制器和模型中以多种方式调用此方法,因此将其作为私有方法包含在两个类中。这很好用,我很满意,但不太符合DRY原则。
如何使此方法可供我的模型和控制器使用,但只出现一次代码?这更像是一个Ruby问题而不是Rails问题,反映了我对面向对象编程(OOP)的新鲜感。我猜想模块或混入(mix-in)是答案,但我到目前为止还没有真正使用过它们,需要一点指导。
谢谢。
3个回答

9

在 Ruby 中,模块有三个不同的用途。首先是命名空间。在模块内部定义类或常量不会与模块外部的类或常量发生冲突。例如:

class Product
  def foo
    puts 'first'
  end
end

module Affiliate
  class Product
    puts 'second'
  end
end

p = Product.new
p.foo # => 'first'

p = Affiliate::Product.new
p.foo # => 'second'

模块的第二个用途是放置那些没有其他明显归属地的方法。你也可以在类中这样做,但使用模块会让阅读代码的人知道它不应该被实例化。像这样:

module Foo
  def self.bar
    puts 'hi'
  end
end

Foo.bar #=> 'hi'

最后(也是最令人困惑的)是模块可以被包含在其他类中。以这种方式使用它们也被称为mixin,因为您正在将所有方法“混合”到您要包含的内容中。
module Foo
  def bar
    puts 'hi'
  end
end

class Baz
  include Foo
end

b = Baz.new
b.bar #=> 'hi'

混合(Mixins)实际上比我在这里所涉及的内容复杂得多,但深入讨论可能会令人困惑。
现在,在我看来,S3似乎是真正属于控制器的东西,因为控制器通常是处理传入和传出连接的对象。如果是这种情况,我只需要在应用程序控制器上有一个受保护的方法,因为它将对所有其他控制器可用,但仍然是私有的。
如果您确实有在模型中使用它的充分理由,那么我会选择使用混合。类似于:
module AwsUtils
private
  def S3
    AWS::S3::Base.establish_connection!\
      :access_key_id     => 'Not telling',
      :secret_access_key => 'Really not telling'

    data = yield
    AWS::S3::Base.disconnect
    data
  end
end

如果您将代码放在lib/aws_utils.rb中,您应该可以通过在控制器和模型中添加include AwsUtils来使用它。Rails知道在lib中查找类和模块,但只有名称匹配(宽字符)时才会查找。我将其称为AwsUtils,因为我知道当rails看到它(aws_utils.rb)时会查找什么,老实说,我不知道S3Utils需要什么;-) 如果我没有清楚地表达某些内容,请随时询问更多信息。在Ruby中,模块往往是那些令新手困惑的东西,虽然非常棒。

非常感谢。我同意这应该放在控制器中,但是我之前写的原始代码是在我真正知道自己在做什么之前的一段时间。模型中有很多不应该存在的东西,但我现在不想重写它。你的技术非常好用,我已经能够将它用于另一个控制器中包含S3方法,所以这是值得的。我的模块和混合理解还不完美(它们很令人困惑-我同意),但这确实帮助了我很多。 - brad

3
您的直觉是正确的:您可以将模块放在lib目录中。为了使这些方法对您的模型可用,只需使用以下内容包含它即可:
class Model < ActiveRecord::Base
  include MyModule
end

所包含的模块实例方法将成为您的类的实例方法。(这被称为混合)
module MyModule
  def S3
    #...
  end
end

  1. 你无法实例化模块。
  2. 如果你包含一个模块,它的所有实例方法都会成为你的类的实例方法,所以要么将它们放在单例类上,要么包含它们,不要两者都做。
- Matt Briggs

1
你可以这样编写一个模块:

module MyModule
  def self.S3(args*)
    AWS::S3::Base.establish_connection!(
      :access_key_id     => 'Not telling',
      :secret_access_key => 'Really not telling'
    )
    data = yield
    AWS::S3::Base.disconnect
    data
  end
end

然后在您的控制器或模型中调用它

MyModule.S3(params*)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接