mediator 0.0.0

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/.autotest ADDED
@@ -0,0 +1,63 @@
1
+ require "autotest/restart"
2
+
3
+ Autotest.add_hook :initialize do |at|
4
+ at.testlib = "minitest/autorun"
5
+
6
+ # Autotest's defaults don't match our idiom, so we'll build the
7
+ # mappings by hand.
8
+
9
+ at.clear_mappings
10
+
11
+ # Changed tests always run themselves.
12
+
13
+ at.add_mapping %r{^test/.*_test.rb$} do |f, _|
14
+ [f]
15
+ end
16
+
17
+ # Run corresponding unit tests if something in lib changes.
18
+
19
+ at.add_mapping %r{^(?:lib)/(.*)\.rb$} do |f, m|
20
+ ["test/#{m[1]}_test.rb"]
21
+ end
22
+
23
+ at.add_exception ".autotest"
24
+ at.add_exception ".git"
25
+ at.add_exception "Gemfile"
26
+ at.add_exception "README.md"
27
+ at.add_exception "Rakefile"
28
+ at.add_exception "mediator.gemspec"
29
+ at.add_exception "pkg"
30
+ end
31
+
32
+ class Autotest
33
+ TESTFILES = Dir["test/*_test.rb"]
34
+
35
+ # Because MiniTest::Spec's generated class names (very
36
+ # understandably) can't map reliably to test files, try to guess a
37
+ # file by turning the describe block's class name into a file path
38
+ # and gradually making it more general until something matches.
39
+
40
+ def consolidate_failures failed
41
+ filters = new_hash_of_arrays
42
+
43
+ failed.each do |method, klass|
44
+ fragments = klass.sub(/Spec$/, "").
45
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
46
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
47
+ downcase.split "_"
48
+
49
+ until fragments.empty?
50
+ candidate = "test/#{fragments.join '_'}_test.rb"
51
+
52
+ if TESTFILES.include? candidate
53
+ filters[candidate] << method
54
+ break
55
+ end
56
+
57
+ fragments.pop
58
+ end
59
+ end
60
+
61
+ filters
62
+ end
63
+ end
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Mediator
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
41
+
42
+ ## TODO
43
+
44
+ * Nested collections or objects, like `r.array :albums` or `r.obj
45
+ :contact` in the example above. Should create a new mediator stack
46
+ for each entry in the collection but somehow mark the current
47
+ mediator as the parent.
48
+
49
+ * For `r.ids`, just value.map(&:id). Should check for `_ids` shortcut
50
+ first though, but only if an explicit value isn't provided.
51
+
52
+ * For `r.id :pro`, should first check for `pro_id` and then
53
+ fall back on `pro.id`.
54
+
55
+ * Nested collections should always exist in rendered output even if
56
+ they're empty or missing.
57
+
58
+ * Benchmarks and micro-opts, esp. around object allocation.
59
+
60
+ ## License (MIT)
61
+
62
+ Copyright 2011 Audiosocket (tech@audiosocket.com)
63
+
64
+ Permission is hereby granted, free of charge, to any person obtaining
65
+ a copy of this software and associated documentation files (the
66
+ 'Software'), to deal in the Software without restriction, including
67
+ without limitation the rights to use, copy, modify, merge, publish,
68
+ distribute, sublicense, and/or sell copies of the Software, and to
69
+ permit persons to whom the Software is furnished to do so, subject to
70
+ the following conditions:
71
+
72
+ The above copyright notice and this permission notice shall be
73
+ included in all copies or substantial portions of the Software.
74
+
75
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
76
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
77
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
78
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
79
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
80
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
81
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Run the tests."
4
+ task(:default) { $: << "./lib"; Dir["test/*_test.rb"].each { |f| load f } }
@@ -0,0 +1,33 @@
1
+ class Mediator
2
+ class Parser
3
+ attr_reader :data
4
+ attr_reader :mediator
5
+
6
+ def initialize mediator, data = {}
7
+ @data = data
8
+ @mediator = mediator
9
+ end
10
+
11
+ def get name
12
+ data[name] || data[name.to_s]
13
+ end
14
+
15
+ def has? name
16
+ !!get(name)
17
+ end
18
+
19
+ def key name, options = nil, &block
20
+ selector = (options && options[:from]) || name
21
+ value ||= get(selector) || (options && options[:value])
22
+
23
+ return if value.nil? or value.respond_to?(:empty?) && value.empty?
24
+
25
+ value = block[value] if block_given?
26
+ key! name, value
27
+ end
28
+
29
+ def key! name, value
30
+ mediator.set name, value
31
+ end
32
+ end
33
+ end
data/lib/mediator.rb ADDED
@@ -0,0 +1,148 @@
1
+ require "mediator/parser"
2
+
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.
7
+
8
+ class Mediator
9
+
10
+ # This lambda takes a single argument, does nothing, and returns `nil`.
11
+
12
+ NOTHING = lambda { |_| nil }
13
+
14
+ # State information availble during `parse` and `render`. This is
15
+ # often an application-specific object that provides authentication
16
+ # and authorization data, but it can be just about anything.
17
+
18
+ attr_reader :context
19
+
20
+ # An optional parent mediator. Used during nested mediation: See
21
+ # `array`, `obj`, etc.
22
+
23
+ attr_reader :parent
24
+
25
+ # The subject of this mediation. A rich(er) domain object that needs
26
+ # to be translated back and forth.
27
+
28
+ attr_reader :subject
29
+
30
+ # Create a new mediator with a `subject` and a mediation
31
+ # `context`. If the context is a Mediator, it will be set as this
32
+ # instance's parent and its context will be reused.
33
+
34
+ def initialize subject, context = nil
35
+ @context = context
36
+ @subject = subject
37
+
38
+ if Mediator === context
39
+ @parent = @context
40
+ @context = @parent.context
41
+ end
42
+ end
43
+
44
+ # Is `subject` the subject of one of the ancestral mediators?
45
+
46
+ def inside? subject
47
+ parent && (parent.subject == subject || parent.inside?(subject))
48
+ end
49
+
50
+ # Is this mediator nested inside a `parent`?
51
+
52
+ def nested?
53
+ !!parent
54
+ end
55
+
56
+ # Folds, spindles, and mutilates `data`, then applies to `subject`
57
+ # and returns it. Subclasses should override `parse!` instead of
58
+ # this method.
59
+
60
+ def parse data
61
+ parse! Mediator::Parser.new(self, data)
62
+ subject
63
+ end
64
+
65
+ # The actual parse implementation. Subclasses should override and
66
+ # consistently call `super`. Default implementation is empty.
67
+
68
+ def parse! parser
69
+ end
70
+
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.
74
+
75
+ def render
76
+ raise NotImplementedError, "Implement #{self.class.name}#render."
77
+ end
78
+
79
+ # Set `name` to `value` on `subject`. The default implementation
80
+ # calls a matching mutator method.
81
+
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.
89
+
90
+ def setting name, value
91
+ value
92
+ end
93
+
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
109
+
110
+ nil
111
+ end
112
+
113
+ # An alias of `for`.
114
+
115
+ def self.[] subject, context = nil
116
+ self.for subject, context
117
+ end
118
+
119
+ # A Hash of mappings from subject to Mediator subclass.
120
+
121
+ def self.map
122
+ @@map
123
+ end
124
+
125
+ @@map = {}
126
+
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`.
132
+
133
+ def self.subject *klasses, &block
134
+ raise "Only call subject on #{name} subclasses." if Mediator == self
135
+
136
+ if block_given?
137
+ unless klasses.empty?
138
+ raise ArgumentError, "Can't provide both classes and a block."
139
+ end
140
+
141
+ map[block] = self
142
+ end
143
+
144
+ klasses.each { |k| map[k] = self }
145
+
146
+ self
147
+ end
148
+ end
data/mediator.gemspec ADDED
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.authors = ["Audiosocket"]
3
+ gem.email = ["tech@audiosocket.com"]
4
+ gem.description = "A go-between for models."
5
+ gem.summary = "Translates models to and from primitive representations."
6
+ gem.homepage = "https://github.com/audiosocket/mediator"
7
+
8
+ gem.files = `git ls-files`.split "\n"
9
+ gem.test_files = `git ls-files -- test/*`.split "\n"
10
+ gem.name = "mediator"
11
+ gem.require_paths = ["lib"]
12
+ gem.version = "0.0.0"
13
+
14
+ gem.required_ruby_version = ">= 1.9.2"
15
+ end
@@ -0,0 +1,80 @@
1
+ require "mediator"
2
+ require "mediator/parser"
3
+ require "minitest/autorun"
4
+ require "ostruct"
5
+
6
+ describe Mediator::Parser do
7
+ before do
8
+ @subject = OpenStruct.new
9
+ @mediator = Mediator.new @subject
10
+ end
11
+
12
+ it "has data" do
13
+ p = Mediator::Parser.new :mediator, :data
14
+ assert_equal :data, p.data
15
+ end
16
+
17
+ it "has default data" do
18
+ assert_equal Hash.new, Mediator::Parser.new(:mediator).data
19
+ end
20
+
21
+ describe "declaring keys explicitly" do
22
+ before do
23
+ @parser = Mediator::Parser.new @mediator,
24
+ emptystring: "", emptyarray: [], isnil: nil
25
+ end
26
+
27
+ it "sets unconditionally with key!" do
28
+ @parser.key! :foo, :bar
29
+ assert_equal :bar, @subject.foo
30
+ end
31
+
32
+ it "can pull from the options hash" do
33
+ @parser.key :foo, value: :bar
34
+ assert_equal :bar, @subject.foo
35
+ end
36
+
37
+ it "ignores empty values" do
38
+ @parser.key :emptystring
39
+ assert_nil @subject.emptystring
40
+
41
+ @parser.key :emptyarray
42
+ assert_nil @subject.emptyarray
43
+ end
44
+
45
+ it "ignores nil values" do
46
+ @parser.key :isnil
47
+ assert_nil @subject.isnil # heh
48
+ end
49
+ end
50
+
51
+ describe "setting values from data" do
52
+ it "grabs from data like it's a Hash" do
53
+ p = Mediator::Parser.new @mediator, foo: "bar"
54
+ p.key :foo
55
+
56
+ assert_equal "bar", @subject.foo
57
+ end
58
+
59
+ it "works with strings and symbols" do
60
+ p = Mediator::Parser.new @mediator, "foo" => "bar"
61
+ p.key :foo
62
+
63
+ assert_equal "bar", @subject.foo
64
+ end
65
+
66
+ it "transforms values with a block" do
67
+ p = Mediator::Parser.new @mediator, foo: "bar"
68
+ p.key(:foo) { |d| d.upcase }
69
+
70
+ assert_equal "BAR", @subject.foo
71
+ end
72
+
73
+ it "can declaratively alias a name with :from" do
74
+ p = Mediator::Parser.new @mediator, quux: "bar"
75
+ p.key :foo, from: :quux
76
+
77
+ assert_equal "bar", @subject.foo
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,203 @@
1
+ require "mediator"
2
+ require "minitest/autorun"
3
+ require "ostruct"
4
+
5
+ describe Mediator do
6
+ before do
7
+ Mediator.map.clear
8
+ end
9
+
10
+ describe "initialization" do
11
+ it "takes a subject and a context" do
12
+ m = Mediator.new :subject, :context
13
+
14
+ assert_equal :context, m.context
15
+ assert_equal :subject, m.subject
16
+ end
17
+
18
+ it "optionally takes a parent context" do
19
+ m = Mediator.new :s, :c
20
+ n = Mediator.new :t, m
21
+
22
+ assert_equal :c, n.context
23
+ assert_equal m, n.parent
24
+ end
25
+ end
26
+
27
+ describe "nesting" do
28
+ it "is inside another mediator when a parent exists" do
29
+ m = Mediator.new :s, :c
30
+
31
+ refute m.nested?
32
+ assert Mediator.new(:s, m).nested?
33
+ end
34
+
35
+ it "can narrow inside? by checking subjects" do
36
+ m = Mediator.new :s, :c
37
+ n = Mediator.new :t, m
38
+ o = Mediator.new :u, n
39
+
40
+ assert n.inside? :s
41
+ assert o.inside? :s
42
+ end
43
+ end
44
+
45
+ describe "parsing" do
46
+ it "is a noop by default" do
47
+ m = Mediator.new :subject
48
+ m.parse :data
49
+ end
50
+
51
+ it "builds a Parser and delegates to parse!" do
52
+ c = Class.new Mediator do
53
+ def parse! parser
54
+ parser.key :foo
55
+ end
56
+ end
57
+
58
+ s = OpenStruct.new
59
+ m = c.new s
60
+
61
+ m.parse foo: :bar
62
+ assert_equal :bar, s.foo
63
+ end
64
+
65
+ it "always returns the subject" do
66
+ c = Class.new Mediator do
67
+ def self.parser
68
+ lambda { |p| :useless! }
69
+ end
70
+ end
71
+
72
+ m = c.new :subject
73
+ assert_equal :subject, m.parse(:data)
74
+ end
75
+ end
76
+
77
+ it "will render at some point" do
78
+ ex = assert_raises NotImplementedError do
79
+ Mediator.new(:s).render
80
+ end
81
+
82
+ assert_equal "Implement Mediator#render.", ex.message
83
+ end
84
+
85
+ describe "setting values on the subject" do
86
+ it "calls a setter by default" do
87
+ s = OpenStruct.new
88
+ m = Mediator.new s
89
+
90
+ m.set :foo, :bar
91
+ assert_equal :bar, s.foo
92
+ end
93
+
94
+ it "can transform incoming values" do
95
+ c = Class.new Mediator do
96
+ def setting name, value
97
+ case value
98
+ when String then value.upcase
99
+ else value
100
+ end
101
+ end
102
+ end
103
+
104
+ s = OpenStruct.new
105
+ m = c.new s
106
+
107
+ m.set :foo, "bar"
108
+ assert_equal "BAR", s.foo
109
+ end
110
+ end
111
+
112
+ describe ".for" do
113
+ it "gets a mediator by class" do
114
+ c = Class.new(Mediator) { subject Symbol }
115
+ m = Mediator.for :foo
116
+
117
+ assert_instance_of c, m
118
+ end
119
+
120
+ it "gets a mediator for a subclass" do
121
+ x = Class.new
122
+ y = Class.new x
123
+ c = Class.new(Mediator) { subject x }
124
+ m = Mediator.for y.new
125
+
126
+ assert_instance_of c, m
127
+ end
128
+
129
+ it "gets a mediator by block eval" do
130
+ c = Class.new Mediator do
131
+ subject do |s|
132
+ "hello" == s
133
+ end
134
+ end
135
+
136
+ assert_instance_of c, Mediator.for("hello")
137
+ assert_nil Mediator.for "Hello!"
138
+ end
139
+
140
+ it "is also available as []" do
141
+ c = Class.new(Mediator) { subject Symbol }
142
+ assert_instance_of c, Mediator[:foo]
143
+ end
144
+ end
145
+
146
+ describe ".subject" do
147
+ it "can only be called on subclasses" do
148
+ ex = assert_raises RuntimeError do
149
+ Mediator.subject
150
+ end
151
+
152
+ assert_equal "Only call subject on Mediator subclasses.", ex.message
153
+ end
154
+
155
+ it "can register a class" do
156
+ c = Class.new Mediator do
157
+ subject Symbol
158
+ end
159
+
160
+ assert_equal c, Mediator.map[Symbol]
161
+ end
162
+
163
+ it "can register multiple classes" do
164
+ c = Class.new Mediator do
165
+ subject String, Symbol
166
+ end
167
+
168
+ assert_equal c, Mediator.map[String]
169
+ assert_equal c, Mediator.map[Symbol]
170
+ end
171
+
172
+ it "can register with a block" do
173
+ c = Class.new Mediator do
174
+ subject do |s|
175
+ Symbol === s
176
+ end
177
+ end
178
+
179
+ b = Mediator.map.keys.first
180
+ refute_nil b
181
+
182
+ assert b[:foo]
183
+ refute b["bar"]
184
+ end
185
+
186
+ it "doesn't allow classes and a block to be mixed" do
187
+ ex = assert_raises ArgumentError do
188
+ Class.new Mediator do
189
+ subject String do
190
+ # ...
191
+ end
192
+ end
193
+ end
194
+
195
+ assert_equal "Can't provide both classes and a block.", ex.message
196
+ end
197
+
198
+ it "returns self" do
199
+ c = Class.new Mediator
200
+ assert_equal c, c.subject
201
+ end
202
+ end
203
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mediator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Audiosocket
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-09 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: A go-between for models.
15
+ email:
16
+ - tech@audiosocket.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .autotest
22
+ - .gitignore
23
+ - Gemfile
24
+ - README.md
25
+ - Rakefile
26
+ - lib/mediator.rb
27
+ - lib/mediator/parser.rb
28
+ - mediator.gemspec
29
+ - test/mediator_parser_test.rb
30
+ - test/mediator_test.rb
31
+ homepage: https://github.com/audiosocket/mediator
32
+ licenses: []
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: 1.9.2
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.10
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Translates models to and from primitive representations.
55
+ test_files:
56
+ - test/mediator_parser_test.rb
57
+ - test/mediator_test.rb