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 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