我们如何在控制器的构造函数中注入参数或者在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
答案 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
中的