• Stars
    star
    112
  • Rank 312,240 (Top 7 %)
  • Language
    Ruby
  • License
    Other
  • Created about 12 years ago
  • Updated over 9 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A dependency injector for Rails for writing clean controllers.

Poniard

A lightweight gem that provides an alternative to Rails controllers. It uses parameter based dependency injection to explicitly make dependencies available, rather than mixing them all in from a base class. This allows controllers to be properly tested in isolation, bringing all the design benefits of TDD (as opposed to "test-first" development, which is more common with the standard integration style controller tests).

Poniard is designed to be compatible with standard controllers. It can be used for your entire application, just one action, or anything in between.

Example

A poniard controller is slightly more verbose than the what you may be used to. In particular, all the dependencies of a method (response in the following example) must be declared as parameters to your method. Poniard will introspect the method before calling, and ensure that the correct values are passed. These values will for the most part be the same objects you normally deal with in Rails (session, flash, etc...).

The following controller renders the default index template, setting the instance variables @message.

module Controller
  class Registration
    def index(response)
      response.default message: "hello"
    end
  end
end

This is differs from traditional controllers in two ways: passing variables to the template is done with an explicit method call rather than instance variable assignment, and dependencies that would normally be made available by a superclass are passed in as parameters to the method.

Wiring this controller into an application is a one-liner in the normal controller definition.

class RegistrationsController < ApplicationController
  include Poniard::Controller

  provided_by Controller::Registration
end

Traditional and poniard styles can be used together. Some actions can be implemented in the normal controller, others can be provided by an injectable one.

class RegistrationsController < ApplicationController
  include Poniard::Controller

  # index action provided by this class
  provided_by Controller::Registration

  # All controller features work in harmony with poniard, such as this
  before_filter :require_user

  # update action implemented normally
  def update
    # ...
  end
end

Sources

Poniard knows about all the standard controller objects such as response, session and flash. Domain specific definitions are then layered on top by creating sources:

class Source
  class Registration
    def finder
      Registration.accepted
    end

    def current_registration
      Registration.find(params[:id])
    end
  end
end

This is wired up in the provided_by call:

provided_by Controller::Registration, sources: [
  Source::Registration
]

Any number of sources can be used, making it easy to reuse logic across controllers.

Testing

Set up a common injector for the scope of your controller that knows about common sources that all tests require (such as response). Add extra required sources on a per test basis (finder in the below example).

require 'poniard/injector'
require 'controller/registration'

describe Controller::Registration do
  let(:response) { double("Poniard::ControllerSource") }
  let(:injector) { Poniard::Injector.new([
    response: response.as_null_object
  ]) }

  def dispatch(action, overrides = {})
    injector.dispatch described_class.new.method(action), overrides
  end

  describe '#index' do
    it 'should render default action with all registrations' do
      finder = double(all: ['r1'])
      response.should_receive(:default).with(registrations: ['r1'])

      dispatch :index, finder: finder
    end
  end
end

Techniques

Built-in sources

See the YARD docs for all the built in controller sources.

Layouts

If a layout method is implemented in a controller, it will be used to select a layout for the controller. This is equivalent to adding a custom layout method to a standard controller.

Mime types

The Rails respond_to API is not very Object-Oriented, so is hard to test in isolation. Poniard provides an alternative respond_with that allows you to provide a response object, which is much easier to work with.

Authorization

Poniard sources can raise exceptions to indicate authorization failure, which can then be handled in a standard manner using rescue_from.

module Source
  class Admin
    def current_admin(session)
      User.find_by_id(session[:admin_id])
    end

    def authorized_admin(current_admin)
      current_admin || raise(ResponseException::Unauthorized)
    end
  end
end

This can be slightly weird if the method being authorized does not actually need to interact with the admin, since it will have a method parameter that is never used.

RSpec::Matchers.define :have_param do |attribute|
  match do |obj|
    obj.parameters.map(&:last).include?(attribute)
  end
end

def instance_method(name)
  described_class.new.method(name)
end

it 'requires authorization' do
  instance_method(:index).should have_param(:authorized_admin)
end

Developing

Status

Not widely used. May be some obvious things missing from built-in controller sources that you will have to add.

As far as I know Poniard has not been used on any high traffic applications, and I wouldn't be surprised if there is a performance penalty for using it due to the use of reflection. Please benchmark (and share!) before using in such an environment.

Compatibility

Requires 1.9 or above.

Support

Make a new github issue.

Contributing

Fork and patch! Please update the README and other documentation if you add features.

More Repositories

1

enki

A Ruby on Rails blogging app for the fashionable developer. It's better than Mephisto or SimpleLog
Ruby
819
star
2

rspec-fire

Obsolete - use verifying doubles in RSpec 3
Ruby
362
star
3

tufte-graph

a jQuery plugin that makes pretty bar charts
JavaScript
236
star
4

kronic

A dirt simple library for parsing and formatting human readable dates
Ruby
151
star
5

db2s3

A rails plugin to backup Mysql to Amazon S3
Ruby
69
star
6

node-amqp

AMQP client for nodejs
JavaScript
52
star
7

consul-client

Ruby client gem for Consul HTTP API.
Ruby
43
star
8

jquery-enumerable

The only fully tested and API consistent enumerable plugin for jQuery (collect, inject and friends)
JavaScript
41
star
9

socialbeat

A proof of concept social crowd beat visualization thing, inspired by Giles Bowkett's RubyFringe talk.
Ruby
21
star
10

classifier

