mediator 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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