Giter Site home page Giter Site logo

rails-ioc's Introduction

Quickstart

# Gemfile:
gem 'rails-ioc'

# config/dependencies/development.rb:
RailsIOC::Dependencies.define do     
  prototype :card_validator,  CreditCardValidator, "Visa", "Mastercard"
  prototype :payment_gateway, DummyPaymentGateway, ref(:card_validator)

  controller PaymentsController, {
    payment_gateway: ref(:payment_gateway),
  }
end   

# app/controllers/payments_controller.rb:
class PaymentsController < ApplicationController
  def accept_payment
    @payment_gateway.process(params[:credit_card])
  end
end

# spec/controllers/payments_controller_spec.rb
it "processes a succesful payment" do
  controller_dependencies(payment_gateway: mock(PaymentGateway))
  controller.payment_gateway.should_receive(:process).with("4111").and_return(true)
  post :accept_payment, credit_card: "4111"
  response.status.should == 200
end

Background

Inversion of control via dependency injection is widely used in large software systems and is generally accepted to be a "Good Thing". If the terms are unfamiliar these are good places to start reading:

The practice as described in the articles above is not particularly common in Ruby applications. Jamis Buck, author of the erstwhile DI framework Copland makes a compelling argument against it. If you have to ask the question "Will I need IOC for my next Rails app", the answer is almost certainly "No". The core components of a standard Rails app usually interact politely and can generally be easily tested.

Still, having run into several Rails projects which dealt with dependencies beyond an ActiveRecord database connection (payment gateways, email services, version control systems, image processing services and more) I realised that I was reminiscing fondly about Java and .NET's various containers for two reasons:

  1. I really despise mocking calls to MyService#new to force a controller to use a stub implementation in a unit test.
  2. There is no clean way to switch dependencies out on a per-environment basis (to use the testable version of a payment gateway in the staging environment, for example).

RailsIOC attempts to make these problems less painful for applications with complex interactions with external services by providing a lightweight way to define dependencies and inject them into ActionController. The patterns will be familiar to anyone who has used Spring or Unity. Dependencies are defined in pure Ruby using a simple internal DSL. Where possible, RailsIOC enforces constructor injection. The exception to this rule is the creation of controllers, where it avoids interfering with Rails' own instantiation and injects dependencies as @local_variables.

Cleaner Testing

Before

# Controller:
@gateway = Gateway.new(ServiceA.new, ServiceB.new)
@gateway.do_something!

# RSpec:
@svc_a = mock(ServiceA)
@svc_b = mock(ServiceB)
ServiceA.stub(:new).and_return(@svc_a)
ServiceB.stub(:new).and_return(@svc_b)
@gateway = mock(Gateway)
@gateway.should_receive(:do_something!).and_return(12345)
Gateway.stub(:new).with(@svc_a, @svc_b).and_return(@gateway)

After

# Controller:
@gateway.do_something!

# RSpec:
controller_dependencies(gateway: mock(Gateway))
controller.gateway.should_receive(:do_something!).and_return(12345)

Customise and Override Dependencies Per-Environment

Before

# app/controllers/payments_controller.rb:
class PaymentsController < ApplicationController
  def accept_payment
    if Rails.env.development? || Rails.env.test?
      @credit_card_validator = BogusCardValidator.new
    else
      @credit_card_validator = RealCardValidator.new
    end
    if Rails.env.production? 
      @gateway = RealPaymentGateway.new
    elsif Rails.env.staging? 
      @gateway = RealPaymentGateway.new(use_testing_url: true)
    else
      @gateway = BogusPaymentGateway.new
    end
    card = @credit_card_validator.validate(params[:card])
    @gateway.process(card)
  end
end

After

# app/controllers/payments_controller:
class PaymentsController < ApplicationController
  def accept_payment    
    card = @credit_card_validator.validate(params[:card])
    @gateway.process(card)
  end
end
  
# config/dependencies/production.rb:
RailsIOC::Dependencies.define do
  prototype :payment_gateway,       RealPaymentGateway
  prototype :credit_card_validator, RealCardValidator

  controller PaymentsController, {
    gateway:               ref(:payment_gateway)
    credit_card_validator: ref(:credit_card_validator)
  }
end

# config/dependencies/staging.rb:
RailsIOC::Dependencies.define do
  inherit_environment(:production)
  
  prototype :payment_gateway, RealPaymentGateway, {use_testing_url: true}
end

# config/dependencies/development.rb:
RailsIOC::Dependencies.define do
  inherit_environment(:production)

  singleton :payment_gateway, BogusPaymentGateway
  singleton :credit_card_validator, BogusCardValidator
end

rails-ioc's People

Contributors

rickgrundy avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.