adequate_exposure 0.0.1
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 +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
|