activesupport-decorators 1.0.1 → 1.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ac0e53aa4f0f2a5552ebdffb9bc6f8862bfb583
4
- data.tar.gz: 0bc4b6985d4c867c732345478627e3ed2c69be1f
3
+ metadata.gz: 68468583a43ef2fe9365a6d4f47f6e96a5cb2520
4
+ data.tar.gz: fa6523c3c0aafa1a4c5d669e415037a5fdd89bef
5
5
  SHA512:
6
- metadata.gz: a7ea6a25f3474820c952786c12d9c04d6062ee8bc398b2b003170432561ef479bc82d32a3367f86640349c5d204da5ca4832078e4eeccad10163fe1129b3bb46
7
- data.tar.gz: d68066a4e76725ffd03bfd4efa174f7fed47ebc686f006c9e458b551ef311a6d72bee37e8f039b492b942f87fdd612612d2df3154a4b8a8559bcd49d9a93c6a3
6
+ metadata.gz: fc7f2882249afc13d84c940f50e2d3fb7d721d5d489b217cedf7a5d85b3c091597838e7d785c667995e94e11fff3f5a9c64872352dbef8ae6130bb0915dac20a
7
+ data.tar.gz: f1705d7f814348c16b4bd62506fc1f304351d6c2ab87f3a88dfe2cc4c1e8d374ff12917a43e85f690b76dbbe7080b621133f8736b49a850702374b03d5502c75
data/README.md CHANGED
@@ -4,11 +4,10 @@ ActiveSupport Decorators
4
4
  The decorator pattern is particularly useful when extending constants in rails engines or vice versa. To implement
5
5
  the decorator pattern, you need to load the decorator after the original file has been loaded. When you reference a
6
6
  class in a Rails application, ActiveSupport will only load the first file it finds that matches the class name. This
7
- means that you will need to manually load the additional (decorator) file. Usually you don't want to want to introduce
8
- hard dependencies such as require statements. You also don't want to preload a bunch of classes in a Rails initializer.
9
- This is a tiny gem that provides you with a simple way to specify load file dependencies.
7
+ means that you need to manually load the additional (decorator) file or eager load all of them on application startup.
8
+ This is a tiny gem that provides you with a simple way to tell ActiveSupport to load your decorator files when needed.
10
9
 
11
- ## Installation
10
+ ### Installation
12
11
 
13
12
  Add it to your Gemfile and run bundle install:
14
13
 
@@ -16,7 +15,7 @@ Add it to your Gemfile and run bundle install:
16
15
  gem 'activesupport-decorators', '~> 1.0'
17
16
  ```
18
17
 
19
- ## Example 1 - Engine extends application class.
18
+ #### Example 1 - Engine extends application class.
20
19
 
21
20
  Your main rails application defines a model called Pet (in app/models/pet.rb):
22
21
 
@@ -26,7 +25,8 @@ end
26
25
  ```
27
26
 
28
27
  Your rails engine adds the concept of pet owners to the application. You extend the Pet model in the engine with
29
- the following model decorator (in my_engine/app/models/pet_decorator.rb).
28
+ the following model decorator (in my_engine/app/models/pet_decorator.rb). Note that you could use 'class Pet' instead
29
+ of 'Pet.class_eval do'.
30
30
 
31
31
  ```Ruby
32
32
  Pet.class_eval do
@@ -49,7 +49,7 @@ module MyEngine
49
49
  end
50
50
  ```
51
51
 
52
- ## Example 2 - Application extends engine class.
52
+ #### Example 2 - Application extends engine class.
53
53
 
54
54
  Similar to the example above except the initializer is placed in the main application instead of the engine. Create a
55
55
  file called config/initializers/set_decorator_dependencies.rb (or any other name) with content:
