Как сделать метод доступным как для моего контроллера, так и для модели в Rails?

У меня есть частный метод в приложении 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)

Я вызываю этот метод несколькими способами в моем контроллере и модели, поэтому включил его в оба класса как частный метод. Это прекрасно работает, и я доволен, но это не очень СУХОЙ.

Как я могу сделать этот метод доступным как для моей модели, так и для контроллера, но только один раз появляется код? Это больше вопрос Ruby, чем вопрос Rails и отражает мою новизну в ООП. Я предполагаю, что модуль или смесь - это ответ, но я до сих пор не использовал их до сих пор и нуждаюсь в небольшой руке.

Спасибо.

Ответ 1

Модули используются для 3-х разных вещей в рубине. Во-первых, это пространство имен. Наличие классов или констант внутри модуля не будет сталкиваться с классами или константами вне этого модуля. Что-то вроде этого

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'

Миксины на самом деле представляют собой более сложную тему, чем я здесь, но углубляться, вероятно, будет путать.

Теперь для меня 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, потому что я знаю, какие рельсы будут искать, когда он увидит это (aws_utils.rb), и, честно говоря, я понятия не имею, что ему понадобится для S3Utils; -)

Не стесняйтесь спрашивать больше информации, если я что-то не понимаю. Модули имеют тенденцию быть одной из тех вещей в рубине, которые в то время как удивительные, прямо озадачивают новичков.

Ответ 2

Ваша догадка правильная: вы можете поместить модуль в каталог lib. В чтобы сделать эти методы доступными для ваших моделей, просто включите его с:

class Model < ActiveRecord::Base
  include MyModule
end

Включенные методы экземпляра модуля станут экземплярами вашего класса. (Это известно как mixin)

module MyModule
  def S3
    #...
  end
end

Ответ 3

Вы можете написать модуль как:

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 *)