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 CHANGED
@@ -1,43 +1,6 @@
1
1
  # Mediator
2
2
 
3
- ## Notes on Render
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 Audiosocket (tech@audiosocket.com)
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
@@ -1,4 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
3
  desc "Run the tests."
4
- task(:default) { $: << "./lib"; Dir["test/*_test.rb"].each { |f| load f } }
4
+ task(:test) { $: << "./lib"; Dir["test/*_test.rb"].each { |f| load f } }
5
+
6
+ task :default => :test
@@ -0,0 +1,7 @@
1
+ class Mediator
2
+
3
+ # A RuntimeError subclass for Mediator exceptions.
4
+
5
+ class Error < RuntimeError
6
+ end
7
+ end
@@ -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
- data[name] || data[name.to_s]
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
- selector = (options && options[:from]) || name
21
- value ||= get(selector) || (options && options[:value])
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
- return if value.nil? or value.respond_to?(:empty?) && value.empty?
43
+ Mediator[subj, mediator].parse data
44
+ block[subj] if block
24
45
 
25
- value = block[value] if block_given?
26
- key! name, value
46
+ subj
27
47
  end
28
48
 
29
- def key! name, value
30
- mediator.set name, value
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,8 @@
1
+ class Mediator
2
+ class Processor
3
+ def empty? value, options = nil
4
+ !(options && options[:empty]) &&
5
+ (value.nil? || value.respond_to?(:empty?) && value.empty?)
6
+ end
7
+ end
8
+ 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
- # Helps translate an object to and from more primitive, untyped
4
- # forms. Mediators should subclass and implement `parse` and `render`,
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
- # `array`, `obj`, etc.
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!` instead of
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! Mediator::Parser.new(self, data)
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`. Default implementation is empty.
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
- # Create a more primitive representation of `subject`. Depending on
72
- # app design and `context`, the output of `render` may not always be
73
- # valid input to `parse`. Implement in subclasses.
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 render
76
- raise NotImplementedError, "Implement #{self.class.name}#render."
107
+ def parser data
108
+ Mediator::Parser.new self, data
77
109
  end
78
110
 
79
- # Set `name` to `value` on `subject`. The default implementation
80
- # calls a matching mutator method.
111
+ # Create a more primitive representation of `subject`. Subclasses
112
+ # should generally override `render!` instead of this method.
81
113
 
82
- def set name, value
83
- subject.send "#{name}=", setting(name, value)
84
- end
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
- def setting name, value
91
- value
118
+ outgoing r.data
92
119
  end
93
120
 
94
- # Find and instantiate a mediator for `subject` by consulting
95
- # `map`. Returns `nil` if no mediator can be found. Inheritance is
96
- # respected for mediators registered by class, so:
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
- nil
125
+ def render! renderer
126
+ mediate! renderer
111
127
  end
112
128
 
113
- # An alias of `for`.
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 self.[] subject, context = nil
116
- self.for subject, context
133
+ def renderer
134
+ Mediator::Renderer.new self
117
135
  end
118
136
 
119
- # A Hash of mappings from subject to Mediator subclass.
137
+ # Gets the `name` property from `subject`. The default
138
+ # implementation calls the `name` method if it exists.
120
139
 
121
- def self.map
122
- @@map
123
- end
140
+ def get name
141
+ value = subject.send name if subject.respond_to? name
142
+ value ||= construct name
124
143
 
125
- @@map = {}
144
+ getting name, value if value
145
+ end
126
146
 
127
- # Register this Mediator subclass's interest in one or more subject
128
- # classes. If more detailed selection behavior is necessary,
129
- # `subject` can take a block instead. When the mediator for a
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 self.subject *klasses, &block
134
- raise "Only call subject on #{name} subclasses." if Mediator == self
151
+ def getting name, value
152
+ value
153
+ end
135
154
 
136
- if block_given?
137
- unless klasses.empty?
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
- map[block] = self
142
- end
158
+ def set name, value
159
+ subject.send "#{name}=", setting(name, value)
160
+ end
143
161
 
144
- klasses.each { |k| map[k] = self }
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
- self
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 = ["Audiosocket"]
3
- gem.email = ["tech@audiosocket.com"]
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/audiosocket/mediator"
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.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 "declaring keys explicitly" do
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 key!" do
28
- @parser.key! :foo, :bar
29
- assert_equal :bar, @subject.foo
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 "setting values from data" do
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
@@ -43,15 +43,32 @@ describe Mediator do
43
43
  end
44
44
 
45
45
  describe "parsing" do
46
- it "is a noop by default" do
46
+ it "raises NotImplementedError by default" do
47
47
  m = Mediator.new :subject
48
- m.parse :data
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! parser
54
- parser.key :foo
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 self.parser
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
- it "will render at some point" do
78
- ex = assert_raises NotImplementedError do
79
- Mediator.new(:s).render
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
- assert_equal "Implement Mediator#render.", ex.message
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(Mediator) { subject Symbol }
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
- c = Class.new(Mediator) { subject x }
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 do
131
- subject do |s|
132
- "hello" == s
133
- end
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(Mediator) { subject Symbol }
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
- describe ".subject" do
147
- it "can only be called on subclasses" do
148
- ex = assert_raises RuntimeError do
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 "Only call subject on Mediator subclasses.", ex.message
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
- subject Symbol
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 do
165
- subject String, Symbol
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 do
174
- subject do |s|
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
- Class.new Mediator do
189
- subject String do
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 classes and a block.", ex.message
284
+ assert_equal "Can't provide both a subject and a block.", ex.message
196
285
  end
197
286
 
198
- it "returns self" do
287
+ it "returns the registered thing" do
199
288
  c = Class.new Mediator
200
- assert_equal c, c.subject
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.0
4
+ version: 0.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - Audiosocket
8
+ - John Barnette
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-09 00:00:00.000000000Z
12
+ date: 2012-01-17 00:00:00.000000000Z
13
13
  dependencies: []
14
14
  description: A go-between for models.
15
15
  email:
16
- - tech@audiosocket.com
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/audiosocket/mediator
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