mediator 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|