mediator 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -42
- data/Rakefile +3 -1
- data/lib/mediator/errors.rb +7 -0
- data/lib/mediator/parser.rb +31 -10
- data/lib/mediator/processor.rb +8 -0
- data/lib/mediator/registry.rb +76 -0
- data/lib/mediator/renderer.rb +53 -0
- data/lib/mediator.rb +89 -68
- data/mediator.gemspec +4 -4
- data/test/mediator_parser_test.rb +101 -6
- data/test/mediator_renderer_test.rb +143 -0
- data/test/mediator_test.rb +130 -41
- metadata +11 -5
data/README.md
CHANGED
@@ -1,43 +1,6 @@
|
|
1
1
|
# Mediator
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
# taken from the old notation in a bunch of different models, just
|
6
|
-
# examples of shit that needs to work
|
7
|
-
|
8
|
-
def render! r
|
9
|
-
|
10
|
-
# from Account
|
11
|
-
|
12
|
-
if context.root?
|
13
|
-
r.key :color
|
14
|
-
r.key :key, self[:domain]
|
15
|
-
end
|
16
|
-
|
17
|
-
if subject.is? :sso
|
18
|
-
r.key :sso, private: sso_private
|
19
|
-
end
|
20
|
-
|
21
|
-
# from Album
|
22
|
-
|
23
|
-
if inside? artist
|
24
|
-
r.key :artist, id: artist.id, name: artist.name
|
25
|
-
end
|
26
|
-
|
27
|
-
# from Artist
|
28
|
-
|
29
|
-
r.array :albums
|
30
|
-
r.obj :contact
|
31
|
-
|
32
|
-
# from Collaborator
|
33
|
-
|
34
|
-
r.ids :tracks, tracks.active
|
35
|
-
r.id :pro # WAS: a.set :pro, pro.id if pro
|
36
|
-
|
37
|
-
# from Model
|
38
|
-
|
39
|
-
r.key :created, from: :created_at
|
40
|
-
end
|
3
|
+
I sit between your domain model and the cold, cruel world.
|
41
4
|
|
42
5
|
## TODO
|
43
6
|
|
@@ -49,17 +12,16 @@
|
|
49
12
|
* For `r.ids`, just value.map(&:id). Should check for `_ids` shortcut
|
50
13
|
first though, but only if an explicit value isn't provided.
|
51
14
|
|
52
|
-
* For `r.id :pro`, should first check for `pro_id` and then
|
53
|
-
fall back on `pro.id`.
|
54
|
-
|
55
15
|
* Nested collections should always exist in rendered output even if
|
56
16
|
they're empty or missing.
|
57
17
|
|
58
18
|
* Benchmarks and micro-opts, esp. around object allocation.
|
59
19
|
|
20
|
+
* Decent doco.
|
21
|
+
|
60
22
|
## License (MIT)
|
61
23
|
|
62
|
-
Copyright 2011
|
24
|
+
Copyright 2011 John Barnette (code@jbarnette.com)
|
63
25
|
|
64
26
|
Permission is hereby granted, free of charge, to any person obtaining
|
65
27
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
data/lib/mediator/parser.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require "mediator/processor"
|
2
|
+
|
1
3
|
class Mediator
|
2
|
-
class Parser
|
4
|
+
class Parser < Processor
|
3
5
|
attr_reader :data
|
4
6
|
attr_reader :mediator
|
5
7
|
|
@@ -8,26 +10,45 @@ class Mediator
|
|
8
10
|
@mediator = mediator
|
9
11
|
end
|
10
12
|
|
11
|
-
def get name
|
12
|
-
|
13
|
+
def get name, options = nil
|
14
|
+
selector = (options && options[:from]) || name
|
15
|
+
(options && options[:value]) || data[selector] || data[selector.to_s]
|
13
16
|
end
|
14
17
|
|
15
18
|
def has? name
|
16
19
|
!!get(name)
|
17
20
|
end
|
18
21
|
|
22
|
+
def id name, options = {}
|
23
|
+
key "#{name}_id", options.merge(from: name)
|
24
|
+
end
|
25
|
+
|
19
26
|
def key name, options = nil, &block
|
20
|
-
|
21
|
-
|
27
|
+
if name[-1] == "?"
|
28
|
+
name = name[0..-2].intern
|
29
|
+
end
|
30
|
+
|
31
|
+
value = get name, options
|
32
|
+
|
33
|
+
return if empty? value, options
|
34
|
+
mediator.set name, block ? block[value] : value
|
35
|
+
end
|
36
|
+
|
37
|
+
def obj name, options = nil, &block
|
38
|
+
data = get name, options
|
39
|
+
subj = (options && options[:subject]) || mediator.get(name)
|
40
|
+
|
41
|
+
return if empty? data, options or subj.nil?
|
22
42
|
|
23
|
-
|
43
|
+
Mediator[subj, mediator].parse data
|
44
|
+
block[subj] if block
|
24
45
|
|
25
|
-
|
26
|
-
key! name, value
|
46
|
+
subj
|
27
47
|
end
|
28
48
|
|
29
|
-
def
|
30
|
-
|
49
|
+
def union name, options = nil, &block
|
50
|
+
(options ||= {}).merge! value: self.data
|
51
|
+
obj name, options, &block
|
31
52
|
end
|
32
53
|
end
|
33
54
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "mediator/errors"
|
2
|
+
|
3
|
+
class Mediator
|
4
|
+
module Registry
|
5
|
+
|
6
|
+
# Sugar for `register`.
|
7
|
+
#
|
8
|
+
# class FooMediator < Mediator
|
9
|
+
# accept Foo # same as Mediator.register FooMediator, Foo
|
10
|
+
# end
|
11
|
+
|
12
|
+
def accept *subjects, &block
|
13
|
+
register self, *subjects, &block
|
14
|
+
end
|
15
|
+
|
16
|
+
# Find and instantiate a mediator for `subject` by consulting
|
17
|
+
# `map`. Returns `nil` if no mediator can be found. Inheritance is
|
18
|
+
# respected for mediators registered by class, so:
|
19
|
+
#
|
20
|
+
# A = Class.new
|
21
|
+
# B = Class.new A
|
22
|
+
# M = Class.new Mediator
|
23
|
+
#
|
24
|
+
# M.subject A
|
25
|
+
# Mediator.for B.new # => A
|
26
|
+
#
|
27
|
+
# Mediators are searched in reverse insertion order.
|
28
|
+
|
29
|
+
def for subject, context = nil
|
30
|
+
map.keys.reverse.each do |criteria|
|
31
|
+
return map[criteria].new subject, context if criteria === subject
|
32
|
+
end
|
33
|
+
|
34
|
+
raise Error, "Can't find a Mediator for #{subject.inspect}."
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sugar for `for`.
|
38
|
+
|
39
|
+
def [] subject, context = nil
|
40
|
+
self.for subject, context
|
41
|
+
end
|
42
|
+
|
43
|
+
# A map from subject class or block to Mediator subclass.
|
44
|
+
|
45
|
+
def map
|
46
|
+
@@map ||= {}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Sugar for creating and registering a Mediator subclass.
|
50
|
+
|
51
|
+
def mediate *subjects, &block
|
52
|
+
mediator = Class.new self, &block
|
53
|
+
register mediator, *subjects
|
54
|
+
end
|
55
|
+
|
56
|
+
# Register a Mediator subclass's interest in one or more subject
|
57
|
+
# classes. If more detailed selection behavior is necessary,
|
58
|
+
# `subject` can take a block instead. When the mediator for a
|
59
|
+
# subject is discovered with `Mediator.for` the given block will be
|
60
|
+
# passed the subject and can return `true` or `false`.
|
61
|
+
|
62
|
+
def register mklass, *subjects, &block
|
63
|
+
if block_given?
|
64
|
+
unless subjects.empty?
|
65
|
+
raise ArgumentError, "Can't provide both a subject and a block."
|
66
|
+
end
|
67
|
+
|
68
|
+
map[block] = mklass
|
69
|
+
end
|
70
|
+
|
71
|
+
subjects.each { |k| map[k] = mklass }
|
72
|
+
|
73
|
+
mklass
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "mediator/processor"
|
2
|
+
|
3
|
+
class Mediator
|
4
|
+
class Renderer < Processor
|
5
|
+
attr_reader :data
|
6
|
+
attr_reader :mediator
|
7
|
+
|
8
|
+
def initialize mediator, data = nil
|
9
|
+
@data = data || {}
|
10
|
+
@mediator = mediator
|
11
|
+
end
|
12
|
+
|
13
|
+
def get name, options = nil
|
14
|
+
selector = (options && options[:from]) || name
|
15
|
+
(options && options[:value]) || mediator.get(selector)
|
16
|
+
end
|
17
|
+
|
18
|
+
def id name, options = {}
|
19
|
+
key name, options.merge(from: "#{name}_id")
|
20
|
+
end
|
21
|
+
|
22
|
+
def key name, options = nil, &block
|
23
|
+
if name[-1] == "?"
|
24
|
+
(options ||= {})[:from] = name
|
25
|
+
name = name[0..-2].intern
|
26
|
+
end
|
27
|
+
|
28
|
+
value = get name, options
|
29
|
+
return if empty? value, options
|
30
|
+
|
31
|
+
data[name] = block ? block[value] : value
|
32
|
+
end
|
33
|
+
|
34
|
+
def obj name, options = nil, &block
|
35
|
+
value = get name, options
|
36
|
+
return if empty? value, options
|
37
|
+
|
38
|
+
if value
|
39
|
+
rendered = Mediator[value, mediator].render
|
40
|
+
munged = block ? block[rendered] : rendered
|
41
|
+
merge = options && options[:merge]
|
42
|
+
|
43
|
+
merge ? data.merge!(munged) : data[name] = munged
|
44
|
+
munged
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def union name, options = nil, &block
|
49
|
+
(options ||= {}).merge! merge: true
|
50
|
+
obj name, options, &block
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/mediator.rb
CHANGED
@@ -1,15 +1,13 @@
|
|
1
|
+
require "mediator/errors"
|
1
2
|
require "mediator/parser"
|
3
|
+
require "mediator/registry"
|
4
|
+
require "mediator/renderer"
|
2
5
|
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# or use the class-level helpers to declare the necessary
|
6
|
-
# transformations.
|
6
|
+
# Can't we all just get along? Mediators should subclass and implement
|
7
|
+
# `parse!` and `render!`.
|
7
8
|
|
8
9
|
class Mediator
|
9
|
-
|
10
|
-
# This lambda takes a single argument, does nothing, and returns `nil`.
|
11
|
-
|
12
|
-
NOTHING = lambda { |_| nil }
|
10
|
+
extend Registry
|
13
11
|
|
14
12
|
# State information availble during `parse` and `render`. This is
|
15
13
|
# often an application-specific object that provides authentication
|
@@ -18,7 +16,7 @@ class Mediator
|
|
18
16
|
attr_reader :context
|
19
17
|
|
20
18
|
# An optional parent mediator. Used during nested mediation: See
|
21
|
-
# `
|
19
|
+
# `obj`, etc.
|
22
20
|
|
23
21
|
attr_reader :parent
|
24
22
|
|
@@ -41,108 +39,131 @@ class Mediator
|
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
42
|
+
# Called during `get` if `subject` doesn't have a value for `name`
|
43
|
+
# during parsing. Subclasses can override to provide factories for
|
44
|
+
# dependent attributes. The default implementation returns `nil`.
|
45
|
+
|
46
|
+
def construct name
|
47
|
+
end
|
48
|
+
|
49
|
+
# Called before passing `data` to `parse`. Subclasses can override
|
50
|
+
# to transform raw incoming data. The default implementation returns
|
51
|
+
# `data` unchanged.
|
52
|
+
|
53
|
+
def incoming data
|
54
|
+
data
|
55
|
+
end
|
56
|
+
|
44
57
|
# Is `subject` the subject of one of the ancestral mediators?
|
45
58
|
|
46
59
|
def inside? subject
|
47
60
|
parent && (parent.subject == subject || parent.inside?(subject))
|
48
61
|
end
|
49
62
|
|
63
|
+
# Two-way mediation. Mediators whose parsing and rendering
|
64
|
+
# operations are identical should override and consistently call
|
65
|
+
# `super`. `mediator` will be `parser` or `renderer`. The default
|
66
|
+
# implementation raises NotImplementedError.
|
67
|
+
|
68
|
+
def mediate! mediator
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
50
72
|
# Is this mediator nested inside a `parent`?
|
51
73
|
|
52
74
|
def nested?
|
53
75
|
!!parent
|
54
76
|
end
|
55
77
|
|
78
|
+
# Called after rendering. Subclasses can override to transform raw
|
79
|
+
# outgoing data. The default implementation returns `data`
|
80
|
+
# unchanged.
|
81
|
+
|
82
|
+
def outgoing data
|
83
|
+
data
|
84
|
+
end
|
85
|
+
|
56
86
|
# Folds, spindles, and mutilates `data`, then applies to `subject`
|
57
|
-
# and returns it. Subclasses should override `parse!`
|
58
|
-
# this method.
|
87
|
+
# and returns it. Subclasses should generally override `parse!`
|
88
|
+
# instead of this method.
|
59
89
|
|
60
90
|
def parse data
|
61
|
-
parse!
|
91
|
+
parse! parser incoming data
|
62
92
|
subject
|
63
93
|
end
|
64
94
|
|
65
95
|
# The actual parse implementation. Subclasses should override and
|
66
|
-
# consistently call `super`.
|
96
|
+
# consistently call `super`. The default implementation calls
|
97
|
+
# `mediate!`.
|
67
98
|
|
68
99
|
def parse! parser
|
100
|
+
mediate! parser
|
69
101
|
end
|
70
102
|
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
103
|
+
# Construct a parser instance for `data`. The parser will be passed
|
104
|
+
# to the Mediator's `parse!` method. The default implementation
|
105
|
+
# returns a new instance of Mediator::Parser.
|
74
106
|
|
75
|
-
def
|
76
|
-
|
107
|
+
def parser data
|
108
|
+
Mediator::Parser.new self, data
|
77
109
|
end
|
78
110
|
|
79
|
-
#
|
80
|
-
#
|
111
|
+
# Create a more primitive representation of `subject`. Subclasses
|
112
|
+
# should generally override `render!` instead of this method.
|
81
113
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
# Called when setting `name` to `value` on `subject`. Can be used to
|
87
|
-
# transform incoming values, e.g., trimming all strings. The default
|
88
|
-
# implementation returns `value` unchanged.
|
114
|
+
def render
|
115
|
+
r = renderer
|
116
|
+
render! r
|
89
117
|
|
90
|
-
|
91
|
-
value
|
118
|
+
outgoing r.data
|
92
119
|
end
|
93
120
|
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
# A = Class.new
|
99
|
-
# B = Class.new A
|
100
|
-
# M = Class.new Mediator
|
101
|
-
#
|
102
|
-
# M.subject A
|
103
|
-
# Mediator.for B.new # => A
|
104
|
-
|
105
|
-
def self.for subject, context = nil
|
106
|
-
map.each do |criteria, mediator|
|
107
|
-
return mediator.new subject, context if criteria === subject
|
108
|
-
end
|
121
|
+
# The actual render implementation. Subclasses should override and
|
122
|
+
# consistently call `super`. The default implementation calls
|
123
|
+
# `mediate!`.
|
109
124
|
|
110
|
-
|
125
|
+
def render! renderer
|
126
|
+
mediate! renderer
|
111
127
|
end
|
112
128
|
|
113
|
-
#
|
129
|
+
# Construct a renderer instance. The renderer will be passed to the
|
130
|
+
# Mediator's `render!` method. The default implementation returns a
|
131
|
+
# new instance of Mediator::Renderer.
|
114
132
|
|
115
|
-
def
|
116
|
-
|
133
|
+
def renderer
|
134
|
+
Mediator::Renderer.new self
|
117
135
|
end
|
118
136
|
|
119
|
-
#
|
137
|
+
# Gets the `name` property from `subject`. The default
|
138
|
+
# implementation calls the `name` method if it exists.
|
120
139
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
140
|
+
def get name
|
141
|
+
value = subject.send name if subject.respond_to? name
|
142
|
+
value ||= construct name
|
124
143
|
|
125
|
-
|
144
|
+
getting name, value if value
|
145
|
+
end
|
126
146
|
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# subject is discovered with `Mediator.for` the given block will be
|
131
|
-
# passed the subject and can return `true` or `false`.
|
147
|
+
# Called when getting `name` from `subject`. Can be used to
|
148
|
+
# transform outgoing values, e.g., turning Time objects into epoch
|
149
|
+
# seconds. The default implementation returns `value` unchanged.
|
132
150
|
|
133
|
-
def
|
134
|
-
|
151
|
+
def getting name, value
|
152
|
+
value
|
153
|
+
end
|
135
154
|
|
136
|
-
|
137
|
-
|
138
|
-
raise ArgumentError, "Can't provide both classes and a block."
|
139
|
-
end
|
155
|
+
# Set `name` to `value` on `subject`. The default implementation
|
156
|
+
# calls a matching mutator method.
|
140
157
|
|
141
|
-
|
142
|
-
|
158
|
+
def set name, value
|
159
|
+
subject.send "#{name}=", setting(name, value)
|
160
|
+
end
|
143
161
|
|
144
|
-
|
162
|
+
# Called when setting `name` to `value` on `subject`. Can be used to
|
163
|
+
# transform incoming values, e.g., trimming all strings. The default
|
164
|
+
# implementation returns `value` unchanged.
|
145
165
|
|
146
|
-
|
166
|
+
def setting name, value
|
167
|
+
value
|
147
168
|
end
|
148
169
|
end
|
data/mediator.gemspec
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
|
-
gem.authors = ["
|
3
|
-
gem.email = ["
|
2
|
+
gem.authors = ["John Barnette"]
|
3
|
+
gem.email = ["code@jbarnette.com"]
|
4
4
|
gem.description = "A go-between for models."
|
5
5
|
gem.summary = "Translates models to and from primitive representations."
|
6
|
-
gem.homepage = "https://github.com/
|
6
|
+
gem.homepage = "https://github.com/jbarnette/mediator"
|
7
7
|
|
8
8
|
gem.files = `git ls-files`.split "\n"
|
9
9
|
gem.test_files = `git ls-files -- test/*`.split "\n"
|
10
10
|
gem.name = "mediator"
|
11
11
|
gem.require_paths = ["lib"]
|
12
|
-
gem.version = "0.0.
|
12
|
+
gem.version = "0.0.1"
|
13
13
|
|
14
14
|
gem.required_ruby_version = ">= 1.9.2"
|
15
15
|
end
|
@@ -5,6 +5,8 @@ require "ostruct"
|
|
5
5
|
|
6
6
|
describe Mediator::Parser do
|
7
7
|
before do
|
8
|
+
Mediator.map.clear
|
9
|
+
|
8
10
|
@subject = OpenStruct.new
|
9
11
|
@mediator = Mediator.new @subject
|
10
12
|
end
|
@@ -18,17 +20,35 @@ describe Mediator::Parser do
|
|
18
20
|
assert_equal Hash.new, Mediator::Parser.new(:mediator).data
|
19
21
|
end
|
20
22
|
|
21
|
-
describe "
|
23
|
+
describe "id" do
|
24
|
+
it "translates name to subject.name_id" do
|
25
|
+
p = Mediator::Parser.new @mediator, foo: 42
|
26
|
+
|
27
|
+
p.id :foo
|
28
|
+
assert_equal 42, @subject.foo_id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "key" do
|
22
33
|
before do
|
23
34
|
@parser = Mediator::Parser.new @mediator,
|
24
|
-
emptystring: "", emptyarray: [], isnil: nil
|
35
|
+
emptystring: "", emptyarray: [], isnil: nil,
|
36
|
+
predicate: true
|
25
37
|
end
|
26
38
|
|
27
|
-
it "sets unconditionally with
|
28
|
-
@
|
29
|
-
|
39
|
+
it "sets unconditionally with empty: true" do
|
40
|
+
@subject.foo = :foo
|
41
|
+
|
42
|
+
@parser.key :foo, empty: true
|
43
|
+
assert_nil @subject.foo
|
44
|
+
end
|
45
|
+
|
46
|
+
it "removes trailing '?' from predicates" do
|
47
|
+
@parser.key :predicate?
|
48
|
+
assert @subject.predicate
|
30
49
|
end
|
31
50
|
|
51
|
+
|
32
52
|
it "can pull from the options hash" do
|
33
53
|
@parser.key :foo, value: :bar
|
34
54
|
assert_equal :bar, @subject.foo
|
@@ -48,7 +68,7 @@ describe Mediator::Parser do
|
|
48
68
|
end
|
49
69
|
end
|
50
70
|
|
51
|
-
describe "
|
71
|
+
describe "key with actual data" do
|
52
72
|
it "grabs from data like it's a Hash" do
|
53
73
|
p = Mediator::Parser.new @mediator, foo: "bar"
|
54
74
|
p.key :foo
|
@@ -77,4 +97,79 @@ describe Mediator::Parser do
|
|
77
97
|
assert_equal "bar", @subject.foo
|
78
98
|
end
|
79
99
|
end
|
100
|
+
|
101
|
+
describe "obj" do
|
102
|
+
before do
|
103
|
+
Top = Class.new OpenStruct
|
104
|
+
Nest = Class.new OpenStruct
|
105
|
+
|
106
|
+
Class.new Mediator do
|
107
|
+
accept Top
|
108
|
+
|
109
|
+
def parse! p
|
110
|
+
p.key :foo
|
111
|
+
p.obj :nest
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
Class.new Mediator do
|
116
|
+
accept Nest
|
117
|
+
|
118
|
+
def parse! p
|
119
|
+
p.key :baz
|
120
|
+
p.key :quux
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it "delegates to a nested mediator" do
|
126
|
+
s = Top.new
|
127
|
+
s.nest = Nest.new
|
128
|
+
|
129
|
+
m = Mediator[s]
|
130
|
+
d = { foo: "foo!", nest: { baz: "baz!", quux: "quux!" } }
|
131
|
+
|
132
|
+
m.parse d
|
133
|
+
|
134
|
+
assert_equal d[:foo], s.foo
|
135
|
+
assert_equal d[:nest][:baz], s.nest.baz
|
136
|
+
assert_equal d[:nest][:quux], s.nest.quux
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "union" do
|
141
|
+
it "uses the same data as its parent" do
|
142
|
+
First = Class.new OpenStruct
|
143
|
+
Second = Class.new OpenStruct
|
144
|
+
|
145
|
+
Class.new Mediator do
|
146
|
+
accept First
|
147
|
+
|
148
|
+
def parse! p
|
149
|
+
p.key :first
|
150
|
+
p.union :unioned
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
Class.new Mediator do
|
155
|
+
accept Second
|
156
|
+
|
157
|
+
def parse! p
|
158
|
+
p.key :second
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
f = First.new
|
163
|
+
s = Second.new
|
164
|
+
|
165
|
+
f.unioned = s
|
166
|
+
|
167
|
+
m = Mediator[f]
|
168
|
+
|
169
|
+
m.parse first: "foo", second: "bar"
|
170
|
+
|
171
|
+
assert_equal "foo", f.first
|
172
|
+
assert_equal "bar", s.second
|
173
|
+
end
|
174
|
+
end
|
80
175
|
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require "mediator"
|
2
|
+
require "mediator/renderer"
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "ostruct"
|
5
|
+
|
6
|
+
describe Mediator::Renderer do
|
7
|
+
before do
|
8
|
+
Mediator.map.clear
|
9
|
+
end
|
10
|
+
|
11
|
+
it "has data" do
|
12
|
+
assert_equal Hash.new, Mediator::Renderer.new(nil).data
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "key" do
|
16
|
+
it "grabs the value from the subject" do
|
17
|
+
c = Class.new Mediator do
|
18
|
+
def render! r
|
19
|
+
r.key :foo
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
s = OpenStruct.new foo: "bar"
|
24
|
+
m = c.new s
|
25
|
+
d = m.render
|
26
|
+
|
27
|
+
assert_equal "bar", d[:foo]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "removes trailing ? from predicates" do
|
31
|
+
c = Class.new Mediator do
|
32
|
+
def render! r
|
33
|
+
r.key :foo?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
s = OpenStruct.new :foo? => true
|
38
|
+
m = c.new s
|
39
|
+
d = m.render
|
40
|
+
|
41
|
+
assert d[:foo]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "ignores nil or empty values" do
|
45
|
+
c = Class.new Mediator do
|
46
|
+
def render! r
|
47
|
+
r.key :foo
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
s = OpenStruct.new
|
52
|
+
m = c.new s
|
53
|
+
d = m.render
|
54
|
+
|
55
|
+
assert_equal Hash.new, d
|
56
|
+
end
|
57
|
+
|
58
|
+
it "optionally allows empty values" do
|
59
|
+
c = Class.new Mediator do
|
60
|
+
def render! r
|
61
|
+
r.key :foo, empty: true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
s = OpenStruct.new
|
66
|
+
m = c.new s
|
67
|
+
d = m.render
|
68
|
+
e = { foo: nil }
|
69
|
+
|
70
|
+
assert_equal e, d
|
71
|
+
end
|
72
|
+
|
73
|
+
it "optionally maps names" do
|
74
|
+
c = Class.new Mediator do
|
75
|
+
def render! r
|
76
|
+
r.key :foo, from: :bar
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
s = OpenStruct.new bar: "baz"
|
81
|
+
m = c.new s
|
82
|
+
d = m.render
|
83
|
+
|
84
|
+
assert_equal "baz", d[:foo]
|
85
|
+
end
|
86
|
+
|
87
|
+
it "can alter values" do
|
88
|
+
c = Class.new Mediator do
|
89
|
+
def render! r
|
90
|
+
r.key(:foo) { |v| v.upcase }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
s = OpenStruct.new foo: "bar"
|
95
|
+
m = c.new s
|
96
|
+
r = Mediator::Renderer.new s
|
97
|
+
d = m.render
|
98
|
+
|
99
|
+
assert_equal "BAR", d[:foo]
|
100
|
+
end
|
101
|
+
|
102
|
+
it "can explicitly define a value" do
|
103
|
+
c = Class.new Mediator do
|
104
|
+
def render! r
|
105
|
+
r.key :foo, value: "bar"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
s = OpenStruct.new
|
110
|
+
m = c.new s
|
111
|
+
d = m.render
|
112
|
+
|
113
|
+
assert_equal "bar", d[:foo]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "obj" do
|
118
|
+
it "allows mediation of an associated object" do
|
119
|
+
c = Class.new Mediator do
|
120
|
+
def render! r
|
121
|
+
r.obj :foo
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
d = Class.new Mediator do
|
126
|
+
accept OpenStruct
|
127
|
+
|
128
|
+
def render! r
|
129
|
+
r.key :bar
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
x = OpenStruct.new bar: "baz"
|
134
|
+
s = OpenStruct.new foo: x
|
135
|
+
|
136
|
+
m = c.new s
|
137
|
+
d = m.render
|
138
|
+
e = { foo: { bar: "baz" } }
|
139
|
+
|
140
|
+
assert_equal e, d
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/test/mediator_test.rb
CHANGED
@@ -43,15 +43,32 @@ describe Mediator do
|
|
43
43
|
end
|
44
44
|
|
45
45
|
describe "parsing" do
|
46
|
-
it "
|
46
|
+
it "raises NotImplementedError by default" do
|
47
47
|
m = Mediator.new :subject
|
48
|
-
|
48
|
+
|
49
|
+
assert_raises NotImplementedError do
|
50
|
+
m.parse :data
|
51
|
+
end
|
49
52
|
end
|
50
53
|
|
51
54
|
it "builds a Parser and delegates to parse!" do
|
52
55
|
c = Class.new Mediator do
|
53
|
-
def parse!
|
54
|
-
|
56
|
+
def parse! p
|
57
|
+
p.key :foo
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
s = OpenStruct.new
|
62
|
+
m = c.new s
|
63
|
+
|
64
|
+
m.parse foo: :bar
|
65
|
+
assert_equal :bar, s.foo
|
66
|
+
end
|
67
|
+
|
68
|
+
it "falls back on mediate!" do
|
69
|
+
c = Class.new Mediator do
|
70
|
+
def mediate! m
|
71
|
+
m.key :foo
|
55
72
|
end
|
56
73
|
end
|
57
74
|
|
@@ -64,22 +81,57 @@ describe Mediator do
|
|
64
81
|
|
65
82
|
it "always returns the subject" do
|
66
83
|
c = Class.new Mediator do
|
67
|
-
def
|
68
|
-
lambda { |p| :useless! }
|
84
|
+
def parse! p
|
69
85
|
end
|
70
86
|
end
|
71
87
|
|
72
88
|
m = c.new :subject
|
73
89
|
assert_equal :subject, m.parse(:data)
|
74
90
|
end
|
91
|
+
|
92
|
+
it "can define a custom parser" do
|
93
|
+
c = Class.new Mediator do
|
94
|
+
def parse! p
|
95
|
+
p.poke
|
96
|
+
end
|
97
|
+
|
98
|
+
def parser data
|
99
|
+
p = Struct.new(:subject) do
|
100
|
+
def poke
|
101
|
+
subject.poked = true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
p.new subject
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
s = OpenStruct.new
|
110
|
+
m = c.new s
|
111
|
+
|
112
|
+
m.parse nil
|
113
|
+
assert s.poked
|
114
|
+
end
|
75
115
|
end
|
76
116
|
|
77
|
-
|
78
|
-
|
79
|
-
|
117
|
+
describe "getting values from the subject" do
|
118
|
+
it "calls a getter by default" do
|
119
|
+
s = OpenStruct.new foo: :bar
|
120
|
+
m = Mediator.new s
|
121
|
+
|
122
|
+
assert_equal :bar, m.get(:foo)
|
80
123
|
end
|
81
124
|
|
82
|
-
|
125
|
+
it "can construct missing values" do
|
126
|
+
c = Class.new Mediator do
|
127
|
+
def construct name
|
128
|
+
"HELLO" if :foo == name
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
m = c.new OpenStruct.new
|
133
|
+
assert_equal "HELLO", m.get(:foo)
|
134
|
+
end
|
83
135
|
end
|
84
136
|
|
85
137
|
describe "setting values on the subject" do
|
@@ -109,9 +161,20 @@ describe Mediator do
|
|
109
161
|
end
|
110
162
|
end
|
111
163
|
|
164
|
+
describe ".accept" do
|
165
|
+
it "is just subclass sugar for .register" do
|
166
|
+
c = Class.new Mediator
|
167
|
+
c.accept Symbol
|
168
|
+
|
169
|
+
assert_instance_of c, Mediator[:foo]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
112
173
|
describe ".for" do
|
113
174
|
it "gets a mediator by class" do
|
114
|
-
c = Class.new
|
175
|
+
c = Class.new Mediator
|
176
|
+
Mediator.register c, Symbol
|
177
|
+
|
115
178
|
m = Mediator.for :foo
|
116
179
|
|
117
180
|
assert_instance_of c, m
|
@@ -120,61 +183,89 @@ describe Mediator do
|
|
120
183
|
it "gets a mediator for a subclass" do
|
121
184
|
x = Class.new
|
122
185
|
y = Class.new x
|
123
|
-
|
186
|
+
|
187
|
+
c = Class.new Mediator
|
188
|
+
Mediator.register c, x
|
189
|
+
|
124
190
|
m = Mediator.for y.new
|
125
191
|
|
126
192
|
assert_instance_of c, m
|
127
193
|
end
|
128
194
|
|
129
195
|
it "gets a mediator by block eval" do
|
130
|
-
c = Class.new Mediator
|
131
|
-
|
132
|
-
|
133
|
-
|
196
|
+
c = Class.new Mediator
|
197
|
+
|
198
|
+
Mediator.register c do |s|
|
199
|
+
"hello" == s
|
134
200
|
end
|
135
201
|
|
136
202
|
assert_instance_of c, Mediator.for("hello")
|
137
|
-
assert_nil Mediator.for "Hello!"
|
138
203
|
end
|
139
204
|
|
140
205
|
it "is also available as []" do
|
141
|
-
c = Class.new
|
206
|
+
c = Class.new Mediator
|
207
|
+
Mediator.register c, Symbol
|
208
|
+
|
142
209
|
assert_instance_of c, Mediator[:foo]
|
143
210
|
end
|
144
|
-
end
|
145
211
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
Mediator.subject
|
212
|
+
it "raises when there's no mediator" do
|
213
|
+
ex = assert_raises Mediator::Error do
|
214
|
+
Mediator.for :foo
|
150
215
|
end
|
151
216
|
|
152
|
-
assert_equal "
|
217
|
+
assert_equal "Can't find a Mediator for :foo.", ex.message
|
153
218
|
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe ".mediate" do
|
222
|
+
it "is just sugar for subclass creation and registration" do
|
223
|
+
A = Class.new OpenStruct
|
224
|
+
B = Class.new A
|
154
225
|
|
155
|
-
it "can register a class" do
|
156
226
|
c = Class.new Mediator do
|
157
|
-
|
227
|
+
accept A
|
228
|
+
|
229
|
+
def parse! p
|
230
|
+
p.key :foo
|
231
|
+
end
|
158
232
|
end
|
159
233
|
|
234
|
+
c.mediate B do
|
235
|
+
def parse! p
|
236
|
+
super
|
237
|
+
p.key :bar
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
i = B.new
|
242
|
+
|
243
|
+
Mediator[i].parse foo: "foo", bar: "bar"
|
244
|
+
|
245
|
+
assert_equal "foo", i.foo
|
246
|
+
assert_equal "bar", i.bar
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe ".register" do
|
251
|
+
it "can register a class" do
|
252
|
+
c = Class.new Mediator
|
253
|
+
Mediator.register c, Symbol
|
254
|
+
|
160
255
|
assert_equal c, Mediator.map[Symbol]
|
161
256
|
end
|
162
257
|
|
163
258
|
it "can register multiple classes" do
|
164
|
-
c = Class.new Mediator
|
165
|
-
|
166
|
-
end
|
259
|
+
c = Class.new Mediator
|
260
|
+
Mediator.register c, String, Symbol
|
167
261
|
|
168
262
|
assert_equal c, Mediator.map[String]
|
169
263
|
assert_equal c, Mediator.map[Symbol]
|
170
264
|
end
|
171
265
|
|
172
266
|
it "can register with a block" do
|
173
|
-
c = Class.new Mediator
|
174
|
-
|
175
|
-
Symbol === s
|
176
|
-
end
|
177
|
-
end
|
267
|
+
c = Class.new Mediator
|
268
|
+
Mediator.register(c) { |s| Symbol === s }
|
178
269
|
|
179
270
|
b = Mediator.map.keys.first
|
180
271
|
refute_nil b
|
@@ -185,19 +276,17 @@ describe Mediator do
|
|
185
276
|
|
186
277
|
it "doesn't allow classes and a block to be mixed" do
|
187
278
|
ex = assert_raises ArgumentError do
|
188
|
-
|
189
|
-
|
190
|
-
# ...
|
191
|
-
end
|
279
|
+
Mediator.register :whatev, String do
|
280
|
+
# ...
|
192
281
|
end
|
193
282
|
end
|
194
283
|
|
195
|
-
assert_equal "Can't provide both
|
284
|
+
assert_equal "Can't provide both a subject and a block.", ex.message
|
196
285
|
end
|
197
286
|
|
198
|
-
it "returns
|
287
|
+
it "returns the registered thing" do
|
199
288
|
c = Class.new Mediator
|
200
|
-
assert_equal c, c
|
289
|
+
assert_equal c, Mediator.register(c, String)
|
201
290
|
end
|
202
291
|
end
|
203
292
|
end
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mediator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
-
|
8
|
+
- John Barnette
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-17 00:00:00.000000000Z
|
13
13
|
dependencies: []
|
14
14
|
description: A go-between for models.
|
15
15
|
email:
|
16
|
-
-
|
16
|
+
- code@jbarnette.com
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
@@ -24,11 +24,16 @@ files:
|
|
24
24
|
- README.md
|
25
25
|
- Rakefile
|
26
26
|
- lib/mediator.rb
|
27
|
+
- lib/mediator/errors.rb
|
27
28
|
- lib/mediator/parser.rb
|
29
|
+
- lib/mediator/processor.rb
|
30
|
+
- lib/mediator/registry.rb
|
31
|
+
- lib/mediator/renderer.rb
|
28
32
|
- mediator.gemspec
|
29
33
|
- test/mediator_parser_test.rb
|
34
|
+
- test/mediator_renderer_test.rb
|
30
35
|
- test/mediator_test.rb
|
31
|
-
homepage: https://github.com/
|
36
|
+
homepage: https://github.com/jbarnette/mediator
|
32
37
|
licenses: []
|
33
38
|
post_install_message:
|
34
39
|
rdoc_options: []
|
@@ -54,4 +59,5 @@ specification_version: 3
|
|
54
59
|
summary: Translates models to and from primitive representations.
|
55
60
|
test_files:
|
56
61
|
- test/mediator_parser_test.rb
|
62
|
+
- test/mediator_renderer_test.rb
|
57
63
|
- test/mediator_test.rb
|