gluer 0.0.4 → 0.0.5
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 +137 -3
- data/lib/gluer/api.rb +1 -1
- data/lib/gluer/configuration.rb +1 -1
- data/lib/gluer/dsl.rb +10 -2
- data/lib/gluer/file_pool.rb +1 -1
- data/lib/gluer/registration_pool.rb +2 -4
- data/lib/gluer/version.rb +1 -1
- data/spec/gluer/dsl_spec.rb +74 -0
- data/spec/{file_pool_spec.rb → gluer/file_pool_spec.rb} +8 -7
- data/spec/{registration_spec.rb → gluer/registration_spec.rb} +1 -0
- data/spec/integration/default_usage_spec.rb +5 -3
- metadata +10 -9
- data/lib/gluer/ordered_set.rb +0 -23
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Gluer
|
2
2
|
|
3
|
-
A configuration
|
3
|
+
A configuration reload tool. Useful when you want to keep registration code
|
4
|
+
next to the objects being registered, in an enviroment where another library is
|
5
|
+
already doing code reload for you. Gluer provides you a way to unregister and
|
6
|
+
re-register configuration code as the code's file is reloaded.
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
@@ -16,9 +19,140 @@ Or install it yourself as:
|
|
16
19
|
|
17
20
|
$ gem install gluer
|
18
21
|
|
19
|
-
##
|
22
|
+
## The problem this gem tries to solve
|
20
23
|
|
21
|
-
|
24
|
+
Let's suppose you are using a library which holds registrations in a global
|
25
|
+
registry and its usage is something along these lines:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'foo_registry'
|
29
|
+
|
30
|
+
class MyFoo
|
31
|
+
# MyFoo code...
|
32
|
+
end
|
33
|
+
|
34
|
+
FooRegistry.add_foo(MyFoo, as: 'bar') { MyBaz.init! }
|
35
|
+
```
|
36
|
+
|
37
|
+
This is simple and harmless, until you start to use some tool or lib to reload
|
38
|
+
code for you, like ActiveSupport. If that code is put in a file which is
|
39
|
+
reloaded in every request in your app (as in development mode) you'll end up
|
40
|
+
with many registrations of `MyFoo` in `FooRegistry`. After two requests:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
FooRegistry.get_all_foos
|
44
|
+
#=> [[MyFoo, 'bar', #<Proc:0x...>], [MyFoo, 'bar', #<Proc:0x...>]]
|
45
|
+
```
|
46
|
+
|
47
|
+
And this sometimes is not good even when developing.
|
48
|
+
|
49
|
+
## A solution
|
50
|
+
|
51
|
+
The way Gluer allows you to escape from this is by enclosing the registration
|
52
|
+
code in a block:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class MyFoo
|
56
|
+
# MyFoo code...
|
57
|
+
end
|
58
|
+
|
59
|
+
Gluer.setup(MyFoo) do
|
60
|
+
foo 'bar' { MyBaz.init! }
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
That way that registration happens once even after many requests. Well, it
|
65
|
+
actually happens in every request, but gets properly removed before being
|
66
|
+
added again. So, after many requests you'll get this:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
FooRegistry.get_all_foos
|
70
|
+
#=> [[MyFoo, 'bar', #<Proc:0x...>]]
|
71
|
+
```
|
72
|
+
|
73
|
+
But firstly, you must configure Gluer in order to make it recognize that
|
74
|
+
`foo` call. If you are using Rails, this goes well in an initializer file, or
|
75
|
+
in a place where you are sure that `MyFoo`'s file was not loaded yet. Assuming
|
76
|
+
that the class `MyFoo`, its configuration and every other code we want Gluer to
|
77
|
+
look at are defined in `app/` folder:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
require 'foo_registry'
|
81
|
+
require 'gluer'
|
82
|
+
|
83
|
+
# Basic configuration
|
84
|
+
Gluer.configure do |config|
|
85
|
+
# The base path is the root to start searchin files which contain
|
86
|
+
# registration code to be reloaded. In this example we'll lookup files only
|
87
|
+
# in 'app/' folder in a Rails app.
|
88
|
+
config.base_path = File.join(Rails.root, 'app')
|
89
|
+
|
90
|
+
# The file loader is a Proc that will be called to load each file found.
|
91
|
+
# In a Rails app you probably want to use ActiveSupport.
|
92
|
+
# Defaults to ->(f) { load(f) }.
|
93
|
+
config.file_loader = ->(f) { ActiveSupport::Dependencies.depend_on(f) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Registration definitions. Here we define the `foo` method.
|
97
|
+
Gluer.define_registration :foo do |registration|
|
98
|
+
registration.on_commit do |registry, context, arg, &block|
|
99
|
+
registry.add_foo(context, as: arg, &block)
|
100
|
+
end
|
101
|
+
|
102
|
+
registration.on_rollback do |registry, context, arg, &block|
|
103
|
+
registry.remove_foo(context, as: arg, &block)
|
104
|
+
end
|
105
|
+
|
106
|
+
registration.registry { FooRegistry }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Put other registration definitions here.
|
110
|
+
```
|
111
|
+
|
112
|
+
The commit hook is called when the registration is to be performed. `registry`
|
113
|
+
is, as you may guess, is the `FooRegistry` object. `context` is the argument
|
114
|
+
given to `Gluer.setup`, in this case the `MyFoo` class object. All remaining
|
115
|
+
arguments and block are forwarded from the call to `foo` in `Gluer.setup`'s
|
116
|
+
block.
|
117
|
+
|
118
|
+
The rollback hook receives the same arguments as the commit hook.
|
119
|
+
|
120
|
+
Next, add some code to do a first load. This could go in your environment.rb
|
121
|
+
file if you are using Rails:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
config.after_initialize do
|
125
|
+
Gluer.reload # initial loading
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
Lastly, in a place that runs early in every request (like a ``before_filter``
|
130
|
+
in `ApplicationController`, if you're using Rails):
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
before_filter do
|
134
|
+
unless Rails.configuration.cache_classes
|
135
|
+
# If classes were cached, our initializer already has loaded everything
|
136
|
+
# Gluer is interested in, and we don't need the overhead of loading stuff
|
137
|
+
# in every request.
|
138
|
+
Gluer.reload
|
139
|
+
end
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
When the file containing `MyFoo` is reloaded, the previous registration is
|
144
|
+
rolled back, and a new registration is done. This keeps the registry
|
145
|
+
`FooRegistry` free of repetitions. In fact, you must know that `Gluer.reload`
|
146
|
+
will load that file eagerly, instead of letting your reloader lib do that for
|
147
|
+
you lazily (even if you customize the file loader, as in the example given).
|
148
|
+
|
149
|
+
## Caveats
|
150
|
+
|
151
|
+
1. `FooRegistry` must provide a way to unregister.
|
152
|
+
2. It uses `grep` to get the files with `Gluer.setup`. Probably a problem in
|
153
|
+
Windows. Tested only in Linux.
|
154
|
+
3. Loads the found files eagerly. So, you should account for the side effects
|
155
|
+
of this.
|
22
156
|
|
23
157
|
## Contributing
|
24
158
|
|
data/lib/gluer/api.rb
CHANGED
@@ -5,7 +5,7 @@ module Gluer
|
|
5
5
|
class << self
|
6
6
|
def setup(context=nil, &block)
|
7
7
|
path = block.binding.eval('__FILE__')
|
8
|
-
file = file_pool.get(path)
|
8
|
+
return unless file = file_pool.get(path)
|
9
9
|
collect_registrations(context, block) do |registration|
|
10
10
|
file.add_registration(registration)
|
11
11
|
end
|
data/lib/gluer/configuration.rb
CHANGED
@@ -28,7 +28,7 @@ module Gluer
|
|
28
28
|
|
29
29
|
def default_file_filter
|
30
30
|
Proc.new do |base_path, magic_signature|
|
31
|
-
output = %x{cd '#{base_path}' && grep -IlFr '#{magic_signature}' --exclude-dir 'spec' .}
|
31
|
+
output = %x{cd '#{base_path}' && grep -IlFr '#{magic_signature}' --include=*.rb --exclude-dir 'spec' .}
|
32
32
|
output.lines.map do |line|
|
33
33
|
::File.expand_path(line.chomp, base_path)
|
34
34
|
end
|
data/lib/gluer/dsl.rb
CHANGED
@@ -2,9 +2,13 @@ require "gluer/registration"
|
|
2
2
|
require "gluer/registration_definition"
|
3
3
|
|
4
4
|
module Gluer
|
5
|
-
def self.define_registration(name)
|
5
|
+
def self.define_registration(name, &block)
|
6
6
|
definition = RegistrationDefinition.new(name)
|
7
|
-
|
7
|
+
if block.arity == 1
|
8
|
+
block.call(definition)
|
9
|
+
else
|
10
|
+
definition.instance_exec(&block)
|
11
|
+
end
|
8
12
|
DSL.add_registration_definition(name, definition)
|
9
13
|
end
|
10
14
|
|
@@ -24,6 +28,10 @@ module Gluer
|
|
24
28
|
defined_registrations.fetch(name)
|
25
29
|
end
|
26
30
|
|
31
|
+
def clear
|
32
|
+
@defined_registrations = nil
|
33
|
+
end
|
34
|
+
|
27
35
|
private
|
28
36
|
|
29
37
|
def defined_registrations
|
data/lib/gluer/file_pool.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'gluer/ordered_set'
|
2
|
-
|
3
1
|
module Gluer
|
4
2
|
class RegistrationPool
|
5
3
|
def initialize
|
@@ -15,7 +13,7 @@ module Gluer
|
|
15
13
|
end
|
16
14
|
|
17
15
|
def add(registration)
|
18
|
-
registrations.
|
16
|
+
registrations.push(registration)
|
19
17
|
end
|
20
18
|
|
21
19
|
def replace(registration_pool)
|
@@ -23,7 +21,7 @@ module Gluer
|
|
23
21
|
end
|
24
22
|
|
25
23
|
def clear
|
26
|
-
@registrations =
|
24
|
+
@registrations = []
|
27
25
|
end
|
28
26
|
|
29
27
|
protected
|
data/lib/gluer/version.rb
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gluer/dsl'
|
3
|
+
|
4
|
+
describe Gluer::DSL do
|
5
|
+
describe "Gluer.define_registration" do
|
6
|
+
let(:commit_block) { Proc.new { |_, _| commit_called } }
|
7
|
+
let(:rollback_block) { Proc.new { |_, _| rollback_called } }
|
8
|
+
let(:registry_block) { Proc.new { registry_called } }
|
9
|
+
|
10
|
+
before do
|
11
|
+
Gluer.define_registration(:foo, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
Gluer::DSL.clear
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when block given doesn't accept an argument" do
|
19
|
+
let(:block) do
|
20
|
+
# Just adding these to the current scope. The block will be
|
21
|
+
# instance-eval'ed.
|
22
|
+
commit_block = commit_block()
|
23
|
+
rollback_block = rollback_block()
|
24
|
+
registry_block = registry_block()
|
25
|
+
|
26
|
+
lambda do
|
27
|
+
on_commit(&commit_block)
|
28
|
+
on_rollback(&rollback_block)
|
29
|
+
registry(®istry_block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "adds the registration" do
|
34
|
+
should_receive(:commit_called)
|
35
|
+
should_receive(:rollback_called)
|
36
|
+
should_receive(:registry_called)
|
37
|
+
|
38
|
+
reg_def = Gluer::DSL.get_registration_definition(:foo)
|
39
|
+
|
40
|
+
reg_def.commit_hook.call(stub('registry'), stub('context'))
|
41
|
+
reg_def.rollback_hook.call(stub('registry'), stub('context'))
|
42
|
+
reg_def.registry_factory.call
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when block given accepts a single argument" do
|
47
|
+
let(:block) do
|
48
|
+
# Just adding these to the current scope. The block will be
|
49
|
+
# instance-eval'ed.
|
50
|
+
commit_block = commit_block()
|
51
|
+
rollback_block = rollback_block()
|
52
|
+
registry_block = registry_block()
|
53
|
+
|
54
|
+
lambda do |registration|
|
55
|
+
registration.on_commit(&commit_block)
|
56
|
+
registration.on_rollback(&rollback_block)
|
57
|
+
registration.registry(®istry_block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "adds the registration" do
|
62
|
+
should_receive(:commit_called)
|
63
|
+
should_receive(:rollback_called)
|
64
|
+
should_receive(:registry_called)
|
65
|
+
|
66
|
+
reg_def = Gluer::DSL.get_registration_definition(:foo)
|
67
|
+
|
68
|
+
reg_def.commit_hook.call(stub('registry'), stub('context'))
|
69
|
+
reg_def.rollback_hook.call(stub('registry'), stub('context'))
|
70
|
+
reg_def.registry_factory.call
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'spec_helper'
|
1
2
|
require 'gluer/file_pool'
|
2
3
|
require 'set'
|
3
4
|
|
@@ -35,9 +36,9 @@ describe Gluer::FilePool do
|
|
35
36
|
end
|
36
37
|
|
37
38
|
it "makes them available from #get" do
|
38
|
-
expect
|
39
|
-
expect
|
40
|
-
expect
|
39
|
+
expect(subject.get('path1')).to_not be_nil
|
40
|
+
expect(subject.get('path2')).to_not be_nil
|
41
|
+
expect(subject.get('path3')).to_not be_nil
|
41
42
|
end
|
42
43
|
|
43
44
|
context "when filtering doesn't change in next call" do
|
@@ -59,7 +60,7 @@ describe Gluer::FilePool do
|
|
59
60
|
|
60
61
|
it "keeps existing files available from #get" do
|
61
62
|
subject.update
|
62
|
-
expect
|
63
|
+
expect(subject.get('path1')).to_not be_nil
|
63
64
|
end
|
64
65
|
|
65
66
|
it "reloads existing files" do
|
@@ -95,9 +96,9 @@ describe Gluer::FilePool do
|
|
95
96
|
subject.update
|
96
97
|
end
|
97
98
|
|
98
|
-
specify "getting the unloaded file results in
|
99
|
+
specify "getting the unloaded file results in nil" do
|
99
100
|
subject.update
|
100
|
-
expect
|
101
|
+
expect(subject.get('path1')).to be_nil
|
101
102
|
end
|
102
103
|
|
103
104
|
it "reloads existing files" do
|
@@ -114,7 +115,7 @@ describe Gluer::FilePool do
|
|
114
115
|
|
115
116
|
it "makes the new file available from #get" do
|
116
117
|
subject.update
|
117
|
-
expect
|
118
|
+
expect(subject.get('path4')).to_not be_nil
|
118
119
|
end
|
119
120
|
end
|
120
121
|
end
|
@@ -52,8 +52,10 @@ describe "Default usage" do
|
|
52
52
|
end
|
53
53
|
registration.registry { Registry }
|
54
54
|
end
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
+
before do
|
58
|
+
f = File.new("temp_code.rb", "w")
|
57
59
|
f.write(<<-CODE)
|
58
60
|
require 'gluer'
|
59
61
|
Context = Object.new
|
@@ -86,7 +88,7 @@ describe "Default usage" do
|
|
86
88
|
before do
|
87
89
|
Gluer.reload
|
88
90
|
|
89
|
-
f = File.
|
91
|
+
f = File.new("temp_code.rb", "w")
|
90
92
|
f.write(<<-CODE)
|
91
93
|
require 'gluer'
|
92
94
|
Context = Object.new
|
@@ -115,7 +117,7 @@ describe "Default usage" do
|
|
115
117
|
before do
|
116
118
|
Gluer.reload
|
117
119
|
|
118
|
-
f = File.
|
120
|
+
f = File.new("temp_code.rb", "w")
|
119
121
|
f.write(<<-CODE)
|
120
122
|
require 'gluer'
|
121
123
|
CODE
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gluer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -112,16 +112,16 @@ files:
|
|
112
112
|
- lib/gluer/dsl.rb
|
113
113
|
- lib/gluer/file.rb
|
114
114
|
- lib/gluer/file_pool.rb
|
115
|
-
- lib/gluer/ordered_set.rb
|
116
115
|
- lib/gluer/registration.rb
|
117
116
|
- lib/gluer/registration_collection.rb
|
118
117
|
- lib/gluer/registration_definition.rb
|
119
118
|
- lib/gluer/registration_hook.rb
|
120
119
|
- lib/gluer/registration_pool.rb
|
121
120
|
- lib/gluer/version.rb
|
122
|
-
- spec/
|
121
|
+
- spec/gluer/dsl_spec.rb
|
122
|
+
- spec/gluer/file_pool_spec.rb
|
123
|
+
- spec/gluer/registration_spec.rb
|
123
124
|
- spec/integration/default_usage_spec.rb
|
124
|
-
- spec/registration_spec.rb
|
125
125
|
- spec/spec_helper.rb
|
126
126
|
homepage: ''
|
127
127
|
licenses:
|
@@ -138,7 +138,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
138
138
|
version: '0'
|
139
139
|
segments:
|
140
140
|
- 0
|
141
|
-
hash:
|
141
|
+
hash: 2279684241105677342
|
142
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
143
|
none: false
|
144
144
|
requirements:
|
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
147
|
version: '0'
|
148
148
|
segments:
|
149
149
|
- 0
|
150
|
-
hash:
|
150
|
+
hash: 2279684241105677342
|
151
151
|
requirements: []
|
152
152
|
rubyforge_project:
|
153
153
|
rubygems_version: 1.8.25
|
@@ -155,7 +155,8 @@ signing_key:
|
|
155
155
|
specification_version: 3
|
156
156
|
summary: Configuration reloader
|
157
157
|
test_files:
|
158
|
-
- spec/
|
158
|
+
- spec/gluer/dsl_spec.rb
|
159
|
+
- spec/gluer/file_pool_spec.rb
|
160
|
+
- spec/gluer/registration_spec.rb
|
159
161
|
- spec/integration/default_usage_spec.rb
|
160
|
-
- spec/registration_spec.rb
|
161
162
|
- spec/spec_helper.rb
|
data/lib/gluer/ordered_set.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
module Gluer
|
2
|
-
class OrderedSet
|
3
|
-
include Enumerable
|
4
|
-
|
5
|
-
def initialize(initial=[])
|
6
|
-
@collection = initial
|
7
|
-
end
|
8
|
-
|
9
|
-
def each
|
10
|
-
@collection.each { |item| yield(item) }
|
11
|
-
end
|
12
|
-
|
13
|
-
def add(new)
|
14
|
-
@collection.delete_if { |existing| existing == new }
|
15
|
-
@collection << new
|
16
|
-
end
|
17
|
-
|
18
|
-
def -(other)
|
19
|
-
raise ArgumentError unless other.is_a?(OrderedSet)
|
20
|
-
self.class.new(@collection - other.instance_eval { @collection })
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|