decent_exposure 2.3.3 → 3.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +22 -0
- data/.travis.yml +3 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +200 -363
- data/Rakefile +6 -0
- data/decent_exposure.gemspec +30 -0
- data/decent_exposure.png +0 -0
- data/hashrocket_logo.png +0 -0
- data/lib/decent_exposure.rb +13 -4
- data/lib/decent_exposure/attribute.rb +55 -0
- data/lib/decent_exposure/behavior.rb +100 -0
- data/lib/decent_exposure/context.rb +61 -0
- data/lib/decent_exposure/controller.rb +53 -0
- data/lib/decent_exposure/exposure.rb +199 -9
- data/lib/decent_exposure/flow.rb +89 -0
- data/lib/decent_exposure/version.rb +2 -2
- data/spec/controller_spec.rb +374 -0
- data/spec/integration_spec.rb +26 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/rails_app.rb +37 -0
- metadata +63 -41
- data/lib/decent_exposure/active_record_strategy.rb +0 -84
- data/lib/decent_exposure/active_record_with_eager_attributes_strategy.rb +0 -8
- data/lib/decent_exposure/configuration.rb +0 -19
- data/lib/decent_exposure/constant_resolver.rb +0 -34
- data/lib/decent_exposure/error.rb +0 -4
- data/lib/decent_exposure/expose.rb +0 -62
- data/lib/decent_exposure/inflector.rb +0 -39
- data/lib/decent_exposure/strategies/assign_from_method.rb +0 -20
- data/lib/decent_exposure/strategies/assign_from_params.rb +0 -24
- data/lib/decent_exposure/strategizer.rb +0 -54
- data/lib/decent_exposure/strategy.rb +0 -47
- data/lib/decent_exposure/strong_parameters_strategy.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ec6d62cc52928970902ade2a9998e8f58758f7c
|
4
|
+
data.tar.gz: 0a3aff2440f88ed56a20197d270402c826c6b5e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c085171f6369dda3d9ae3f459aed654dcdc5a0934f28b61b605ffa1cbff9951cc5b44b214179d3969764064c748c7cddac8dc58655c7972f09dcaa6c5bec0bff
|
7
|
+
data.tar.gz: 692f0b40e7d1435ebaec5ac4d96422b8cb69592afcb3b5010c597f71f65f7c3f88588ee26dffd17ae38cd83ad269f5fa1581030bbf132ea3c4a63a8b38d1bf81
|
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/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Hashrocket
|
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
CHANGED
@@ -1,501 +1,338 @@
|
|
1
|
-
|
2
|
-
[![Build Status](https://api.travis-ci.org/hashrocket/decent_exposure.svg?branch=master)](https://travis-ci.org/hashrocket/decent_exposure)
|
3
|
-
[![Code Climate](https://codeclimate.com/github/hashrocket/decent_exposure/badges/gpa.svg)](https://codeclimate.com/github/hashrocket/decent_exposure)
|
1
|
+
![Decent Exposure](decent_exposure.png)
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
[![Gem Version](https://img.shields.io/gem/v/decent_exposure.svg)](https://rubygems.org/gems/decent_exposure)
|
4
|
+
[![Build Status](https://img.shields.io/travis/hashrocket/decent_exposure.svg)](http://travis-ci.org/hashrocket/decent_exposure)
|
5
|
+
[![Code Climate](https://img.shields.io/codeclimate/github/hashrocket/decent_exposure.svg)](https://codeclimate.com/github/hashrocket/decent_exposure)
|
6
|
+
[![Inline docs](http://inch-ci.org/github/hashrocket/decent_exposure.svg?branch=master&style=shields)](http://inch-ci.org/github/hashrocket/decent_exposure)
|
8
7
|
|
9
|
-
|
8
|
+
### Notice
|
10
9
|
|
11
|
-
|
12
|
-
large part, to the fact that they expose their instance variables directly to
|
13
|
-
their views. This means that your instance variables are your interface... and
|
14
|
-
that you've broken encapsulation. Instance variables are meant to be private,
|
15
|
-
for Science's sake!
|
10
|
+
DecentExposure `3.0` is a major change from `< 3.0`. If you are using `rails <= 4.2.x` please look at `decent_exposure < 3.0`.
|
16
11
|
|
17
|
-
|
12
|
+
**Be aware... mild API changes ahead.**
|
13
|
+
|
14
|
+
### Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
18
17
|
|
19
18
|
```ruby
|
20
|
-
|
21
|
-
|
22
|
-
@person = Person.new(params[:person])
|
23
|
-
end
|
19
|
+
gem 'decent_exposure'
|
20
|
+
```
|
24
21
|
|
25
|
-
|
26
|
-
@person = Person.new(params[:person])
|
27
|
-
if @person.save
|
28
|
-
redirect_to(@person)
|
29
|
-
else
|
30
|
-
render :new
|
31
|
-
end
|
32
|
-
end
|
22
|
+
And then execute:
|
33
23
|
|
34
|
-
|
35
|
-
@person = Person.find(params[:id])
|
36
|
-
end
|
24
|
+
$ bundle
|
37
25
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install decent_exposure
|
29
|
+
|
30
|
+
### API
|
31
|
+
|
32
|
+
The whole API consists of three methods so far: `expose`, `expose!`, and
|
33
|
+
`exposure_config`.
|
34
|
+
|
35
|
+
In the simplest scenario you'll just use it to expose a model in the
|
36
|
+
controller:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class ThingsController < ApplicationController
|
40
|
+
expose :thing
|
46
41
|
end
|
47
42
|
```
|
48
43
|
|
49
|
-
|
44
|
+
Now every time you call `thing` in your controller or view, it will look for an
|
45
|
+
ID and try to perform `Thing.find(id)`. If the ID isn't found, it will call
|
46
|
+
`Thing.new(things_params)`. The result will be memoized in an `@exposed_thing`
|
47
|
+
instance variable.
|
48
|
+
|
49
|
+
#### Example Controller
|
50
|
+
|
51
|
+
Here's what a standard Rails 4 CRUD controller using Decent Exposure might look like:
|
50
52
|
|
51
53
|
```ruby
|
52
|
-
class
|
53
|
-
expose
|
54
|
+
class ThingsController < ApplicationController
|
55
|
+
expose :things, ->{ Thing.all }
|
56
|
+
expose :thing
|
54
57
|
|
55
58
|
def create
|
56
|
-
if
|
57
|
-
redirect_to(
|
59
|
+
if thing.save
|
60
|
+
redirect_to thing_path(thing)
|
58
61
|
else
|
59
62
|
render :new
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
63
66
|
def update
|
64
|
-
if
|
65
|
-
redirect_to(
|
67
|
+
if thing.update(thing_params)
|
68
|
+
redirect_to thing_path(thing)
|
66
69
|
else
|
67
70
|
render :edit
|
68
71
|
end
|
69
72
|
end
|
70
|
-
end
|
71
|
-
```
|
72
|
-
|
73
|
-
And your views from this:
|
74
73
|
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
def destroy
|
75
|
+
thing.destroy
|
76
|
+
redirect_to things_path
|
77
|
+
end
|
78
78
|
|
79
|
-
|
79
|
+
private
|
80
80
|
|
81
|
-
|
82
|
-
|
81
|
+
def thing_params
|
82
|
+
params.require(:thing).permit(:foo, :bar)
|
83
|
+
end
|
84
|
+
end
|
83
85
|
```
|
84
86
|
|
85
|
-
|
87
|
+
### Under the Hood
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
-
...
|
90
|
-
```
|
91
|
-
|
92
|
-
To this:
|
89
|
+
The default resolving workflow is pretty powerful and customizable. It could be
|
90
|
+
expressed with the following pseudocode:
|
93
91
|
|
94
92
|
```ruby
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
`decent_exposure` makes it easy to define named methods that are made available
|
100
|
-
to your views and which memoize the resultant values. It also tucks away the
|
101
|
-
details of the common fetching, initializing and updating of resources and
|
102
|
-
their parameters.
|
103
|
-
|
104
|
-
That's neat and all, but the real advantage comes when it's time to refactor
|
105
|
-
(because you've encapsulated now). What happens when you need to scope your
|
106
|
-
`Person` resource from a `Company`? Which implementation isolates those changes
|
107
|
-
better? In that particular example, `decent_exposure` goes one step farther and
|
108
|
-
will handle the scoping for you (with a smidge of configuration) while still
|
109
|
-
handling all that repetitive initialization, as we'll see next.
|
110
|
-
|
111
|
-
Even if you decide not to use `decent_exposure`, do yourself a favor and stop
|
112
|
-
using instance variables in your views. Your code will be cleaner and easier to
|
113
|
-
refactor as a result. If you want to learn more about this approach, I've
|
114
|
-
expanded on my thoughts in the article [A Diatribe on Maintaining State][1].
|
93
|
+
def fetch(scope, id)
|
94
|
+
instance = id ? find(id, scope) : build(build_params, scope)
|
95
|
+
decorate(instance)
|
96
|
+
end
|
115
97
|
|
116
|
-
|
98
|
+
def id
|
99
|
+
params[:thing_id] || params[:id]
|
100
|
+
end
|
117
101
|
|
118
|
-
|
119
|
-
|
102
|
+
def find(id, scope)
|
103
|
+
scope.find(id)
|
104
|
+
end
|
120
105
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
record. In absence of `params[:id]` `decent_exposure` will try to build a new
|
125
|
-
record.
|
106
|
+
def build(params, scope)
|
107
|
+
scope.new(params) # Thing.new(params)
|
108
|
+
end
|
126
109
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
interact with `person` in your create action, just call save on it and handle
|
131
|
-
the valid/invalid branch. Let's revisit our previous example:
|
110
|
+
def scope
|
111
|
+
model # Thing
|
112
|
+
end
|
132
113
|
|
133
|
-
|
134
|
-
|
135
|
-
|
114
|
+
def model
|
115
|
+
exposure_name.classify.constantize # :thing -> Thing
|
116
|
+
end
|
136
117
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
end
|
118
|
+
def build_params
|
119
|
+
if respond_to?(:thing_params, true) && !request.get?
|
120
|
+
thing_params
|
121
|
+
else
|
122
|
+
{}
|
143
123
|
end
|
144
124
|
end
|
145
|
-
```
|
146
125
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
person.attributes = params[:person]
|
126
|
+
def decorate(thing)
|
127
|
+
thing
|
128
|
+
end
|
151
129
|
```
|
152
130
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
the typical `update_attributes(params[:person])`.
|
157
|
-
|
158
|
-
**An Aside**
|
131
|
+
The exposure is also lazy, which means that it won't do anything until you call
|
132
|
+
the method. To eliminate this laziness you can use the `expose!` macro instead,
|
133
|
+
which will try to resolve the exposure in a before filter.
|
159
134
|
|
160
|
-
|
161
|
-
|
162
|
-
state. Since we've declared an interface to our state and made it available to
|
163
|
-
the view (a.k.a. the place where we actually want to access it), we just let
|
164
|
-
Rails do it's magic and render the `new` view, lazily evaluating `person` when
|
165
|
-
we actually need it.
|
135
|
+
It is possible to override each step with options. The acceptable options to the
|
136
|
+
`expose` macro are:
|
166
137
|
|
167
|
-
|
138
|
+
### `fetch`
|
168
139
|
|
169
|
-
|
170
|
-
|
171
|
-
paradigm, we get an ugly `ActionView::TemplateError` instead. If this is
|
172
|
-
problematic for you, consider using the `expose!` method to circumvent lazy
|
173
|
-
evaluation and eagerly evaluate whilst still in the controller.
|
140
|
+
This is the entry point. The `fetch` proc defines how to resolve your exposure
|
141
|
+
in the first place.
|
174
142
|
|
175
|
-
|
143
|
+
```ruby
|
144
|
+
expose :thing, fetch: ->{ get_thing_some_way_or_another }
|
145
|
+
```
|
176
146
|
|
177
|
-
|
178
|
-
|
179
|
-
|
147
|
+
Because the above behavior overrides the normal workflow, all other options
|
148
|
+
would be ignored. However, Decent Exposure is decent enough to actually blow
|
149
|
+
up with an error so you don't accidentally do this.
|
180
150
|
|
181
|
-
|
151
|
+
There are other less verbose ways to pass the `fetch` block, since you'll
|
152
|
+
probably be using it often:
|
182
153
|
|
183
154
|
```ruby
|
184
|
-
expose(:
|
155
|
+
expose(:thing){ get_thing_some_way_or_another }
|
185
156
|
```
|
186
157
|
|
187
|
-
|
188
|
-
|
189
|
-
<table>
|
190
|
-
<tr>
|
191
|
-
<td><code>id</code> present?</td>
|
192
|
-
<td>Query</td>
|
193
|
-
</tr>
|
194
|
-
<tr>
|
195
|
-
<td><code>true</code></td>
|
196
|
-
<td><code>Person.find(params[:id])</code></td>
|
197
|
-
</tr>
|
198
|
-
<tr>
|
199
|
-
<td><code>false</code></td>
|
200
|
-
<td><code>Person.new(params[:person])</code></td>
|
201
|
-
</tr>
|
202
|
-
</table>
|
203
|
-
|
204
|
-
### Obtaining a collection of objects
|
158
|
+
Or
|
205
159
|
|
206
160
|
```ruby
|
207
|
-
expose
|
161
|
+
expose :thing, ->{ get_thing_some_way_or_another }
|
208
162
|
```
|
209
163
|
|
210
|
-
|
211
|
-
|
212
|
-
<table>
|
213
|
-
<tr>
|
214
|
-
<td>Query</td>
|
215
|
-
</tr>
|
216
|
-
<tr>
|
217
|
-
<td><code>Person.scoped</code></td>
|
218
|
-
</tr>
|
219
|
-
</table>
|
220
|
-
|
221
|
-
### Scoping your object queries
|
222
|
-
|
223
|
-
Want to scope your queries to ensure object hierarchy? `decent_exposure`
|
224
|
-
automatically scopes singular forms of a resource from a plural form where
|
225
|
-
they're defined:
|
164
|
+
Or even shorter
|
226
165
|
|
227
166
|
```ruby
|
228
|
-
expose
|
229
|
-
expose(:person)
|
167
|
+
expose :thing, :get_thing_some_way_or_another
|
230
168
|
```
|
231
169
|
|
232
|
-
|
233
|
-
|
234
|
-
<table>
|
235
|
-
<tr>
|
236
|
-
<td><code>id</code> present?</td>
|
237
|
-
<td>Query</td>
|
238
|
-
</tr>
|
239
|
-
<tr>
|
240
|
-
<td><code>true</code></td>
|
241
|
-
<td><code>Person.scoped.find(params[:id])</code></td>
|
242
|
-
</tr>
|
243
|
-
<tr>
|
244
|
-
<td><code>false</code></td>
|
245
|
-
<td><code>Person.scoped.new(params[:person])</code></td>
|
246
|
-
</tr>
|
247
|
-
</table>
|
248
|
-
|
249
|
-
How about a more realistic scenario where the object hierarchy specifies
|
250
|
-
something useful, like only finding people in a given company:
|
170
|
+
There is another shortcut that allows you to redefine the entire fetch block
|
171
|
+
with less code:
|
251
172
|
|
252
173
|
```ruby
|
253
|
-
expose
|
254
|
-
|
255
|
-
expose
|
174
|
+
expose :comments, from: :post
|
175
|
+
# equivalent to
|
176
|
+
expose :comments, ->{ post.comments }
|
256
177
|
```
|
257
178
|
|
258
|
-
|
259
|
-
|
260
|
-
<table>
|
261
|
-
<tr>
|
262
|
-
<td>person <code>id</code> present?</td>
|
263
|
-
<td>Query</td>
|
264
|
-
</tr>
|
265
|
-
<tr>
|
266
|
-
<td><code>true</code></td>
|
267
|
-
<td><code>Company.find(params[:company_id]).people.find(params[:id])</code></td>
|
268
|
-
</tr>
|
269
|
-
<tr>
|
270
|
-
<td><code>false</code></td>
|
271
|
-
<td><code>Company.find(params[:company_id]).people.new(params[:person])</code></td>
|
272
|
-
</tr>
|
273
|
-
</table>
|
179
|
+
### `id`
|
274
180
|
|
275
|
-
|
181
|
+
The default fetch logic relies on the presence of an ID. And of course Decent
|
182
|
+
Exposure allows you to specify how exactly you want the ID to be extracted.
|
276
183
|
|
277
|
-
|
278
|
-
things you can do:
|
279
|
-
|
280
|
-
**Specify the model name:**
|
184
|
+
Default behavior could be expressed using following code:
|
281
185
|
|
282
186
|
```ruby
|
283
|
-
expose
|
187
|
+
expose :thing, id: ->{ params[:thing_id] || params[:id] }
|
284
188
|
```
|
285
189
|
|
286
|
-
|
190
|
+
But nothing is stopping you from throwing in any arbitrary code:
|
287
191
|
|
288
192
|
```ruby
|
289
|
-
expose
|
193
|
+
expose :thing, id: ->{ 42 }
|
290
194
|
```
|
291
195
|
|
292
|
-
|
196
|
+
Passing lambdas might not always be fun, so here are a couple of shortcuts that
|
197
|
+
could help make life easier.
|
293
198
|
|
294
199
|
```ruby
|
295
|
-
expose
|
200
|
+
expose :thing, id: :custom_thing_id
|
201
|
+
# equivalent to
|
202
|
+
expose :thing, id: ->{ params[:custom_thing_id] }
|
203
|
+
|
204
|
+
expose :thing, id: [:try_this_id, :or_maybe_that_id]
|
205
|
+
# equivalent to
|
206
|
+
expose :thing, id: ->{ params[:try_this_id] || params[:or_maybe_that_id] }
|
296
207
|
```
|
297
208
|
|
298
|
-
|
209
|
+
### `find`
|
210
|
+
|
211
|
+
If an ID was provided, Decent Exposure will try to find the model using it.
|
212
|
+
Default behavior could be expressed with this configuration:
|
299
213
|
|
300
214
|
```ruby
|
301
|
-
expose
|
215
|
+
expose :thing, find: ->(id, scope){ scope.find(id) }
|
302
216
|
```
|
303
217
|
|
304
|
-
|
218
|
+
Where `scope` is a model scope, like `Thing` or `User.active` or
|
219
|
+
`Post.published`.
|
305
220
|
|
306
|
-
|
307
|
-
|
308
|
-
exposure block, a better approach is the use the controller's setter
|
309
|
-
methods:
|
221
|
+
Now, if you're using FriendlyId or Stringex or something similar, you'd have to
|
222
|
+
customize your finding logic. Your code might look somewhat like this:
|
310
223
|
|
311
224
|
```ruby
|
312
|
-
expose(
|
313
|
-
expose(:article)
|
314
|
-
|
315
|
-
def index
|
316
|
-
self.articles = Article.all
|
317
|
-
end
|
225
|
+
expose :thing, find: ->(id, scope){ scope.find_by!(slug: id) }
|
318
226
|
```
|
319
227
|
|
320
|
-
|
321
|
-
|
322
|
-
While we try to make things as easy for you as possible, sometimes you just
|
323
|
-
need to go off the beaten path. For those times, `expose` takes a block which
|
324
|
-
it lazily evaluates and returns the result of when called. So for instance:
|
228
|
+
Again, because this is likely to happen a lot, Decent Exposure gives you a
|
229
|
+
decent shortcut so you can get more done by typing less.
|
325
230
|
|
326
231
|
```ruby
|
327
|
-
expose
|
232
|
+
expose :thing, find_by: :slug
|
328
233
|
```
|
329
234
|
|
330
|
-
|
331
|
-
`environment`.
|
332
|
-
|
333
|
-
#### Using the Default decent_exposure Goodness
|
235
|
+
### `build`
|
334
236
|
|
335
|
-
|
336
|
-
|
337
|
-
receive a proxy object that you can use to lazily evaluate
|
338
|
-
the default decent_exposure logic. For example:
|
237
|
+
When an ID is not present, Decent Exposure tries to build an object for you.
|
238
|
+
By default, it behaves like this:
|
339
239
|
|
340
240
|
```ruby
|
341
|
-
expose(
|
241
|
+
expose :thing, build: ->(thing_params, scope){ scope.new(thing_params) }
|
342
242
|
```
|
343
243
|
|
344
|
-
|
345
|
-
the built-in logic decent_exposure gives you out of the box.
|
244
|
+
### `build_params`
|
346
245
|
|
347
|
-
|
246
|
+
These options are responsible for calulating params before passing them to the
|
247
|
+
build step. The default behavior was modeled with Strong Parameters in mind and
|
248
|
+
is somewhat smart: it calls the `thing_params` controller method if it's
|
249
|
+
available and the request method is not `GET`. In all other cases it produces
|
250
|
+
an empty hash.
|
348
251
|
|
349
|
-
|
350
|
-
`
|
351
|
-
scoping a resource from `current_user` is not an option, but you'd like
|
352
|
-
to verify a resource's relationship to the `current_user`, you can use a
|
353
|
-
custom strategy like the following:
|
252
|
+
You can easily specify which controller method you want it to call instead of
|
253
|
+
`thing_params`, or just provide your own logic:
|
354
254
|
|
355
255
|
```ruby
|
356
|
-
|
357
|
-
|
256
|
+
expose :thing, build_params: :custom_thing_params
|
257
|
+
expose :other_thing, build_params: ->{ { foo: "bar" } }
|
358
258
|
|
359
|
-
|
360
|
-
instance = model.find(params[:id])
|
361
|
-
if current_user != instance.user
|
362
|
-
raise ActiveRecord::RecordNotFound
|
363
|
-
end
|
364
|
-
instance
|
365
|
-
end
|
366
|
-
end
|
367
|
-
```
|
368
|
-
|
369
|
-
You would then use your custom strategy in your controller:
|
259
|
+
private
|
370
260
|
|
371
|
-
|
372
|
-
|
261
|
+
def custom_thing_params
|
262
|
+
# strong parameters stuff goes here
|
263
|
+
end
|
373
264
|
```
|
374
265
|
|
375
|
-
|
376
|
-
common helpers to access common things, such as the `params` hash. For
|
377
|
-
everything else, you can delegate to `controller`, which is the same as
|
378
|
-
`self` in the context of a normal controller action.
|
379
|
-
|
380
|
-
### Customizing your exposures
|
266
|
+
### `scope`
|
381
267
|
|
382
|
-
|
383
|
-
the desired behavior. For changes you want to affect every call to `expose` in
|
384
|
-
a controller or controllers inheriting from it (e.g. `ApplicationController`,
|
385
|
-
if you need to change the behavior for all your controllers), you can define
|
386
|
-
an `decent_configuration` block:
|
268
|
+
Defines the scope that's used in `find` and `build` steps.
|
387
269
|
|
388
270
|
```ruby
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
end
|
393
|
-
end
|
271
|
+
expose :thing, scope: ->{ current_user.things }
|
272
|
+
expose :user, scope: ->{ User.active }
|
273
|
+
expose :post, scope: ->{ Post.published }
|
394
274
|
```
|
395
275
|
|
396
|
-
|
397
|
-
"default" configuration for that controller (and it's ancestors). All things
|
398
|
-
considered, you probably only want to change the strategy in a default.
|
399
|
-
Nonetheless, you can pass any configuration option you can to an individual
|
400
|
-
exposure to the `decent_configuration` block.
|
401
|
-
|
402
|
-
If you don't want a specific configuration to affect every exposure in the
|
403
|
-
given controller, you can give it a name like so:
|
276
|
+
Like before, shortcuts are there to make you happier:
|
404
277
|
|
405
278
|
```ruby
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
finder_parameter :slug
|
410
|
-
end
|
411
|
-
end
|
279
|
+
expose :post, scope: :published
|
280
|
+
# equivalent to
|
281
|
+
expose :post, scope: ->{ Post.published }
|
412
282
|
```
|
413
283
|
|
414
|
-
|
284
|
+
and
|
415
285
|
|
416
286
|
```ruby
|
417
|
-
expose
|
287
|
+
expose :thing, parent: :current_user
|
288
|
+
# equivalent to:
|
289
|
+
expose :thing, scope: ->{ current_user.things }
|
418
290
|
```
|
419
291
|
|
420
|
-
|
292
|
+
### `model`
|
421
293
|
|
422
|
-
|
423
|
-
[strong\_parameters](https://github.com/rails/strong_parameters), add the
|
424
|
-
following to your ApplicationController:
|
294
|
+
Allows you to specify the model class to use. Pretty straightforward.
|
425
295
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
end
|
432
|
-
end
|
296
|
+
```ruby
|
297
|
+
expose :thing, model: ->{ AnotherThing }
|
298
|
+
expose :thing, model: AnotherThing
|
299
|
+
expose :thing, model: "AnotherThing"
|
300
|
+
expose :thing, model: :another_thing
|
433
301
|
```
|
434
302
|
|
435
|
-
|
436
|
-
`attributes` option to your exposure:
|
303
|
+
### `decorate`
|
437
304
|
|
438
|
-
|
439
|
-
|
440
|
-
|
305
|
+
Before returning the thing, Decent Exposure will run it through the
|
306
|
+
decoration process. Initially, this does nothing, but you can obviously change
|
307
|
+
that:
|
441
308
|
|
442
|
-
|
443
|
-
|
444
|
-
params.require(:foo).permit(:bar, :baz)
|
445
|
-
end
|
446
|
-
end
|
309
|
+
```ruby
|
310
|
+
expose :thing, decorate: ->(thing){ ThingDecorator.new(thing) }
|
447
311
|
```
|
448
312
|
|
449
|
-
|
450
|
-
PATCH request.
|
313
|
+
## `exposure_config`
|
451
314
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
Controller testing remains trivially easy. The shift is that you now set expectations on methods rather than instance variables. With RSpec, this mostly means avoiding `assign` and `assigns`.
|
315
|
+
You can pre-save some configuration with `exposure_config` method to reuse it
|
316
|
+
later.
|
456
317
|
|
457
318
|
```ruby
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
# this...
|
462
|
-
it "assigns @companies" do
|
463
|
-
company = Company.create
|
464
|
-
get :index
|
465
|
-
assigns(:companies).should eq([company])
|
466
|
-
end
|
319
|
+
exposure_config :cool_find, find: ->{ very_cool_find_code }
|
320
|
+
exposure_config :cool_build, build: ->{ very_cool_build_code }
|
467
321
|
|
468
|
-
|
469
|
-
|
470
|
-
company = Company.create
|
471
|
-
get :index
|
472
|
-
controller.companies.should eq([company])
|
473
|
-
end
|
474
|
-
end
|
475
|
-
end
|
322
|
+
expose :thing, with: [:cool_find, :cool_build]
|
323
|
+
expose :another_thing, with: :cool_build
|
476
324
|
```
|
477
325
|
|
478
|
-
|
326
|
+
## Contributing
|
479
327
|
|
480
|
-
|
481
|
-
|
328
|
+
1. Fork it (https://github.com/hashrocket/decent_exposure/fork)
|
329
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
330
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
331
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
332
|
+
5. Create a new Pull Request
|
482
333
|
|
483
|
-
|
484
|
-
it "lists people" do
|
485
|
-
assign(:people, [ mock_model(Person, name: 'John Doe') ])
|
486
|
-
render
|
487
|
-
rendered.should have_content('John Doe')
|
488
|
-
end
|
489
|
-
|
490
|
-
# becomes this
|
491
|
-
it "lists people" do
|
492
|
-
view.stub(people: [ mock_model(Person, name: 'John Doe') ])
|
493
|
-
render
|
494
|
-
rendered.should have_content('John Doe')
|
495
|
-
end
|
496
|
-
|
497
|
-
end
|
498
|
-
```
|
334
|
+
## About
|
499
335
|
|
336
|
+
[![Hashrocket logo](hashrocket_logo.png)](https://hashrocket.com)
|
500
337
|
|
501
|
-
[
|
338
|
+
Decent Exposure is supported by the team at [Hashrocket](https://hashrocket.com), a multidisciplinary design & development consultancy. If you'd like to [work with us](https://hashrocket.com/contact-us/hire-us) or [join our team](https://hashrocket.com/contact-us/jobs), don't hesitate to get in touch.
|