context_exposer 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/Gemfile +13 -1
- data/README.md +186 -16
- data/lib/context_exposer.rb +15 -0
- data/lib/context_exposer/base_controller.rb +67 -47
- data/lib/context_exposer/cached_resource_controller.rb +10 -0
- data/lib/context_exposer/integrations.rb +8 -0
- data/lib/context_exposer/integrations/base.rb +15 -0
- data/lib/context_exposer/integrations/key_filter.rb +30 -0
- data/lib/context_exposer/integrations/with_decent_exposure.rb +19 -0
- data/lib/context_exposer/integrations/with_decorates_before.rb +31 -0
- data/lib/context_exposer/integrations/with_instance_vars.rb +19 -0
- data/lib/context_exposer/macros.rb +17 -0
- data/lib/context_exposer/patch/decorates_before_rendering.rb +179 -0
- data/lib/context_exposer/rails_config.rb +3 -0
- data/lib/context_exposer/resource_controller.rb +30 -8
- data/lib/context_exposer/version.rb +1 -1
- data/lib/context_exposer/view_context.rb +1 -1
- data/spec/app/items_spec.rb +36 -0
- data/spec/app/posts_spec.rb +29 -1
- data/spec/context_exposer/expose_resource_spec.rb +51 -38
- data/spec/context_exposer/expose_spec.rb +11 -10
- data/spec/dummy/app/controllers/items_controller.rb +12 -0
- data/spec/dummy/app/controllers/posts_controller.rb +3 -4
- data/spec/dummy/app/views/items/index.html.erb +2 -0
- data/spec/dummy/app/views/items/show.html.erb +2 -0
- data/spec/dummy/app/views/posts/index.html.erb +2 -1
- data/spec/dummy/app/views/posts/show.html.erb +2 -1
- data/spec/dummy/config/initializers/context_exposer.rb +1 -0
- data/spec/dummy/config/routes.rb +1 -57
- data/spec/dummy/log/test.log +483 -0
- data/spec/dummy_spec_helper.rb +82 -0
- data/spec/spec_helper.rb +13 -1
- data/spec/support/decorators/base_decorator.rb +22 -0
- data/spec/support/decorators/item_decorator.rb +11 -0
- data/spec/support/decorators/post_decorator.rb +5 -0
- data/spec/support/models/base.rb +18 -0
- data/spec/support/models/base/clazz_methods.rb +29 -0
- data/spec/support/models/item.rb +9 -0
- data/spec/support/models/post.rb +5 -0
- metadata +38 -2
@@ -0,0 +1,15 @@
|
|
1
|
+
module ContextExposer
|
2
|
+
module Integrations
|
3
|
+
module Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def _exposure_filter keys, options = {}
|
10
|
+
::ContextExposer::Integrations::KeyFilter.new(keys, options).filter
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ContextExposer
|
2
|
+
module Integrations
|
3
|
+
class KeyFilter
|
4
|
+
attr_reader :keys, :options
|
5
|
+
|
6
|
+
def initialize keys, options = {}
|
7
|
+
@keys = keys || []
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
# expose all exposures exposed by decent_exposure to context
|
12
|
+
def filter
|
13
|
+
the_keys = keys - except
|
14
|
+
only.empty? ? the_keys : the_keys.select {|k| only.include? k.to_sym }
|
15
|
+
end
|
16
|
+
|
17
|
+
def except
|
18
|
+
@except ||= symbolize_opts :except
|
19
|
+
end
|
20
|
+
|
21
|
+
def only
|
22
|
+
@only ||= symbolize_opts :only
|
23
|
+
end
|
24
|
+
|
25
|
+
def symbolize_opts name
|
26
|
+
(options[name.to_sym] || {}).map(&:to_sym)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ContextExposer::Integrations
|
2
|
+
module WithDecentExposure
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# expose all exposures exposed by decent_exposure to context
|
7
|
+
def context_expose_decently options = {}
|
8
|
+
expose_keys = _exposures.keys
|
9
|
+
|
10
|
+
_exposure_filter(expose_keys, options).each do |exposure|
|
11
|
+
exposed exposure do
|
12
|
+
send(exposure)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias_method :expose_decently, :context_expose_decently
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ContextExposer::Integrations
|
2
|
+
module WithDecoratesBefore
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# expose all exposures exposed by decent_exposure to context
|
7
|
+
def context_expose_decorated_instance_vars options = {}
|
8
|
+
coll_decorates = __decorates_collection__[0..-1]
|
9
|
+
basic_decorates = __decorates__[0..-1]
|
10
|
+
|
11
|
+
all_decorates = case options[:for]
|
12
|
+
when :collection
|
13
|
+
coll_decorates
|
14
|
+
when :non_collection
|
15
|
+
basic_decorates
|
16
|
+
else
|
17
|
+
coll_decorates + basic_decorates
|
18
|
+
end
|
19
|
+
|
20
|
+
expose_keys = all_decorates.map {|v| v[1..-1]}
|
21
|
+
|
22
|
+
_exposure_filter(keys, options).each do |exposure|
|
23
|
+
exposed exposure do
|
24
|
+
send("@#{exposure}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias_method :expose_decorated_instance_vars, :context_expose_decorated_instance_vars
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ContextExposer::Integrations
|
2
|
+
module WithInstanceVars
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# expose all exposures exposed by decent_exposure to context
|
7
|
+
def context_expose_instance_vars options = {}
|
8
|
+
expose_keys = self.instance_variables.map {|v| v[1..-1]}
|
9
|
+
|
10
|
+
_exposure_filter(expose_keys, options).each do |exposure|
|
11
|
+
exposed exposure do
|
12
|
+
send("@#{exposure}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias_method :expose_instance_vars, :context_expose_instance_vars
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ContextExposer
|
2
|
+
module Macros
|
3
|
+
def context_exposer name = :base, options = {}
|
4
|
+
self.send :include, "ContextExposer::#{name.to_s.camelize}Controller".constantize
|
5
|
+
|
6
|
+
integrates_with [options[:with]].flatten if options[:with]
|
7
|
+
end
|
8
|
+
|
9
|
+
def decorates_before_rendering
|
10
|
+
unless defined? ::DecoratesBeforeRendering
|
11
|
+
raise "DecoratesBeforeRendering not found, please include the gem 'decorates_before_rendering'"
|
12
|
+
end
|
13
|
+
self.send :include, DecoratesBeforeRendering
|
14
|
+
end
|
15
|
+
alias_method :decorates_before_render, :decorates_before_rendering
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module DecoratesBeforeRendering
|
2
|
+
class FindModelError < StandardError; end
|
3
|
+
class DecoratorError < StandardError; end
|
4
|
+
|
5
|
+
def render *args
|
6
|
+
__auto_decorate_exposed_ones_
|
7
|
+
super(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def __auto_decorate_exposed_ones_
|
11
|
+
__decorate_ivars__
|
12
|
+
__decorate_exposed_ones_
|
13
|
+
__decorate_ctx_exposed_ones_
|
14
|
+
rescue StandardError => e
|
15
|
+
__handle_decorate_error_(e)
|
16
|
+
end
|
17
|
+
|
18
|
+
def __handle_decorate_error_ e
|
19
|
+
# puts "Error: #{e}"
|
20
|
+
|
21
|
+
if defined?(::Rails) && (Rails.respond_to? :logger)
|
22
|
+
Rails.logger.warn 'decorates_before_render: auto_decorate error: #{e}'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def __exposed_ones_
|
27
|
+
return [] unless self.class.respond_to? :_exposures
|
28
|
+
@__exposed_ones_ ||= self.class._exposures.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
def __ctx_exposed_ones_
|
32
|
+
return [] unless self.class.respond_to? :_exposure_storage
|
33
|
+
@__ctx_exposed_ones_ ||= self.class._exposure_storage.keys
|
34
|
+
end
|
35
|
+
|
36
|
+
def __decorate_exposed_ones_
|
37
|
+
__exposed_ones_.each do |name|
|
38
|
+
ivar_name = "@#{name}"
|
39
|
+
obj = send(name)
|
40
|
+
decorated = __attempt_to_decorate_(obj)
|
41
|
+
|
42
|
+
decorated = case objs
|
43
|
+
when Array
|
44
|
+
__attempt_to_decorate_list_ objs
|
45
|
+
else
|
46
|
+
__attempt_to_decorate_ objs
|
47
|
+
end
|
48
|
+
|
49
|
+
send(:instance_variable_set, ivar_name, decorated) # if decorated
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def __decorate_ctx_exposed_ones_
|
54
|
+
__ctx_exposed_ones_.each do |name|
|
55
|
+
ivar_name = "@#{name}"
|
56
|
+
objs = ctx.send(name)
|
57
|
+
|
58
|
+
decorated = case objs
|
59
|
+
when Array
|
60
|
+
__attempt_to_decorate_list_ objs
|
61
|
+
else
|
62
|
+
__attempt_to_decorate_ objs
|
63
|
+
end
|
64
|
+
|
65
|
+
ctx.send(:instance_variable_set, ivar_name, decorated) # if decorated
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def __attempt_to_decorate_list_ objs
|
70
|
+
[objs].flatten.compact.map do |obj|
|
71
|
+
__attempt_to_decorate_(obj)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def __attempt_to_decorate_ obj
|
76
|
+
if obj
|
77
|
+
src = __src_for__(obj)
|
78
|
+
decorator = __normalized_decorator_for__(src)
|
79
|
+
__do_decoration_ decorator, obj
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def __do_decoration_ decorator, obj
|
84
|
+
return if !decorator || !obj
|
85
|
+
__validate_decorator!(decorator)
|
86
|
+
decorator.decorate(obj)
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def __validate_decorator! decorator
|
91
|
+
unless decorator.respond_to? :decorate
|
92
|
+
raise DecoratorError, "Decorator: #{decorator} must have a #decorate method"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def __decorate_error! decorator
|
97
|
+
raise DecoratorError, "Decorator: #{decorator} must have a #decorate method"
|
98
|
+
end
|
99
|
+
|
100
|
+
def __src_for__ obj
|
101
|
+
case obj
|
102
|
+
when Class
|
103
|
+
obj.class
|
104
|
+
else
|
105
|
+
obj
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def __normalized_decorator_for__(obj)
|
110
|
+
decorator = __decorator_for__(obj)
|
111
|
+
|
112
|
+
case decorator
|
113
|
+
when String
|
114
|
+
decorator.constantize.new obj
|
115
|
+
when Class
|
116
|
+
decorator.new obj
|
117
|
+
else
|
118
|
+
decorator
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def __decorator_for__(obj)
|
123
|
+
return obj.decorator(self) if obj.respond_to? :decorator
|
124
|
+
__decorator_name_for__(obj).constantize
|
125
|
+
rescue FindModelError => e
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def __decorator_name_for__(obj)
|
130
|
+
"#{__model_name_for__(obj)}Decorator"
|
131
|
+
end
|
132
|
+
|
133
|
+
def __model_name_for__(obj)
|
134
|
+
return obj.model_name if obj.respond_to?(:model_name)
|
135
|
+
raise FindModelError, "#{obj} does not have an associated model"
|
136
|
+
end
|
137
|
+
|
138
|
+
def __decorate_ivars__
|
139
|
+
__validate_decorates_present_
|
140
|
+
return if __decorates_blank?
|
141
|
+
__decorates__ivars
|
142
|
+
__decorates_collection_ivars__
|
143
|
+
end
|
144
|
+
|
145
|
+
def __validate_decorates_present_
|
146
|
+
unless __has_decorates?
|
147
|
+
raise "Internal method '__decorates__' not found. You need to include the 'decorates_before_render' gem "
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def __has_decorates?
|
152
|
+
respond_to?(:__decorates__)
|
153
|
+
end
|
154
|
+
|
155
|
+
def __decorates_blank?
|
156
|
+
__decorates__.blank? and __decorates_collection__.blank?
|
157
|
+
end
|
158
|
+
|
159
|
+
def __decorates_collection_ivars__
|
160
|
+
if !__decorates_collection__.nil?
|
161
|
+
__decorate_ivar_names__(__decorates_collection__) do |ivar_name, ivar, options|
|
162
|
+
decorated = options.fetch(:with).decorate_collection(ivar)
|
163
|
+
instance_variable_set(ivar_name, decorated) if decorated
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def __decorates__ivars
|
169
|
+
if !__decorates__.nil?
|
170
|
+
__decorate_ivar_names__(__decorates__) do |ivar_name, ivar, options|
|
171
|
+
decorator = options.key?(:with) ? options.fetch(:with) : __decorator_for__(ivar)
|
172
|
+
if decorator
|
173
|
+
decorated = __do_decoration_(decorator, ivar)
|
174
|
+
instance_variable_set(ivar_name, decorated)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -3,18 +3,35 @@ module ContextExposer::ResourceController
|
|
3
3
|
include ContextExposer::BaseController
|
4
4
|
|
5
5
|
included do
|
6
|
-
|
7
|
-
|
8
|
-
exposed(_normalized_resource_name.singularize) do
|
9
|
-
find_single_resource
|
10
|
-
end
|
11
|
-
|
12
|
-
exposed(_normalized_resource_name.pluralize) { find_all_resources }
|
13
|
-
exposed(_normalized_resource_list) { find_all_resources.to_a }
|
6
|
+
expose_resources
|
14
7
|
end
|
15
8
|
|
16
9
|
protected
|
17
10
|
|
11
|
+
module ClassMethods
|
12
|
+
def expose_resources *types
|
13
|
+
types = types.flatten
|
14
|
+
types = types.empty? ? [:all] : types
|
15
|
+
|
16
|
+
unless expose_resource_method? :one, types
|
17
|
+
_exposing(_normalized_resource_name.singularize) { find_single_resource }
|
18
|
+
end
|
19
|
+
|
20
|
+
unless expose_resource_method? :many, types
|
21
|
+
_exposing(_normalized_resource_name.pluralize) { find_all_resources }
|
22
|
+
end
|
23
|
+
|
24
|
+
unless expose_resource_method? :list, types
|
25
|
+
_exposing(_normalized_resource_list) { find_all_resources.to_a }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def expose_resource_method? type, types
|
30
|
+
([type, :all] & types).empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
18
35
|
def resource_id
|
19
36
|
params[:id]
|
20
37
|
end
|
@@ -28,6 +45,11 @@ module ContextExposer::ResourceController
|
|
28
45
|
end
|
29
46
|
|
30
47
|
module ClassMethods
|
48
|
+
# for use in ResourceController
|
49
|
+
def _exposing name, options = {}, &block
|
50
|
+
exposed name, options, &block
|
51
|
+
end
|
52
|
+
|
31
53
|
def _the_resource
|
32
54
|
clazz_name = self.to_s.sub(/Controller$/, '').singularize
|
33
55
|
clazz_name.constantize
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Run Capybara Spec on Dummy app PostsController
|
2
|
+
|
3
|
+
require 'dummy_spec_helper'
|
4
|
+
|
5
|
+
feature 'show list of items' do
|
6
|
+
let(:names) { ['Item 1', 'Item 2'] }
|
7
|
+
let(:name) { names.first }
|
8
|
+
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
names.each{|n| Item.new n }
|
12
|
+
|
13
|
+
visit items_path
|
14
|
+
end
|
15
|
+
|
16
|
+
context '2 items' do
|
17
|
+
scenario 'display item 1 and 2' do
|
18
|
+
page.should have_content names.first
|
19
|
+
page.should have_content names.last
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
feature 'show item' do
|
25
|
+
let(:name) { 'Item 1' }
|
26
|
+
|
27
|
+
before do
|
28
|
+
Item.new name
|
29
|
+
end
|
30
|
+
|
31
|
+
scenario 'display one item' do
|
32
|
+
visit item_path(1)
|
33
|
+
|
34
|
+
page.should have_content "A very nice #{name}"
|
35
|
+
end
|
36
|
+
end
|