rails中的控制器构造函数参数?

时间:2018-06-08 19:39:01

标签: ruby-on-rails

我们如何在控制器的构造函数中注入参数或者在rails中依赖注入的原理是什么?

假设我需要将支付服务注入控制器。从.NET世界,我将使用DI框架,将其配置为为IPaymentService依赖项注入CreditPaymentService实例。

在rails中,我们如何实现这一目标?特别是对于测试和嘲笑?

我读过多个来源说明我们不需要任何DI框架来完成ruby中的DI,但我不知道如何为控制器做这个?

谢谢!

// .NET
public class OrderController : Controller
{
    IPaymentService _paymentService;

    public OrderController(IPaymentService paymentService)
    {
        _paymentService = paymentService;
    }

    public object Pay(Order order)
    {       
        _paymentService.process(order.GetTotal());
    }
}

# Rails
class order_controller < ApplicationController

    def initialize(payment_service)
        @payment_service = payment_service
    end 

    def pay
        order = request.parameters(:order)
        @payment_service.process(order)
    end
end

2 个答案:

答案 0 :(得分:1)

尝试这样的事情:

class OrdersController < ApplicationController

  def pay
    # you'll need to define payment_params elsewhere in the controller
    PaymentService.call(payment_params)
  end

end

PaymentService只是一个普通的红宝石对象:

class PaymentService

  attr_accessor *%w(
    options
  ).freeze

  class << self 

    def call(options={})
      new(options).call
    end

  end # Class Methods

  #==========================================================================
  # Instance Methods
  #==========================================================================

    def intialize(options={})
      @options = options
    end

    def call 
      # do stuff
      # return something
    end

end

我个人想让call成为一种类方法,所以我不必这样做:

PaymentService.new(payment_params).call

对我而言看起来不太干净。但是,这是个人偏好的问题。

你可以把它放进去:

app
 |- ...
 |- services
 |   |- payment_service.rb
 |- ...

这会导致服务自动加载,您不必使用config.autoload_paths

测试服务是微不足道的(重点是,不是吗?)。这是一个rspec示例:

require 'rails_helper'

RSpec.describe PaymentService do
  before(:all) do
    @method_name = "call"
  end
  describe "#call" do
    it "responds" do
      expect(described_class.respond_to?(@method_name)).to be_truthy
    end
    context "when using good params" do 
      before(:each){ @params = good_params }
      it "does something" do
        expect(calling_the_service).to do_something
      end
    end
  end
end

def calling_the_service
  described_class.send method_name, params
end

def good_params
  {some: :arguments}
end

def params
  @params 
end

def method_name
  @method_name
end

事实上,我做的更像是:

class ApplicationController < ActionController::Base
  # I have a custom module that lets me make this call. Among other things,
  # it creates the call_service method on all controllers. 
  acts_as calling: :services 
end

OrdersController现在知道如何call_service

class OrdersController < ApplicationController

  def pay
    # Given the acts_as calling: :services call, above, the OrdersController 
    # knows how to inspect the SERVICE_DETAIL constant on PaymentService 
    # and construct the appropriate arguments. In this case, passing in 
    # something like {current_user: 1}
    call_service PaymentService
  end

end

我将一些内容移到ServiceBase

class ServiceBase 

  attr_accessor *%w(
    options
  ).freeze

  class << self 

    def call(options={})
      new(options).call
    end

  end # Class Methods

  #======================================================================
  # Instance Methods
  #======================================================================

    def intialize(options={})
      @options = options
    end

  private 

    # This method reads the REQUIRED_ARGS AND REQUIRED_VALUES constants 
    # and determines whether a valid service call was made. It also logs
    # errors so that I can go back and see failures.
    def good_to_go?
      # some stuff
    end

    def decorated_options
      @decorated_options ||= OptionsDecorator.new(options)
    end

end

现在我以常量的形式声明服务的一些元数据。它们基本上定义了服务的接口:

class PaymentService < ServiceBase

  SERVICE_DETAILS = [
    {current_user: [:id]}
  ].freeze

  REQUIRED_ARGS = %w(
    current_user
  ).freeze

  REQUIRED_VALUES = %w(
    current_user_id
  ).freeze

  delegate *%w(
    current_user_id
  ), to: :decorated_options

  #======================================================================
  # Instance Methods
  #======================================================================

    def call 
      raise unless good_to_go?
      # do stuff
      # return something
    end

end

答案 1 :(得分:0)

在Rails中,向控制器添加构造函数并没有意义。如果您正在寻找&#34;注入&#34;在控制器中有一些功能,你可以在控制器中定义一个mixin模块(例如在lib目录下)和include

module Foo
  def say_hello!
    puts "Hello world"
  end
end

class OrdersController < ApplicationController
  include Foo

  def index
    say_hello! # "Hello world"
  end
end

或将其定义为服务类并直接调用

class FooService
  def initialize
    puts "Hello world"
  end
end

class OrdersController < ApplicationController
  def index
    FooService.new # "Hello world"
  end
end

请注意,lib下的文件默认情况下不会被Rails自动加载,而您需要执行类似

的操作
config.autoload_paths += %W(#{config.root}/lib)
application.rb

中的