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 CHANGED
@@ -1,6 +1,9 @@
1
1
  # Gluer
2
2
 
3
- A configuration reloader lib.
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
- ## Usage
22
+ ## The problem this gem tries to solve
20
23
 
21
- TODO: Write usage instructions here
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
@@ -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
- yield definition
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
@@ -9,7 +9,7 @@ module Gluer
9
9
  end
10
10
 
11
11
  def get(path)
12
- files.fetch(path)
12
+ files[path]
13
13
  end
14
14
 
15
15
  def clear
@@ -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.add(registration)
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 = OrderedSet.new
24
+ @registrations = []
27
25
  end
28
26
 
29
27
  protected
data/lib/gluer/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Gluer
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -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(&registry_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(&registry_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 { subject.get('path1') }.to_not raise_error
39
- expect { subject.get('path2') }.to_not raise_error
40
- expect { subject.get('path3') }.to_not raise_error
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 { subject.get('path1') }.to_not raise_error
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 key error" do
99
+ specify "getting the unloaded file results in nil" do
99
100
  subject.update
100
- expect { subject.get('path1') }.to raise_error(KeyError)
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 { subject.get('path4') }.to_not raise_error
118
+ expect(subject.get('path4')).to_not be_nil
118
119
  end
119
120
  end
120
121
  end
@@ -1,3 +1,4 @@
1
+ require 'spec_helper'
1
2
  require 'gluer/registration'
2
3
 
3
4
  describe Gluer::Registration do
@@ -52,8 +52,10 @@ describe "Default usage" do
52
52
  end
53
53
  registration.registry { Registry }
54
54
  end
55
+ end
55
56
 
56
- f = File.open("temp_code.rb", "w")
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.open("temp_code.rb", "w")
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.open("temp_code.rb", "w")
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
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-08 00:00:00.000000000 Z
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/file_pool_spec.rb
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: -2018170584366941344
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: -2018170584366941344
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/file_pool_spec.rb
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
@@ -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