liaison 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +30 -0
- data/README.md +116 -0
- data/Rakefile +6 -0
- data/liaison.gemspec +24 -0
- data/lib/liaison.rb +3 -0
- data/lib/liaison/mock_presenter.rb +31 -0
- data/lib/liaison/presenter.rb +95 -0
- data/lib/liaison/version.rb +3 -0
- data/spec/mock_presenter_spec.rb +50 -0
- data/spec/presenter_spec.rb +99 -0
- data/spec/spec_helper.rb +1 -0
- metadata +119 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Copyright (c)2011, Mike Burns
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
* Redistributions in binary form must reproduce the above
|
12
|
+
copyright notice, this list of conditions and the following
|
13
|
+
disclaimer in the documentation and/or other materials provided
|
14
|
+
with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of Mike Burns nor the names of other
|
17
|
+
contributors may be used to endorse or promote products derived
|
18
|
+
from this software without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
24
|
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
25
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
26
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
27
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
28
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
29
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
30
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
Liaison
|
2
|
+
=======
|
3
|
+
|
4
|
+
A Rails presenter class.
|
5
|
+
|
6
|
+
A major idea of [the presenter pattern](http://blog.jayfields.com/2007/03/rails-presenter-pattern.html) is to break off the business logic from the view object, letting the view logic be a dumb instance that knows how to get, set, and validate values. The business logic can then query the presenter object for the values as needed.
|
7
|
+
|
8
|
+
Look, here's an example business object:
|
9
|
+
|
10
|
+
class SignUp
|
11
|
+
attr_reader :user
|
12
|
+
|
13
|
+
def initialize(presenter, account_builder = Account)
|
14
|
+
@email = presenter[:email]
|
15
|
+
@password = presenter[:password]
|
16
|
+
@account_name = presenter[:account_name]
|
17
|
+
|
18
|
+
@presenter = presenter
|
19
|
+
@account_builder = account_builder
|
20
|
+
end
|
21
|
+
|
22
|
+
def save
|
23
|
+
if presenter.valid?
|
24
|
+
account = account_builder.new(:name => account_name)
|
25
|
+
@user = account.users.build(:email => email, :password => password)
|
26
|
+
account.save.tap do |succeeded|
|
27
|
+
presenter.add_errors(account.errors) unless succeeded
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
attr_accessor :email, :password, :account_name, :account_builder, :presenter
|
35
|
+
end
|
36
|
+
|
37
|
+
It's just a class, which you can unit test as you please. A presenter object is passed in, then we pull the values out, make sure it's valid, and add errors to it as needed. This class does not deal directly with validations, state, or any of the ActiveModel nonsense.
|
38
|
+
|
39
|
+
Now you need to know how to use a `Presenter` object, so this is what the controller looks like:
|
40
|
+
|
41
|
+
class SignupsController < ApplicationController
|
42
|
+
def new
|
43
|
+
@sign_up = presenter
|
44
|
+
end
|
45
|
+
|
46
|
+
def create
|
47
|
+
@sign_up = presenter.with_params(params[:sign_up])
|
48
|
+
db = SignUp.new(@sign_up)
|
49
|
+
|
50
|
+
if db.save
|
51
|
+
sign_in_as(db.user)
|
52
|
+
redirect_to root_url
|
53
|
+
else
|
54
|
+
render :new
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def presenter
|
61
|
+
Presenter.new('sign_up',
|
62
|
+
:fields => [:email, :password, :account_name],
|
63
|
+
:validator => SignUpValidator.new)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
In our `new` action we simply set the `@sign_up` i-var to an instance of the `Presenter`. In `create` we use that `Presenter` instance, adding CGI params in. Then we pass that to the `SignUp` class defined above and it's all boring from there.
|
68
|
+
|
69
|
+
The `presenter` method in the above example produces a new `Presenter` instance. This instance has a model name (`sign_up`), fields the form will handle (`email`, `password`, and `account_name`), and a validator (`SignUpValidator`). The validator is any instance of `ActiveModel::Validator`, for example:
|
70
|
+
|
71
|
+
class SignUpValidator < ActiveModel::EachValidator
|
72
|
+
def validate_each(record, attribute, value)
|
73
|
+
record.errors.add(attribute, "can't be blank") if value.blank?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
You, the author of the business logic class, are in charge of checking in on these validations and errors. For example, before saving any objects you should check `Presenter#valid?`. And after you've saved something to the database you should add any errors onto the presenter using `Presenter#add_errors`.
|
78
|
+
|
79
|
+
Getting Data
|
80
|
+
----------
|
81
|
+
|
82
|
+
An instance of the `Presenter` object is Hash-like: it implements the `Enumerable` module, which means it has an `#each` method among many others; it also has a `#[]` method, which you can use to access values just like with the CGI `params` hash.
|
83
|
+
|
84
|
+
Testing
|
85
|
+
-------
|
86
|
+
|
87
|
+
When writing your unit tests it'll be handy to have a mock presenter around, which is why we package a `MockPresenter` class for you to use. It gives you access to the `#have_errors` and `#have_no_errors` RSpec matchers.
|
88
|
+
|
89
|
+
|
90
|
+
describe SignUp, 'invalid' do
|
91
|
+
let(:params) { { :email => '',
|
92
|
+
:password => 'bar',
|
93
|
+
:account_name => 'baz' } }
|
94
|
+
let(:errors) { { :email => "can't be blank" } }
|
95
|
+
let(:presenter) do
|
96
|
+
MockPresenter.new(:valid => false,
|
97
|
+
:params => params,
|
98
|
+
:errors => errors)
|
99
|
+
end
|
100
|
+
let(:account_builder) { MockAccount.new(:valid => true) }
|
101
|
+
|
102
|
+
subject { SignUp.new(presenter, account_builder) }
|
103
|
+
|
104
|
+
it "does not save the account or user" do
|
105
|
+
subject.save.should be_false
|
106
|
+
|
107
|
+
presenter.should have_errors(errors)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
Contact
|
112
|
+
-------
|
113
|
+
|
114
|
+
Copyright 2011 [Mike Burns](http://mike-burns.com/).
|
115
|
+
|
116
|
+
Please [open a pull request on Github](https://github.com/mike-burns/liaison/pulls) as needed.
|
data/Rakefile
ADDED
data/liaison.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "liaison/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "liaison"
|
7
|
+
s.version = Liaison::VERSION
|
8
|
+
s.authors = ["Mike Burns"]
|
9
|
+
s.email = ["mike@mike-burns.com"]
|
10
|
+
s.homepage = "https://github.com/mike-burns/liaison"
|
11
|
+
s.license = 'BSD'
|
12
|
+
s.summary = %q{A Rails presenter class.}
|
13
|
+
s.description = %q{An object that works with form_for that encapsulates validations and data management, leaving the business logic up to your testable old self.}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency('activemodel')
|
21
|
+
|
22
|
+
s.add_development_dependency('rspec')
|
23
|
+
s.add_development_dependency('rake')
|
24
|
+
end
|
data/lib/liaison.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
class MockPresenter
|
2
|
+
def initialize(opts = {})
|
3
|
+
@valid = opts.delete(:valid) != false
|
4
|
+
@fields = opts[:params]
|
5
|
+
@errors = opts[:errors] || {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](key)
|
9
|
+
@fields[key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
@errors.empty? && @valid
|
14
|
+
end
|
15
|
+
|
16
|
+
def errors
|
17
|
+
@errors
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_errors(errs)
|
21
|
+
errs.each {|k,v| @errors[k] = v}
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_no_errors?
|
25
|
+
@errors.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_errors?(errors)
|
29
|
+
errors.all? {|k,v| @errors[k] == v}
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
class Presenter
|
4
|
+
extend ActiveModel::Naming
|
5
|
+
include ActiveModel::Conversion
|
6
|
+
include ActiveModel::Validations
|
7
|
+
|
8
|
+
validate :instance_validations
|
9
|
+
|
10
|
+
# Constructs a new Presenter object which can be passed to a form or
|
11
|
+
# generally filled with values. It must take a model name, which is a string
|
12
|
+
# that is the name of the model you are presenting. It can also take a
|
13
|
+
# validator and fields.
|
14
|
+
#
|
15
|
+
# Presenter.new('sign_up',
|
16
|
+
# :fields => [:account_name, :email, :password],
|
17
|
+
# :validator => SignUpValidator)
|
18
|
+
def initialize(model_name, opts = {})
|
19
|
+
@@model_name = model_name
|
20
|
+
|
21
|
+
@validator = opts[:validator]
|
22
|
+
@fields = opts[:fields]
|
23
|
+
|
24
|
+
self.class.send(:attr_accessor,*@fields) unless @fields.nil? || @fields.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def instance_validations
|
28
|
+
validates_with(@validator, :attributes => @fields) if @validator
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.model_name # :nodoc:
|
32
|
+
ActiveModel::Name.new(Class.new do
|
33
|
+
attr_reader :name
|
34
|
+
def initialize(n)
|
35
|
+
@name = n
|
36
|
+
end
|
37
|
+
end.new(@@model_name))
|
38
|
+
end
|
39
|
+
|
40
|
+
def persisted? # :nodoc:
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set the params from the form using this.
|
45
|
+
#
|
46
|
+
# @sign_up_presenter.with_params(params[:sign_up])
|
47
|
+
def with_params(params = {})
|
48
|
+
params.each {|k,v| self.send("#{k}=", v)}
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Combine error messages from any ActiveModel object with the presenter's, so
|
53
|
+
# they will show on the form.
|
54
|
+
#
|
55
|
+
# @sign_up_presenter.add_errors(account.errors)
|
56
|
+
#
|
57
|
+
# You will probably use it like this:
|
58
|
+
#
|
59
|
+
# class SignUp
|
60
|
+
# attr_accessor :presenter
|
61
|
+
# def save
|
62
|
+
# account = Account.new
|
63
|
+
# account.save.tap do |succeeded|
|
64
|
+
# presenter.add_errors(account.errors) unless succeeded
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
def add_errors(errs)
|
69
|
+
errs.each {|k,v| errors.add(k,v)}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Access individual values as if this were the CGI params hash.
|
73
|
+
#
|
74
|
+
# @sign_up_presenter[:account_name]
|
75
|
+
def [](key)
|
76
|
+
to_hash[key]
|
77
|
+
end
|
78
|
+
|
79
|
+
# This is an instance of Enumerable, which means you can iterate over the
|
80
|
+
# keys and values as set by the form.
|
81
|
+
#
|
82
|
+
# @sign_up_presenter.each {|k,v| puts "the form set #{k} to #{v}" }
|
83
|
+
def each(&block)
|
84
|
+
to_hash.each(&block)
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
def to_hash
|
90
|
+
@fields.inject({}) do |acc,field|
|
91
|
+
acc[field] = send(field)
|
92
|
+
acc
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MockPresenter, 'params' do
|
4
|
+
let(:params) {{'a' => 'one', 'b' => 'two'}}
|
5
|
+
|
6
|
+
subject { MockPresenter.new(:params => params) }
|
7
|
+
|
8
|
+
it "can access values using []" do
|
9
|
+
params.each do |k,v|
|
10
|
+
subject[k].should == v
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe MockPresenter, 'validations and errors' do
|
16
|
+
let(:errors) {{ :account_name => "can't be blank" }}
|
17
|
+
|
18
|
+
subject { MockPresenter.new(:valid => false, :errors => errors) }
|
19
|
+
|
20
|
+
it "is invalid with errors" do
|
21
|
+
subject.should_not be_valid
|
22
|
+
subject.errors.should_not be_blank
|
23
|
+
errors.each do |k,v|
|
24
|
+
subject.errors[k].should include(v)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe MockPresenter, 'adding errors' do
|
30
|
+
let(:errors) {{ :account_name => "can't be blank" }}
|
31
|
+
|
32
|
+
subject { MockPresenter.new }
|
33
|
+
|
34
|
+
it "can add errors" do
|
35
|
+
subject.add_errors(errors)
|
36
|
+
|
37
|
+
subject.should_not be_valid
|
38
|
+
subject.errors.should_not be_blank
|
39
|
+
errors.each do |k,v|
|
40
|
+
subject.errors[k].should include(v)
|
41
|
+
end
|
42
|
+
subject.should have_errors(errors)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "has no errors by default" do
|
46
|
+
subject.should be_valid
|
47
|
+
subject.errors.should be_blank
|
48
|
+
subject.should have_no_errors
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Presenter do
|
4
|
+
let(:model_name) { 'sign_up' }
|
5
|
+
|
6
|
+
subject { Presenter.new(model_name) }
|
7
|
+
|
8
|
+
it "handles the ActiveModel naming" do
|
9
|
+
subject.class.model_name.singular.should == model_name
|
10
|
+
end
|
11
|
+
|
12
|
+
it "is an ActiveModel conversion" do
|
13
|
+
subject.should_not be_persisted
|
14
|
+
subject.to_model.should == subject
|
15
|
+
subject.to_key.should be_nil
|
16
|
+
subject.to_param.should be_nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Presenter, 'validations' do
|
21
|
+
let(:model_name) { 'sign_up' }
|
22
|
+
let(:fields) { [:a, :b] }
|
23
|
+
let(:failing_validator) do
|
24
|
+
Class.new(ActiveModel::Validator) do
|
25
|
+
def initialize(opts)
|
26
|
+
@@attributes = opts[:attributes]
|
27
|
+
super(opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate(record)
|
31
|
+
record.errors[:base] << 'invalid'
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.has_set_attributes_to?(attribs)
|
35
|
+
@@attributes == attribs
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
let(:succeeding_validator) do
|
40
|
+
Class.new(ActiveModel::Validator) do
|
41
|
+
def validate(record)
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
let(:errors) { {:name => "can't be blank"} }
|
47
|
+
|
48
|
+
it "is valid by default" do
|
49
|
+
presenter = Presenter.new(model_name)
|
50
|
+
presenter.should be_valid
|
51
|
+
presenter.errors.should be_empty
|
52
|
+
end
|
53
|
+
|
54
|
+
it "runs validations as given" do
|
55
|
+
presenter = Presenter.new(model_name,
|
56
|
+
:validator => failing_validator,
|
57
|
+
:fields => fields)
|
58
|
+
presenter.should be_invalid
|
59
|
+
presenter.errors.should_not be_empty
|
60
|
+
failing_validator.should have_set_attributes_to(fields)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "runs validations as given" do
|
64
|
+
presenter = Presenter.new(model_name, :validator => succeeding_validator)
|
65
|
+
presenter.should be_valid
|
66
|
+
presenter.errors.should be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it "adds errors" do
|
70
|
+
presenter = Presenter.new(model_name)
|
71
|
+
presenter.add_errors(errors)
|
72
|
+
presenter.errors.should_not be_blank
|
73
|
+
errors.each do |k,v|
|
74
|
+
presenter.errors[k].should include(v)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe Presenter, "enumerations" do
|
80
|
+
let(:model_name) { 'sign_up' }
|
81
|
+
subject do
|
82
|
+
Presenter.
|
83
|
+
new(model_name, :fields => [:a,:b]).
|
84
|
+
with_params('a' => 'hello', 'b' => 'goodbye')
|
85
|
+
end
|
86
|
+
|
87
|
+
it "eaches" do
|
88
|
+
subject.each do |k,v|
|
89
|
+
fail unless [:a,:b].include?(k)
|
90
|
+
v.should == 'hello' if k == :a
|
91
|
+
v.should == 'goodbye' if k == :b
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "is indexable" do
|
96
|
+
subject[:a].should == 'hello'
|
97
|
+
subject[:b].should == 'goodbye'
|
98
|
+
end
|
99
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'liaison'
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: liaison
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mike Burns
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-10 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activemodel
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
description: An object that works with form_for that encapsulates validations and data management, leaving the business logic up to your testable old self.
|
63
|
+
email:
|
64
|
+
- mike@mike-burns.com
|
65
|
+
executables: []
|
66
|
+
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
extra_rdoc_files: []
|
70
|
+
|
71
|
+
files:
|
72
|
+
- .gitignore
|
73
|
+
- Gemfile
|
74
|
+
- LICENSE
|
75
|
+
- README.md
|
76
|
+
- Rakefile
|
77
|
+
- liaison.gemspec
|
78
|
+
- lib/liaison.rb
|
79
|
+
- lib/liaison/mock_presenter.rb
|
80
|
+
- lib/liaison/presenter.rb
|
81
|
+
- lib/liaison/version.rb
|
82
|
+
- spec/mock_presenter_spec.rb
|
83
|
+
- spec/presenter_spec.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
homepage: https://github.com/mike-burns/liaison
|
86
|
+
licenses:
|
87
|
+
- BSD
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
requirements: []
|
112
|
+
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 1.7.2
|
115
|
+
signing_key:
|
116
|
+
specification_version: 3
|
117
|
+
summary: A Rails presenter class.
|
118
|
+
test_files: []
|
119
|
+
|