Classifier is a general module to allow Bayesian and other types of classifications.
Ruby
20
star
11

rust-puzzlefighter

A puzzle fighter/swordfighting clone.
Rust
19
star
12

kamel

Create KML files for tasty overlays on google earth and google maps
Ruby
18
star
13

writing

bits of writing
Ruby
16
star
14

haskell-servant-react-auth-example

Haskell
15
star
15

xtdo

Damnit, todo list MY WAY
Ruby
15
star
16

lesstile

Converts text formatted with an exceedingly simple markup language into HTML - perfect for comments on your blog.
Ruby
15
star
17

sandbox

Random bits and pieces
JavaScript
14
star
18

nytimes-slider

The hottest implementation of the nytimes slider you ever did see
JavaScript
14
star
19

dotfiles

My dotfiles
Perl
11
star
20

outage-party

The only incident response plan you'll ever need.
HTML
9
star
21

curlophone-orchestra

Uses bonjour to play a great symphony across many computers
Ruby
9
star
22

dominion-solitaire

An ncurses Dominion implementation with a focus on lightning quick goldfishing.
Ruby
9
star
23

xspec

XSpec is an rspec-inspired testing library that is written in a literate style and designed to be obvious to use, highly modular, and easy to extend. A concept piece.
Ruby
8
star
24

mnemonicker-wordlist

Lists of words.
7
star
25

sheets

Various Lilypond scores I have need for
LilyPond
7
star
26

nested-attributes-demo

An example of how to use nested attributes with rails 3
Ruby
7
star
27

gnucash2ledger

Convert GnuCash files to a format supported by the ledger command line application
Ruby
7
star
28

dm-checked-types

CHECK constraints and validations for your models
Ruby
6
star
29

factorio-layout-designer

JavaScript
6
star
30

buildhawk

Historical information about your build, on a webpage!
Ruby
6
star
31

factorio-hs

Haskell library for working with Factorio data
Haskell
5
star
32

dovin

A proof-assistant for Possibility Storm-style problems for Magic: The Gathering
Haskell
4
star
33

node-amqp-ragel

Node AMQP client that uses a ragel parser in a C extension. Doesn't work.
JavaScript
4
star
34

failtrain

Ruby
4
star
35

jruby-guice

Example app showing how to wire up Ruby classes with Google Guice.
Ruby
4
star
36

vitamin-vcv-modules

Assorted VCV Rack modules
C++
4
star
37

vegeinfo

Most pro-vege websites are shit, so I'm trying to make a better one
JavaScript
4
star
38

mtg-bench

Scala implementation of Magic The Gathering rules, geared towards algorithmic training
Scala
3
star
39

consul-fs

Proof-of-concept fuse adapter for Consul KV store.
Ruby
3
star
40

xtdo-hs

Haskell port of xtdo
Haskell
3
star
41

mnemonicker

An aid for remembering numbers
Ruby
3
star
42

veganmelb

Vegan. In Melbourne. Yeah!
JavaScript
3
star
43

roborabb

present for my bro
Ruby
3
star
44

enki-vim

Publish to enki with VIM. That's pretty neat.
Ruby
3
star
45

project-euler

Literate Haskell solutions to Project Euler
Haskell
3
star
46

nom

command line access to ausnom.com
Ruby
2
star
47

vegan-month

Vegan Month home page
HTML
2
star
48

project-hoff

HOFF
Ruby
2
star
49

blog-v2

Ruby
2
star
50

enki-website

Main page for enki
Ruby
2
star
51

babushka-deps

My deps for babushka
Ruby
2
star
52

reader

venus, with my config
Python
2
star
53

gust

ruby gist clone on top of plain rack
Ruby
2
star
54

singing

I'm learning to sing
2
star
55

card_game

Random Ruby utility libraries for modeling card games
Ruby
2
star
56

mit-advanced-ds

http://courses.csail.mit.edu/6.851/spring12/index.html
Ruby
2
star
57

rhnh

My blog - a fork of enki
Ruby
1
star
58

rack-my-id

An OpenID provider that does very little
Ruby
1
star
59

xaviershay.com

My home page
HTML
1
star
60

mtg-bench-ruby

algorithmic training for MtG
Ruby
1
star
61

clear-menulet

A menulet that tells you when the CLEAR grain exchange is open, with growl notifications
1
star
62

aasm-presentation

Lightning talk for #roro
1
star
63

ori-tracker

C#
1
star
64

cryptopals-solutions

Haskell
1
star
65

snes-game

Assembly
1
star
66

ludum-dare-35

PureScript
1
star
67

ausnom

Nutritional data of foods, as released by FSANZ
Ruby
1
star
68

cibass

It's a CI server which doesn't work yet.
Ruby
1
star
69

mopus-gagnum

Haskell
1
star
70

pacebot

A bot to hang out in slack channels where runners frequent.
JavaScript
1
star
71

licross

crossword game
Haskell
1
star
72

factorio-solver-ui

hella wip
JavaScript
1
star
73

dm-nested-transactions

Adds nested transaction support to DataMapper
Ruby
1
star
74

scribebot

Quick and dirty slackbot for remembering things
Ruby
1
star
75

minisculus

a challenge!
Ruby
1
star
76

pacecalculator

http://pacecalculator.herokuapp.com
JavaScript
1
star
77

hrange

An experimental haskell implementation of the range query language.
Haskell
1
star
78

legendary-bench

Haskell
1
star
79

alien-parade

Aliens on parade! Flocking + gosu.
Ruby
1
star
80

voxels-bench

Rust
1
star