adequate_exposure 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 +22 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +151 -0
- data/Rakefile +6 -0
- data/adequate_exposure.gemspec +22 -0
- data/lib/adequate_exposure.rb +13 -0
- data/lib/adequate_exposure/attribute.rb +31 -0
- data/lib/adequate_exposure/context.rb +44 -0
- data/lib/adequate_exposure/controller.rb +11 -0
- data/lib/adequate_exposure/exposure.rb +41 -0
- data/lib/adequate_exposure/flow.rb +105 -0
- data/lib/adequate_exposure/version.rb +3 -0
- data/spec/controller_spec.rb +167 -0
- data/spec/integration_spec.rb +26 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/rails_app.rb +39 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6a1d34ba1b491cb226abd1ed966b2e1fc51469a5
|
4
|
+
data.tar.gz: f5a79cc81e6918171764fe3441f81e7da2a372bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 332ca67a8253cbb83e395a95bc79167a9ac355843a772adf1b373569c83086afcc2daed5eb30bbae058a4d57b9fe73992a28d3acaadcc60bac04f47f7bb8eb5d
|
7
|
+
data.tar.gz: 2558b8622f561444eae1ba3b1c19d46b059cd83f2fe0705e06af4e33bb93e821392b6f02a5d11ebcda03fee01b162066072e60e66fa4cebff0dbdc02b6c01abb
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Hashrocket Workstation
|
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,151 @@
|
|
1
|
+
# Adequate Exposure
|
2
|
+
|
3
|
+
This is WIP. Please don't send pull requests yet, I'm still actively rewriting things.
|
4
|
+
|
5
|
+
Adequate exposure. Exposing things, adequately.
|
6
|
+
|
7
|
+
Adequate exposure is a lightweight alternative to [Decent Exposure](https://github.com/voxdolo/decent_exposure). With it's narrowly focused api you can get exactly what you need without all the extra dressing.
|
8
|
+
|
9
|
+
*Note: It is not the intent of the author to imply that Decent Exposure is inadequate.)*
|
10
|
+
|
11
|
+
Installation is as simple as: `$ gem install adequate_exposure`. Once you have that down we can start talking about the API.
|
12
|
+
|
13
|
+
## API
|
14
|
+
|
15
|
+
The whole API consists of one `expose` method.
|
16
|
+
|
17
|
+
In the simplest scenario you'll just use it to expose a model in the controller:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class ThingsController < ApplicationController
|
21
|
+
expose :thing
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
Now every time you call `thing` in your controller or view, it'll look for an id and try to perform `Thing.find(id)` or `Thing.new` if the id is not found. It'll also memoize the result in `@exposed_thing` instance variable.
|
26
|
+
|
27
|
+
You can also provide your own logic of how `thing` should be resolved by passing a block that'll be executed in your controller context.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class ThingsController < ApplicationController
|
31
|
+
expose(:thing){ Thing.find(get_thing_id_somehow) }
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def get_thing_id_somehow
|
36
|
+
42
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
The default resolving workflow if pretty powerful and customizable. It could be expressed with the following pseudocode:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
def fetch(scope, id)
|
45
|
+
id ? decorate(find(id, scope)) : decorate(build(scope))
|
46
|
+
end
|
47
|
+
|
48
|
+
def id
|
49
|
+
params[:thing_id] || params[:id]
|
50
|
+
end
|
51
|
+
|
52
|
+
def find(id, scope)
|
53
|
+
scope.find(id) # Thing.find(id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def build(scope)
|
57
|
+
scope.new # Thing.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def scope
|
61
|
+
model # Thing
|
62
|
+
end
|
63
|
+
|
64
|
+
def model
|
65
|
+
exposure_name.classify.constantize # :thing -> Thing
|
66
|
+
end
|
67
|
+
|
68
|
+
def decorate(thing)
|
69
|
+
thing
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
Each step is overridable with options. The acceptable options to the `expose` macro are:
|
74
|
+
|
75
|
+
**find**
|
76
|
+
|
77
|
+
How to perform the finding. Could be useful if you don't want to use standard Rails finder.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
expose :thing, find: ->(id, scope){ scope.find_by(slug: id) }
|
81
|
+
```
|
82
|
+
|
83
|
+
**build**
|
84
|
+
|
85
|
+
Allows to override the build process that takes place when id is not provided.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
expose :thing, build: ->(scope){ Thing.build_with_defaults }
|
89
|
+
```
|
90
|
+
|
91
|
+
**id**
|
92
|
+
|
93
|
+
Allows to specify how to extract id from parameters hash.
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
# default
|
97
|
+
expose :thing, id: ->{ params[:thing_id] || params[:id] }
|
98
|
+
|
99
|
+
# id is always goona be 42
|
100
|
+
expose :thing, id: ->{ 42 }
|
101
|
+
|
102
|
+
# equivalent to id: ->{ params[:custom_thing_id] }
|
103
|
+
expose :thing, id: :custom_thing_id
|
104
|
+
|
105
|
+
# equivalent to id: ->{ params[:try_this_id] || params[:or_maybe_that_id] }
|
106
|
+
expose :thing, id: %i[try_this_id or_maybe_that_id]
|
107
|
+
```
|
108
|
+
|
109
|
+
**scope**
|
110
|
+
|
111
|
+
Defines the scope that's used in `find` and `build` steps.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
expose :thing, scope: ->{ current_user.things }
|
115
|
+
expose :thing, scope: :current_user # the same as above
|
116
|
+
```
|
117
|
+
|
118
|
+
**model**
|
119
|
+
|
120
|
+
Specify the model to use.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
expose :thing, model: ->{ AnotherThing }
|
124
|
+
expose :thing, model: AnotherThing
|
125
|
+
expose :thing, model: :another_thing
|
126
|
+
```
|
127
|
+
|
128
|
+
**fetch**
|
129
|
+
|
130
|
+
Allows to override the `fetch` logic that's happening when you first call exposed helper.
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
expose :thing, fetch: ->{ get_thing_some_way_or_another }
|
134
|
+
expose(:thing){ get_thing_some_way_or_another }
|
135
|
+
```
|
136
|
+
|
137
|
+
**decorate**
|
138
|
+
|
139
|
+
Allows to define a block that wraps and instance before it's returned. Useful for decorators.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
expose :thing, decorate: ->(thing){ ThingDecorator.new(thing) }
|
143
|
+
```
|
144
|
+
|
145
|
+
## Contributing
|
146
|
+
|
147
|
+
1. Fork it ( https://github.com/[my-github-username]/adequate_exposure/fork )
|
148
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
149
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
150
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
151
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "adequate_exposure/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "adequate_exposure"
|
8
|
+
spec.version = AdequateExposure::VERSION
|
9
|
+
spec.authors = ["Pavel Pravosud"]
|
10
|
+
spec.email = ["pavel@pravosud.com"]
|
11
|
+
spec.summary = "More adequate than decent"
|
12
|
+
spec.homepage = "https://github.com/rwz/adequate_exposure"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.test_files = spec.files.grep(/\Aspec\//)
|
16
|
+
spec.require_path = "lib"
|
17
|
+
|
18
|
+
spec.required_ruby_version = "~> 2.0"
|
19
|
+
|
20
|
+
spec.add_dependency "railties", "~> 4.0"
|
21
|
+
spec.add_dependency "activesupport", "~> 4.0"
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "active_support/lazy_load_hooks"
|
2
|
+
|
3
|
+
module AdequateExposure
|
4
|
+
autoload :Controller, "adequate_exposure/controller"
|
5
|
+
autoload :Exposure, "adequate_exposure/exposure"
|
6
|
+
autoload :Attribute, "adequate_exposure/attribute"
|
7
|
+
autoload :Context, "adequate_exposure/context"
|
8
|
+
autoload :Flow, "adequate_exposure/flow"
|
9
|
+
|
10
|
+
ActiveSupport.on_load :action_controller do
|
11
|
+
extend Controller
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AdequateExposure
|
2
|
+
class Attribute
|
3
|
+
attr_reader :name, :fetch, :ivar_name
|
4
|
+
|
5
|
+
def initialize(name:, fetch:, ivar_name:)
|
6
|
+
@name, @fetch, @ivar_name = name, fetch, ivar_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def getter_method_name
|
10
|
+
name.to_sym
|
11
|
+
end
|
12
|
+
|
13
|
+
def setter_method_name
|
14
|
+
"#{name}=".to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def expose!(klass)
|
18
|
+
attribute = self
|
19
|
+
|
20
|
+
klass.instance_eval do
|
21
|
+
define_method attribute.getter_method_name do
|
22
|
+
Context.new(self, attribute).get
|
23
|
+
end
|
24
|
+
|
25
|
+
define_method attribute.setter_method_name do |value|
|
26
|
+
Context.new(self, attribute).set(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "active_support/core_ext/module/delegation"
|
2
|
+
|
3
|
+
module AdequateExposure
|
4
|
+
class Context
|
5
|
+
attr_reader :context, :attribute
|
6
|
+
|
7
|
+
def initialize(context, attribute)
|
8
|
+
@context, @attribute = context, attribute
|
9
|
+
end
|
10
|
+
|
11
|
+
def get
|
12
|
+
ivar_defined?? ivar_get : set(fetch_value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def set(value)
|
16
|
+
ivar_set(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
delegate :instance_variable_set, :instance_variable_get,
|
22
|
+
:instance_variable_defined?, to: :context
|
23
|
+
|
24
|
+
def ivar_defined?
|
25
|
+
instance_variable_defined?(ivar_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def ivar_get
|
29
|
+
instance_variable_get(ivar_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def ivar_set(value)
|
33
|
+
instance_variable_set(ivar_name, value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ivar_name
|
37
|
+
"@#{attribute.ivar_name}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_value
|
41
|
+
context.instance_exec(&attribute.fetch)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "active_support/core_ext/hash/reverse_merge"
|
2
|
+
|
3
|
+
module AdequateExposure
|
4
|
+
module Controller
|
5
|
+
def expose(name, **options, &block)
|
6
|
+
options = options.merge(name: name)
|
7
|
+
options.reverse_merge! fetch: block if block_given?
|
8
|
+
Exposure.new(options).expose! self
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module AdequateExposure
|
2
|
+
class Exposure
|
3
|
+
attr_reader :options
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def expose!(controller)
|
10
|
+
expose_attribute! controller
|
11
|
+
expose_helper_methods! controller
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def expose_attribute!(controller)
|
17
|
+
attribute.expose! controller
|
18
|
+
end
|
19
|
+
|
20
|
+
def expose_helper_methods!(controller)
|
21
|
+
helper_methods = [ attribute.getter_method_name, attribute.setter_method_name ]
|
22
|
+
controller.helper_method *helper_methods
|
23
|
+
end
|
24
|
+
|
25
|
+
def attribute
|
26
|
+
@attribute ||= begin
|
27
|
+
local_options = options
|
28
|
+
|
29
|
+
name = options.fetch(:name)
|
30
|
+
ivar_name = "exposed_#{name}"
|
31
|
+
fetch = ->{ Flow.new(self, local_options).fetch }
|
32
|
+
|
33
|
+
Attribute.new(
|
34
|
+
name: name,
|
35
|
+
ivar_name: ivar_name,
|
36
|
+
fetch: fetch
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "active_support/hash_with_indifferent_access"
|
2
|
+
require "active_support/core_ext/array/wrap"
|
3
|
+
require "active_support/core_ext/string/inflections"
|
4
|
+
|
5
|
+
module AdequateExposure
|
6
|
+
class Flow
|
7
|
+
attr_reader :controller, :options
|
8
|
+
delegate :params, to: :controller
|
9
|
+
|
10
|
+
def initialize(controller, **options)
|
11
|
+
@controller, @options = controller, HashWithIndifferentAccess.new(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
options.fetch(:name)
|
16
|
+
end
|
17
|
+
|
18
|
+
%i[fetch find build scope model id decorate].each do |method_name|
|
19
|
+
define_method method_name do |*args|
|
20
|
+
ivar_name = "@#{method_name}"
|
21
|
+
return instance_variable_get(ivar_name) if instance_variable_defined?(ivar_name)
|
22
|
+
instance_variable_set(ivar_name, handle_action(method_name, *args))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def default_fetch
|
29
|
+
id ? decorate(find(id, scope)) : decorate(build(scope))
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_id
|
33
|
+
fetch_first_defined_param %I[#{name}_id id]
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_scope
|
37
|
+
model
|
38
|
+
end
|
39
|
+
|
40
|
+
def default_model
|
41
|
+
symbol_to_class(name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def default_find(id, scope)
|
45
|
+
scope.find(id)
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_build(scope)
|
49
|
+
scope.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_decorate(instance)
|
53
|
+
instance
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_custom_model(value)
|
57
|
+
Class === value ? value : symbol_to_class(value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_custom_scope(value)
|
61
|
+
scope_parent = controller.instance_exec{ send value }
|
62
|
+
scope_parent.send(name.to_s.pluralize)
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle_custom_id(value)
|
66
|
+
fetch_first_defined_param value
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def id_attribute_name
|
72
|
+
Array.wrap(possible_id_keys).detect{ |key| params.key?(key) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def handle_action(name, *args)
|
76
|
+
if options.key?(name)
|
77
|
+
handle_custom_action(name, *args)
|
78
|
+
else
|
79
|
+
send("default_#{name}", *args)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_custom_action(name, *args)
|
84
|
+
value = options[name]
|
85
|
+
|
86
|
+
if Proc === value
|
87
|
+
controller.instance_exec(*args, &value)
|
88
|
+
else
|
89
|
+
send("handle_custom_#{name}", value, *args)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def symbol_to_class(symbol)
|
94
|
+
symbol.to_s.classify.constantize
|
95
|
+
end
|
96
|
+
|
97
|
+
def fetch_first_defined_param(keys)
|
98
|
+
Array.wrap(keys).each do |key|
|
99
|
+
return params[key] if params.key?(key)
|
100
|
+
end
|
101
|
+
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AdequateExposure::Controller do
|
4
|
+
class Thing; end
|
5
|
+
class DifferentThing; end
|
6
|
+
|
7
|
+
class BaseController
|
8
|
+
def self.helper_method(*); end
|
9
|
+
|
10
|
+
def params
|
11
|
+
@params ||= HashWithIndifferentAccess.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:controller_klass) do
|
16
|
+
Class.new(BaseController) do
|
17
|
+
extend AdequateExposure::Controller
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:controller){ controller_klass.new }
|
22
|
+
|
23
|
+
def expose(*args, &block)
|
24
|
+
controller_klass.expose(*args, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
context "getter/setter methods" do
|
28
|
+
before{ expose :thing }
|
29
|
+
|
30
|
+
it "defines getter method" do
|
31
|
+
expect(controller).to respond_to(:thing)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "defines setter method" do
|
35
|
+
expect(controller).to respond_to(:thing=).with(1).argument
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "helper methods" do
|
40
|
+
it "exposes getter and setter as controller helper methods" do
|
41
|
+
expect(controller_klass).to receive(:helper_method).with(:thing, :thing=)
|
42
|
+
expose :thing
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with block" do
|
47
|
+
before do
|
48
|
+
expose(:thing){ compute_thing }
|
49
|
+
end
|
50
|
+
|
51
|
+
it "executes block to calculate the value" do
|
52
|
+
allow(controller).to receive(:compute_thing).and_return(42)
|
53
|
+
expect(controller.thing).to eq(42)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "executes the block once and memoizes the result" do
|
57
|
+
expect(controller).to receive(:compute_thing).once.and_return(42)
|
58
|
+
10.times{ controller.thing }
|
59
|
+
end
|
60
|
+
|
61
|
+
it "allows setting value directly" do
|
62
|
+
expect(controller).to_not receive(:compute_thing)
|
63
|
+
controller.thing = :foobar
|
64
|
+
expect(controller.thing).to eq(:foobar)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "redefine fetch" do
|
69
|
+
before do
|
70
|
+
expose :thing, fetch: ->{ compute_thing }
|
71
|
+
allow(controller).to receive(:compute_thing).and_return(42)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "uses provided fetch proc instead of default" do
|
75
|
+
expect(controller.thing).to eq(42)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "default behaviour" do
|
80
|
+
before{ expose :thing }
|
81
|
+
|
82
|
+
it "builds a new instance when id is not provided" do
|
83
|
+
expect(controller.thing).to be_instance_of(Thing)
|
84
|
+
end
|
85
|
+
|
86
|
+
context "find" do
|
87
|
+
before{ expect(Thing).to receive(:find).with(10) }
|
88
|
+
after{ controller.thing }
|
89
|
+
|
90
|
+
it "finds Thing if thing_id param is provided" do
|
91
|
+
controller.params.merge! thing_id: 10
|
92
|
+
end
|
93
|
+
|
94
|
+
it "finds Thing if id param if provided" do
|
95
|
+
controller.params.merge! id: 10
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "override model" do
|
101
|
+
after{ expect(controller.thing).to be_instance_of(DifferentThing) }
|
102
|
+
|
103
|
+
it "allows overriding model class with proc" do
|
104
|
+
expose :thing, model: ->{ DifferentThing }
|
105
|
+
end
|
106
|
+
|
107
|
+
it "allows overriding model with class" do
|
108
|
+
expose :thing, model: DifferentThing
|
109
|
+
end
|
110
|
+
|
111
|
+
it "allows overriding model class with symbol" do
|
112
|
+
expose :thing, model: :different_thing
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "override scope" do
|
117
|
+
it "allows overriding scope with proc" do
|
118
|
+
scope = double("Scope")
|
119
|
+
expose :thing, scope: ->{ scope }
|
120
|
+
expect(scope).to receive(:new).and_return(42)
|
121
|
+
expect(controller.thing).to eq(42)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "allows overriding with symbol" do
|
125
|
+
current_user = double("User")
|
126
|
+
scope = double("Scope")
|
127
|
+
scoped_thing = double("Thing")
|
128
|
+
|
129
|
+
expect(controller).to receive(:current_user).and_return(current_user)
|
130
|
+
expect(current_user).to receive(:things).and_return(scope)
|
131
|
+
expect(scope).to receive(:new).and_return(scoped_thing)
|
132
|
+
expose :thing, scope: :current_user
|
133
|
+
|
134
|
+
expect(controller.thing).to eq(scoped_thing)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "override id" do
|
139
|
+
after do
|
140
|
+
expect(Thing).to receive(:find).with(42)
|
141
|
+
controller.thing
|
142
|
+
end
|
143
|
+
|
144
|
+
it "allows overriding id with proc" do
|
145
|
+
expose :thing, id: ->{ get_thing_id_somehow }
|
146
|
+
expect(controller).to receive(:get_thing_id_somehow).and_return(42)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "allows overriding id with symbol" do
|
150
|
+
expose :thing, id: :custom_thing_id
|
151
|
+
controller.params.merge! thing_id: 10, custom_thing_id: 42
|
152
|
+
end
|
153
|
+
|
154
|
+
it "allows overriding id with an array of symbols" do
|
155
|
+
expose :thing, id: %i[non-existent-id lolwut another_id_param]
|
156
|
+
controller.params.merge! another_id_param: 42
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "override decorator" do
|
161
|
+
it "allows specify decorator" do
|
162
|
+
expose :thing, decorate: ->(thing){ decorate(thing) }
|
163
|
+
expect(controller).to receive(:decorate).with(an_instance_of(Thing))
|
164
|
+
controller.thing
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "support/rails_app"
|
3
|
+
require "rspec/rails"
|
4
|
+
|
5
|
+
describe BirdsController, type: :controller do
|
6
|
+
context "finds bird by id" do
|
7
|
+
let(:mockingbird){ double("Bird") }
|
8
|
+
before{ expect(Bird).to receive(:find).with("mockingbird").once.and_return(mockingbird) }
|
9
|
+
after{ expect(controller.bird).to eq(mockingbird) }
|
10
|
+
|
11
|
+
it "finds model by id" do
|
12
|
+
get :show, id: "mockingbird"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "finds model by bird_id" do
|
16
|
+
get :show, bird_id: "mockingbird"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "builds bird if id is not provided" do
|
21
|
+
bird = double("Bird")
|
22
|
+
expect(Bird).to receive(:new).and_return(bird)
|
23
|
+
get :show
|
24
|
+
expect(controller.bird).to eq(bird)
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "adequate_exposure"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
require "active_support/dependencies/autoload"
|
3
|
+
require "action_controller"
|
4
|
+
require "action_dispatch"
|
5
|
+
require "rails"
|
6
|
+
|
7
|
+
module Rails
|
8
|
+
class App
|
9
|
+
def env_config; {} end
|
10
|
+
|
11
|
+
def routes
|
12
|
+
@routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes|
|
13
|
+
routes.draw do
|
14
|
+
resource :birds
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.application
|
21
|
+
@app ||= App.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Bird
|
26
|
+
end
|
27
|
+
|
28
|
+
class ApplicationConroller < ActionController::Base
|
29
|
+
include Rails.application.routes.url_helpers
|
30
|
+
end
|
31
|
+
|
32
|
+
class BirdsController < ApplicationConroller
|
33
|
+
expose :bird
|
34
|
+
|
35
|
+
def show
|
36
|
+
render nothing: true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adequate_exposure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pavel Pravosud
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- pavel@pravosud.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- adequate_exposure.gemspec
|
54
|
+
- lib/adequate_exposure.rb
|
55
|
+
- lib/adequate_exposure/attribute.rb
|
56
|
+
- lib/adequate_exposure/context.rb
|
57
|
+
- lib/adequate_exposure/controller.rb
|
58
|
+
- lib/adequate_exposure/exposure.rb
|
59
|
+
- lib/adequate_exposure/flow.rb
|
60
|
+
- lib/adequate_exposure/version.rb
|
61
|
+
- spec/controller_spec.rb
|
62
|
+
- spec/integration_spec.rb
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
- spec/support/rails_app.rb
|
65
|
+
homepage: https://github.com/rwz/adequate_exposure
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '2.0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.2.2
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: More adequate than decent
|
89
|
+
test_files:
|
90
|
+
- spec/controller_spec.rb
|
91
|
+
- spec/integration_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
- spec/support/rails_app.rb
|