@@ -58,21 +58,21 @@ file called config/initializers/set_decorator_dependencies.rb (or any other name
58
58
  ActiveSupportDecorators.add("#{MyEngine::Rails::Engine.root}/app", "#{Rails.root}/app")
59
59
  ```
60
60
 
61
- ## Example 3 - Engine extends another engine class.
61
+ #### Example 3 - Engine extends another engine class.
62
62
 
63
63
  ```Ruby
64
64
  module MyEngine
65
65
  module Rails
66
66
  class Engine < ::Rails::Engine
67
67
  initializer :set_decorator_dependencies do |app|
68
- ActiveSupportDecorators.add("#{AnotherEngine::Rails::Engine.root}/app", "#{Rails.root}/app")
68
+ ActiveSupportDecorators.add("#{AnotherEngine::Rails::Engine.root}/app", "#{MyEngine::Rails::Engine.root}/app")
69
69
  end
70
70
  end
71
71
  end
72
72
  end
73
73
  ```
74
74
 
75
- ## Debugging
75
+ ### Debugging
76
76
 
77
77
  Need to know which decorator files are loaded? Enable debug output:
78
78
 
@@ -80,32 +80,18 @@ Need to know which decorator files are loaded? Enable debug output:
80
80
  ActiveSupportDecorators.debug = true
81
81
  ```
82
82
 
83
- ## Decorator file pattern
84
-
85
- By default decorator files are matched with '_decorator' appended to the file name. You can remove this suffix
86
- completely or use your own one. Note the method signature of add:
87
-
88
- ```Ruby
89
- ActiveSupportDecorators.add(path, decorator_path, file_pattern = '_decorator')
90
- ```
91
-
92
- ## Decorating decorators
93
-
94
- Nested decorators are supported. Just set them to decorate a path where the dependant decorators are placed.
95
-
96
- ## Comparison to other gems
97
-
98
- This is yet another decorator gem because it:
99
- * allows you to specify where and how you name decorators.
100
- * allows you to limit the paths you decorate.
101
- * other gems tend to eager load as seen [here]
102
- (https://github.com/atd/rails_engine_decorators/blob/master/lib/rails_engine_decorators/engine/configuration.rb)
103
- and [here](https://github.com/parndt/decorators/blob/master/lib/decorators/railtie.rb).
104
- * other gems assume you use 'MyClass.class_eval' to define decorators since it is how you trigger activesupport to load
105
- the original file. This gem allows you to use the normal 'class MyClass' which means you can define constants in
106
- decorators.
107
-
108
- However if you do not want to specify which path's you allow to be decorated, you should use another gem. Instead of
109
- single file look ups it will need to search your decorator directories for a matching file, which is not feasible
110
- especially in a context where any ruby file may be decorated. The best solution for such a scenario would be to
111
- eager load the decorators like other gems do.
83
+ ### Comparison to other gems
84
+
85
+ Other gems work by simply telling Rails to eager load all your decorators on application startup as seen [here]
86
+ (https://github.com/atd/rails_engine_decorators/blob/master/lib/rails_engine_decorators/engine/configuration.rb) and
87
+ [here](https://github.com/parndt/decorators/blob/master/lib/decorators/railtie.rb). They expect your decorators to use
88
+ 'MyClass.class_eval do' to extend the original class as this is what triggers the original class to be loaded.
89
+ Disadvantages of this approach include:
90
+ * decorators may not expect other classes to be decorated already, it's a bad idea to depend on load order.
91
+ * development mode is a bit slower since your decorator eager loading usually has a cascade effect on the application.
92
+ This is more noticeable when using JRuby as it will be a compile action instead of class load action.
93
+ * using 'MyClass.class_eval do' instead of 'class MyClass' means you can not define constants.
94
+
95
+ This gem works by hooking into ActiveSupport, which means that decorators are loaded as required instead of at
96
+ application startup. You can use 'class MyClass' and expect that other classes are already decorated, since when you
97
+ reference other classes they will be decorated on the fly when ActiveSupport loads them.
@@ -1,18 +1,17 @@
1
- require 'active_support_decorators/graph'
2
-
3
1
  module ActiveSupportDecorators
4
- Dependency = Struct.new(:path, :decorator_path, :file_pattern)
2
+ DECORATOR_PATTERN = '_decorator'
5
3
 
6
4
  def self.clear
7
5
  dependencies.clear
8
6
  end
9
7
 
10
8
  def self.dependencies
11
- @dependencies ||= []
9
+ @dependencies ||= {}
12
10
  end
13
11
 
14
- def self.add(path, decorator_path, file_pattern = '_decorator')
15
- dependencies << Dependency.new(path, decorator_path, file_pattern)
12
+ def self.add(path, decorator_path)
13
+ dependencies[path] ||= []
14
+ dependencies[path] << decorator_path
16
15
  end
17
16
 
18
17
  def self.debug
@@ -26,34 +25,44 @@ module ActiveSupportDecorators
26
25
  private
27
26
  def self.load_path_order(file_name)
28
27
  # Do not process with .rb extension if provided.
29
- sanitized_file_name = file_name.sub(/\.rb$/,'')
30
-
31
- graph = Graph.new
32
- graph.add(sanitized_file_name)
28
+ sanitized_file_name = file_name.sub(/\.rb$/, '')
29
+ decorated_file = nil
30
+ decorators = []
33
31
 
34
- dependencies.each do |dep|
35
- # If an attempt is made to load the original file, ensure the decorators are loaded afterwards.
36
- if sanitized_file_name.starts_with?(dep.path)
37
- relative_name = sanitized_file_name.sub(dep.path, '')
38
- decorator_file = "#{dep.decorator_path}#{relative_name}#{dep.file_pattern}"
32
+ dependencies.each do |path, decorator_paths|
33
+ decorator_paths.each do |decorator_path|
34
+ # If an attempt is made to load the original file, ensure the decorators are loaded afterwards.
35
+ if sanitized_file_name.starts_with?(path)
36
+ relative_name = sanitized_file_name.sub(path, '')
37
+ candidate_file = "#{decorator_path}#{relative_name}#{DECORATOR_PATTERN}"
39
38
 
40
- if File.file?(decorator_file + '.rb')
41
- graph.add_with_edge(sanitized_file_name, decorator_file)
39
+ if File.file?(candidate_file + '.rb')
40
+ decorated_file = sanitized_file_name
41
+ decorators << candidate_file
42
+ end
42
43
  end
43
- end
44
44
 
45
- # If an attempt is made to load a decorator file, ensure the original/decorated file is loaded first.
46
- # This is only supported when a decorator was not added with add_global.
47
- if dep.path && sanitized_file_name.starts_with?(dep.decorator_path)
48
- relative_name = sanitized_file_name.sub(dep.decorator_path, '')
49
- decorated_file = "#{dep.path}#{relative_name}".sub(/#{dep.file_pattern}$/, '')
45
+ # If an attempt is made to load a decorator file, ensure the original/decorated file is loaded first.
46
+ # This is only supported when a decorator was not added with add_global.
47
+ if sanitized_file_name.starts_with?(decorator_path)
48
+ relative_name = sanitized_file_name.sub(decorator_path, '')
49
+ candidate_file = "#{path}#{relative_name}".sub(/#{DECORATOR_PATTERN}$/, '')
50
50
 
51
- if File.file?(decorated_file + '.rb')
52
- graph.add_with_edge(decorated_file, sanitized_file_name)
51
+ if File.file?(candidate_file + '.rb')
52
+ fail "File #{sanitized_file_name} is a decorator for #{candidate_file}, but this file is already
53
+ decorating #{decorated_file}." unless decorated_file.nil? || decorated_file == candidate_file
54
+ decorated_file = candidate_file
55
+ decorators << sanitized_file_name
56
+ end
53
57
  end
54
58
  end
55
59
  end
56
60
 
57
- graph.list_by_order
61
+ if decorated_file
62
+ # Decorators are sorted to ensure the load order is always the same.
63
+ [decorated_file] + decorators.sort
64
+ else
65
+ [sanitized_file_name]
66
+ end
58
67
  end
59
68
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveSupportDecorators
2
- VERSION = '1.0.1'
2
+ VERSION = '1.0.2'
3
3
  end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveSupportDecorators do
4
+
5
+ before :each do
6
+ # Clear class from Ruby environment.
7
+ ActiveSupport::Dependencies.clear
8
+ Object.send(:remove_const, :Pet) if defined?(Pet)
9
+ end
10
+
11
+ describe 'when configuring a decorator dependency' do
12
+ before :all do
13
+ original_path = File.join(File.dirname(__FILE__), 'support/originals')
14
+ decorator_path = File.join(File.dirname(__FILE__), 'support/decorators')
15
+ ActiveSupportDecorators.clear
16
+ ActiveSupportDecorators.add(original_path, decorator_path)
17
+ end
18
+
19
+ context 'and requiring the original file as .rb' do
20
+ it 'it loads the decorator file after loading the original' do
21
+ path = File.join(File.dirname(__FILE__), 'support/originals/pet.rb')
22
+ ActiveSupport.require_or_load(path)
23
+
24
+ Pet.new.owner.should eq('Mr. Robinson')
25
+ end
26
+ end
27
+
28
+ context 'and requiring the original file' do
29
+ it 'it loads the decorator file after loading the original' do
30
+ path = File.join(File.dirname(__FILE__), 'support/originals/pet')
31
+ ActiveSupport.require_or_load(path)
32
+
33
+ Pet.new.owner.should eq('Mr. Robinson')
34
+ end
35
+ end
36
+
37
+ context 'and requiring the decorator file' do
38
+ it 'it loads the original file first' do
39
+ path = File.join(File.dirname(__FILE__), 'support/decorators/pet_decorator')
40
+ ActiveSupport.require_or_load(path)
41
+
42
+ Pet.new.owner.should eq('Mr. Robinson')
43
+ end
44
+ end
45
+ end
46
+
47
+ describe 'when not using activesupport-decorators' do
48
+ before :all do
49
+ ActiveSupportDecorators.clear
50
+ end
51
+
52
+ it 'should not automatically load decorators' do
53
+ pet_path = File.join(File.dirname(__FILE__), 'support/originals/pet')
54
+ ActiveSupport.require_or_load(pet_path)
55
+
56
+ expect { Pet.new.owner }.to raise_error
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,5 @@
1
+ Pet.class_eval do
2
+ def owner
3
+ 'Mr. Robinson'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Pet
2
+ def bark
3
+ 'Woof!'
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'activesupport-decorators'
5
+
6
+ RSpec.configure do |config|
7
+ # No extra config yet.
8
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activesupport-decorators
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pierre Pretorius
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-01 00:00:00.000000000 Z
11
+ date: 2014-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -49,9 +49,12 @@ files:
49
49
  - Rakefile
50
50
  - lib/active_support_decorators/active_support_decorators.rb
51
51
  - lib/active_support_decorators/dependencies_patch.rb
52
- - lib/active_support_decorators/graph.rb
53
52
  - lib/active_support_decorators/version.rb
54
53
  - lib/activesupport-decorators.rb
54
+ - spec/lib/active_support_decorators/active_support_decorator_spec.rb
55
+ - spec/lib/active_support_decorators/support/decorators/pet_decorator.rb
56
+ - spec/lib/active_support_decorators/support/originals/pet.rb
57
+ - spec/spec_helper.rb
55
58
  homepage: https://github.com/pierre-pretorius/activesupport-decorators
56
59
  licenses:
57
60
  - MIT
@@ -1,49 +0,0 @@
1
- class Graph
2
- Node = Struct.new(:object) do
3
- def depends_on
4
- @depends_on ||= []
5
- end
6
- end
7
-
8
- def initialize
9
- @nodes = []
10
- end
11
-
12
- def list_by_order
13
- result = []
14
-
15
- until @nodes.empty?
16
- nodes_without_dependencies = @nodes.select { |n| n.depends_on.empty? }
17
- result += nodes_without_dependencies.map { |n| n.object }.sort
18
-
19
- nodes_without_dependencies.each do |to_delete|
20
- @nodes.delete(to_delete)
21
- @nodes.each { |n| n.depends_on.delete(to_delete) }
22
- end
23
- end
24
-
25
- result
26
- end
27
-
28
- def add(object)
29
- find_or_add_node(object)
30
- end
31
-
32
- def add_with_edge(from_object, to_object)
33
- fail 'Can not add edge from object to itself' if from_object == to_object
34
- from_node = find_or_add_node(from_object)
35
- to_node = find_or_add_node(to_object)
36
- to_node.depends_on << from_node
37
- fail 'EMPTY' if from_node.nil?
38
- end
39
-
40
- private
41
- def find_or_add_node(object)
42
- node = @nodes.find { |n| n.object == object }
43
- unless node
44
- node = Node.new(object)
45
- @nodes.push(node)
46
- end
47
- node
48
- end
49
- end