context_exposer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/Gemfile +22 -0
- data/LICENSE.txt +22 -0
- data/README.md +111 -0
- data/Rakefile +1 -0
- data/context_exposer.gemspec +23 -0
- data/lib/context_exposer.rb +8 -0
- data/lib/context_exposer/base_controller.rb +84 -0
- data/lib/context_exposer/version.rb +3 -0
- data/lib/context_exposer/view_context.rb +14 -0
- data/spec/app/posts_spec.rb +1 -0
- data/spec/context_exposer/expose_spec.rb +128 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/posts_controller.rb +13 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/posts/index.html.erb +1 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +30 -0
- data/spec/dummy/config/environments/production.rb +65 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +10 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +60 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/log/test.log +2163 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/spec_helper.rb +8 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 12da37ed9022c53dd944d8c73f53c0da2980a65f
|
4
|
+
data.tar.gz: 7cd49fd373d8ea82633f36064c21eb95303e3ab4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4cc5284dce7e9ac8c4d9afd9470cbd69e6f29f2bcc2c75749439ad195a86326e9848dd292db2cc1e8f2d31641eee89d28198e43620d295fe930a39bb631524a9
|
7
|
+
data.tar.gz: 92d81c8be3d0dd2dd4e067f3c3c1a73837319d01ffeaaba0a2c4f29890e89caaa75960b7650994ade13f545c0446339bcc69f3aa57428679500d14e94d293b61
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --format nested
|
data/Gemfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in context_exposer.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'pry'
|
7
|
+
gem 'rails', '>= 3.1'
|
8
|
+
|
9
|
+
gem "rspec-rails", :group => [:test, :development]
|
10
|
+
|
11
|
+
group :test do
|
12
|
+
# gem 'factory_girl_rails', :require => false
|
13
|
+
# gem "capybara"
|
14
|
+
# gem "capybara-webkit"
|
15
|
+
# gem 'growl'
|
16
|
+
# gem "spork"
|
17
|
+
# gem "guard"
|
18
|
+
# gem "guard-bundler"
|
19
|
+
# gem "guard-rspec"
|
20
|
+
# gem "guard-spork"
|
21
|
+
# gem "launchy"
|
22
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kristian Mandrup
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# ContextExposer
|
2
|
+
|
3
|
+
Allows the Controller to exposes a Context object to the View.
|
4
|
+
This Context object alone contains all the information passed to the View from the controller.
|
5
|
+
|
6
|
+
No more pollution of the View with content helper methods or even worse, instance variables.
|
7
|
+
|
8
|
+
The Context object will by default be an instance of `ContextExposer::ViewContext`, but you can subclass this baseclass to add you own logic for more complex scenarios. This also allows for a more modular approach, where you can easily share or subclass logic between different view contexts. Nice!
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'context_exposer'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install context_exposer
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Use the `exposed` method which takes a name of the method to be created on the ViewContext and a block with the logic.
|
27
|
+
|
28
|
+
Example:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class PostsController < ActionController::Base
|
32
|
+
include ContextExposer::BaseController
|
33
|
+
|
34
|
+
exposed(:post) { Post.find params[:id] }
|
35
|
+
exposed(:posts) { Post.find params[:id] }
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
You can also define your own subclass of `ViewContext` and designate an instance of this custom class as your "exposed" target, via `view_context_class`method.
|
40
|
+
|
41
|
+
Example:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class PostsController < ActionController::Base
|
45
|
+
include ContextExposer::BaseController
|
46
|
+
|
47
|
+
view_context_class :posts_view_context
|
48
|
+
|
49
|
+
exposed(:post) { Post.find params[:id] }
|
50
|
+
exposed(:posts) { Post.find params[:id] }
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class PostsViewContext < ContextExposer::ViewContext
|
56
|
+
def initialize controller
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
def total
|
61
|
+
posts.size
|
62
|
+
end
|
63
|
+
|
64
|
+
def admin_posts
|
65
|
+
return [] unless admin?
|
66
|
+
posts.select {|post| post.admin? }
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def current_user
|
72
|
+
controller.current_user
|
73
|
+
end
|
74
|
+
|
75
|
+
def admin?
|
76
|
+
current_user.admin?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
This opens up some amazing possibilities to really put the logic where it belongs.The custom ViewContext would benefit from having the "admin" and "user" logic extracted either to separate modules or a custom ViewContext base class ;)
|
82
|
+
|
83
|
+
This approach opens up many new exciting ways to slice and dice your logic in a much better way, a new *MVC-C* architecture, the extra "C" for *Context*.
|
84
|
+
|
85
|
+
|
86
|
+
## TODO
|
87
|
+
|
88
|
+
Add some useful subclasses of `BaseController`, that add some extra magic!
|
89
|
+
Also add some basic modules to integrate with typical authentication solutions etc. Get inspiration from other similar gems, fx `decent_exposure`. Allow for integration between these different solutions.
|
90
|
+
|
91
|
+
### ResourceController
|
92
|
+
|
93
|
+
It sould be nice to have a `ResourceController` to automatically set up the typical singular and plural-form resource helpers.
|
94
|
+
|
95
|
+
This would simplify the above `PostsController` example to this:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
class PostsController < ActionController::Base
|
99
|
+
include ContextExposer::ResourceController
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
Please help out :)
|
104
|
+
|
105
|
+
## Contributing
|
106
|
+
|
107
|
+
1. Fork it
|
108
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
109
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
110
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
111
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'context_exposer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "context_exposer"
|
8
|
+
spec.version = ContextExposer::VERSION
|
9
|
+
spec.authors = ["Kristian Mandrup"]
|
10
|
+
spec.email = ["kmandrup@gmail.com"]
|
11
|
+
spec.description = %q{Exposes a ViewContext object to the View with all the data needed by the view}
|
12
|
+
spec.summary = %q{The Context object becomes the single communication point between View and Controller}
|
13
|
+
spec.homepage = "https://github.com/kristianmandrup/context_exposer"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module ContextExposer::BaseController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_filter :configure_exposed_context
|
6
|
+
|
7
|
+
expose_context :context
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_context
|
11
|
+
@view_context ||= build_view_context
|
12
|
+
end
|
13
|
+
alias_method :context, :view_context
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def exposed name, &block
|
17
|
+
# puts "store: #{name} in hash storage for class #{self}"
|
18
|
+
exposure_storage[name.to_sym] = block
|
19
|
+
end
|
20
|
+
|
21
|
+
def view_context_class name
|
22
|
+
define_method :view_context_class do
|
23
|
+
@view_context_class ||= name.kind_of?(Class) ? name : name.to_s.camelize.constantize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def expose_context name
|
30
|
+
return if exposed_view_context?
|
31
|
+
if ActionController::Base.instance_methods.include?(name.to_sym)
|
32
|
+
Kernel.warn "[WARNING] You are exposing the `#{name}` method, " \
|
33
|
+
"which overrides an existing ActionController method of the same name. " \
|
34
|
+
"Consider a different exposure name\n" \
|
35
|
+
"#{caller.first}"
|
36
|
+
end
|
37
|
+
helper_method name
|
38
|
+
hide_action name
|
39
|
+
@exposed_view_context = true
|
40
|
+
end
|
41
|
+
|
42
|
+
def exposed_view_context?
|
43
|
+
@exposed_view_context ||= false
|
44
|
+
end
|
45
|
+
|
46
|
+
def exposure_storage
|
47
|
+
exposure_hash[self.to_s] ||= {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def exposure_hash
|
51
|
+
@exposure_hash ||= {}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# must be called after Controller is instantiated
|
56
|
+
def configure_exposed_context
|
57
|
+
return if configured_exposed_context?
|
58
|
+
clazz = self.class
|
59
|
+
exposed_methods = clazz.send(:exposure_hash)[clazz.to_s] || []
|
60
|
+
# puts "exposed_methods for: #{clazz} - #{exposed_methods}"
|
61
|
+
exposed_methods.each do |name, procedure|
|
62
|
+
view_context.send :define_singleton_method, name do
|
63
|
+
procedure.call
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@configured_exposed_context = true
|
67
|
+
end
|
68
|
+
|
69
|
+
def configured_exposed_context?
|
70
|
+
@configured_exposed_context == true
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
# returns a ViewContext object
|
76
|
+
# view helpers can be exposed as singleton methods, dynamically be attached (see below)
|
77
|
+
def build_view_context
|
78
|
+
view_context_class.new self
|
79
|
+
end
|
80
|
+
|
81
|
+
def view_context_class
|
82
|
+
@view_context_class ||= ContextExposer::ViewContext
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class ContextExposer::ViewContext
|
2
|
+
attr_reader :controller
|
3
|
+
|
4
|
+
def initialize controller = nil
|
5
|
+
@controller = controller
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def define_singleton_method(name, &block)
|
11
|
+
eigenclass = class<<self; self end
|
12
|
+
eigenclass.class_eval {define_method name, block}
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# Run Capybara Spec on Dummy app PostsController
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'context_exposer'
|
2
|
+
require 'action_controller'
|
3
|
+
|
4
|
+
class MyController < ActionController::Base
|
5
|
+
include ContextExposer::BaseController
|
6
|
+
|
7
|
+
exposed(:post) { Post.find params[:id] }
|
8
|
+
|
9
|
+
def post
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class MyCoolController < ActionController::Base
|
14
|
+
include ContextExposer::BaseController
|
15
|
+
|
16
|
+
exposed(:coolness) { "MegaCool" }
|
17
|
+
|
18
|
+
view_context_class :mega_cool_view_context
|
19
|
+
|
20
|
+
def show
|
21
|
+
configure_exposed_context
|
22
|
+
end
|
23
|
+
|
24
|
+
def params; end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MegaCoolViewContext < ContextExposer::ViewContext
|
28
|
+
def initialize controller
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def power_of number
|
33
|
+
number * number
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe ContextExposer::BaseController do
|
38
|
+
|
39
|
+
describe "controller" do
|
40
|
+
subject { controller }
|
41
|
+
|
42
|
+
let(:controller) { MyController.new }
|
43
|
+
|
44
|
+
# run action post
|
45
|
+
before :each do
|
46
|
+
controller.post
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'defines :post as an action_method' do
|
50
|
+
expect(subject.action_methods).to include('post')
|
51
|
+
end
|
52
|
+
|
53
|
+
it "defines a method context" do
|
54
|
+
expect(subject).to respond_to(:context)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "exposes the context to the view layer as a helper" do
|
58
|
+
expect(subject._helper_methods).to include(:context)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "prevents the context method from being routable" do
|
62
|
+
expect(subject.hidden_actions).to include("context")
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'configured exposed context' do
|
66
|
+
expect(subject.configured_exposed_context?).to be_true
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'context' do
|
70
|
+
subject { controller.context }
|
71
|
+
|
72
|
+
it "is an instance of ContextExposer::ViewContext" do
|
73
|
+
expect(subject).to be_a ContextExposer::ViewContext
|
74
|
+
end
|
75
|
+
|
76
|
+
it "defines a method :bird" do
|
77
|
+
expect(subject).to respond_to(:bird)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "calling method :bird returns 'Bird' " do
|
81
|
+
expect(subject.bird).to eq "Bird"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'MyCoolController' do
|
87
|
+
subject { controller }
|
88
|
+
|
89
|
+
let(:controller) { MyCoolController.new }
|
90
|
+
|
91
|
+
# run action post
|
92
|
+
before :each do
|
93
|
+
controller.show
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'context' do
|
97
|
+
subject { controller.context }
|
98
|
+
|
99
|
+
it "inherits from ContextExposer::ViewContext" do
|
100
|
+
expect(subject).to be_a ContextExposer::ViewContext
|
101
|
+
end
|
102
|
+
|
103
|
+
it "is an instance of MegaCoolViewContext" do
|
104
|
+
expect(subject).to be_a MegaCoolViewContext
|
105
|
+
end
|
106
|
+
|
107
|
+
it "has reference to controller" do
|
108
|
+
expect(subject.controller).to eq controller
|
109
|
+
end
|
110
|
+
|
111
|
+
it "defines a method :coolness" do
|
112
|
+
expect(subject).to respond_to(:coolness)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "calling method :coolness returns 'MegaCool' " do
|
116
|
+
expect(subject.coolness).to eq "MegaCool"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "defines a method :power_of" do
|
120
|
+
expect(subject).to respond_to(:power_of)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "calling method :power_of(2) returns 4" do
|
124
|
+
expect(subject.power_of 2).to eq 4
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|