decent_exposure 2.3.3 → 3.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|
-
[](https://travis-ci.org/hashrocket/decent_exposure)
|
3
|
-
[](https://codeclimate.com/github/hashrocket/decent_exposure)
|
1
|
+

|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
[](https://rubygems.org/gems/decent_exposure)
|
4
|
+
[](http://travis-ci.org/hashrocket/decent_exposure)
|
5
|
+
[](https://codeclimate.com/github/hashrocket/decent_exposure)
|
6
|
+
[](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
|
+
[](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.
|