resourcerer 0.2.2 → 2.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +7 -0
- data/README.md +207 -186
- data/lib/resourcerer.rb +12 -3
- data/lib/resourcerer/configuration.rb +166 -0
- data/lib/resourcerer/controller.rb +50 -0
- data/lib/resourcerer/resource.rb +195 -10
- data/lib/resourcerer/version.rb +5 -0
- data/spec/features/guitars_controller_spec.rb +51 -0
- data/spec/resourcerer/controller_spec.rb +400 -0
- data/spec/resourcerer/param_key_spec.rb +48 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/rails_app.rb +60 -0
- metadata +103 -32
- data/lib/resourcerer/inflector.rb +0 -33
- data/lib/resourcerer/resource_configuration.rb +0 -45
- data/lib/resourcerer/resourceable.rb +0 -39
- data/lib/resourcerer/strategies/assign_attributes.rb +0 -34
- data/lib/resourcerer/strategies/assign_from_method.rb +0 -23
- data/lib/resourcerer/strategies/assign_from_params.rb +0 -13
- data/lib/resourcerer/strategies/default_strategy.rb +0 -34
- data/lib/resourcerer/strategies/eager_attributes_strategy.rb +0 -10
- data/lib/resourcerer/strategies/strong_parameters_strategy.rb +0 -10
- data/lib/resourcerer/strategy.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1097af8bba31336db7caf41e84c05587cb1f43fb3561405c28e73f9a034d510d
|
4
|
+
data.tar.gz: fce0ba2273e4632d6ea879d97d3b3de6a66c9ba8398416ad488b8b81ade15bcb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8cc87a1601df6643f9abb2134b236379f00bca9a39ae088f2670051651b2e422f896c1684602c215d1a98c72576a2f8c46f3310839fd4cc0d2a392990febdc8
|
7
|
+
data.tar.gz: 9e9d1ab0976fcdccb8987b90d2368ae46dc65f1848787a51644e31ba213e16351b77a85739f9b2ab49ab7c02a3cda1687380e3f0dfc876273038c1cc3e2499ce
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -1,294 +1,315 @@
|
|
1
|
+
<h1 align="center">
|
1
2
|
Resourcerer
|
2
|
-
|
3
|
-
|
3
|
+
<p align="center">
|
4
|
+
<a href="https://github.com/ElMassimo/resourcerer/actions">
|
5
|
+
<img alt="Build Status" src="https://github.com/ElMassimo/resourcerer/workflows/build/badge.svg"/>
|
6
|
+
</a>
|
7
|
+
<a href="https://codeclimate.com/github/ElMassimo/vite_ruby">
|
8
|
+
<img alt="Maintainability" src="https://codeclimate.com/github/ElMassimo/vite_ruby/badges/gpa.svg"/>
|
9
|
+
</a>
|
10
|
+
<a href="https://codeclimate.com/github/ElMassimo/resourcerer">
|
11
|
+
<img alt="Test Coverage" src="https://codeclimate.com/github/ElMassimo/resourcerer/badges/coverage.svg"/>
|
12
|
+
</a>
|
13
|
+
<a href="https://rubygems.org/gems/resourcerer">
|
14
|
+
<img alt="Gem Version" src="https://img.shields.io/gem/v/resourcerer.svg?colorB=e9573f"/>
|
15
|
+
</a>
|
16
|
+
<a href="https://github.com/ElMassimo/resourcerer/blob/master/LICENSE.txt">
|
17
|
+
<img alt="License" src="https://img.shields.io/badge/license-MIT-428F7E.svg"/>
|
18
|
+
</a>
|
19
|
+
</p>
|
20
|
+
</h1>
|
21
|
+
|
22
|
+
A small library to help you avoid boilerplate for standard CRUD actions, while improving your controllers' readibility.
|
23
|
+
|
24
|
+
### Installation
|
25
|
+
|
26
|
+
Add this line to your application's Gemfile:
|
4
27
|
|
5
28
|
```ruby
|
6
|
-
|
7
|
-
|
8
|
-
@person = Person.new
|
9
|
-
end
|
29
|
+
gem 'resourcerer'
|
30
|
+
```
|
10
31
|
|
11
|
-
|
12
|
-
@person = Person.new(person_params)
|
13
|
-
if @person.save
|
14
|
-
redirect_to(@person)
|
15
|
-
else
|
16
|
-
render :new
|
17
|
-
end
|
18
|
-
end
|
32
|
+
And then execute:
|
19
33
|
|
20
|
-
|
21
|
-
@person = Person.find(params[:id])
|
22
|
-
end
|
34
|
+
$ bundle
|
23
35
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
params.require(:person).permit(:name)
|
36
|
-
end
|
36
|
+
Or install it yourself as:
|
37
|
+
|
38
|
+
$ gem install resourcerer
|
39
|
+
|
40
|
+
### Usage
|
41
|
+
|
42
|
+
In the simplest scenario you'll just use it to define a resource in the controller:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class BandsController < ApplicationController
|
46
|
+
resource :band
|
37
47
|
end
|
38
48
|
```
|
39
49
|
|
40
|
-
|
50
|
+
Now every time you call `band` in your controller or view, it will look for an
|
51
|
+
ID and try to perform `Band.find(id)`. If an ID parameter isn't found, it will
|
52
|
+
call `Band.new(band_params)`. The result will be memoized in a
|
53
|
+
`@resourcerer_band` instance variable.
|
54
|
+
|
55
|
+
#### Example
|
56
|
+
|
57
|
+
Here's what a standard Rails CRUD controller using Resourcerer might look like:
|
41
58
|
|
42
59
|
```ruby
|
43
|
-
class
|
44
|
-
resource :
|
60
|
+
class BandsController < ApplicationController
|
61
|
+
resource :band do
|
62
|
+
permit [:name, :genre]
|
63
|
+
end
|
45
64
|
|
46
65
|
def create
|
47
|
-
if
|
48
|
-
redirect_to(
|
66
|
+
if band.save
|
67
|
+
redirect_to band_path(band)
|
49
68
|
else
|
50
69
|
render :new
|
51
70
|
end
|
52
71
|
end
|
53
72
|
|
54
73
|
def update
|
55
|
-
if
|
56
|
-
redirect_to(
|
74
|
+
if band.save
|
75
|
+
redirect_to band_path(band)
|
57
76
|
else
|
58
77
|
render :edit
|
59
78
|
end
|
60
79
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
80
|
+
|
81
|
+
def destroy
|
82
|
+
band.destroy
|
83
|
+
redirect_to bands_path
|
84
|
+
end
|
66
85
|
end
|
67
86
|
```
|
68
87
|
|
69
|
-
|
88
|
+
That's [way less code than usual!](https://gist.github.com/ElMassimo/18fbdb7108f46f3c975712945f7a3318) :smiley:
|
70
89
|
|
71
|
-
|
90
|
+
### Under the Hood
|
72
91
|
|
73
|
-
|
92
|
+
The default resolving workflow is pretty powerful and customizable. It could be
|
93
|
+
expressed with the following pseudocode:
|
74
94
|
|
75
|
-
|
95
|
+
```ruby
|
96
|
+
def fetch(scope, id)
|
97
|
+
instance = id ? find(id, scope) : build(attrs, scope)
|
98
|
+
instance.tap { instance.assign_attributes(attrs) if assign? }
|
99
|
+
end
|
76
100
|
|
77
|
-
|
101
|
+
def id
|
102
|
+
params[:band_id] || params[:id]
|
103
|
+
end
|
78
104
|
|
79
|
-
|
80
|
-
|
81
|
-
|
105
|
+
def find(id, scope)
|
106
|
+
scope.find(id)
|
107
|
+
end
|
108
|
+
|
109
|
+
def build(params, scope)
|
110
|
+
scope.new(params) # Band.new(params)
|
111
|
+
end
|
112
|
+
|
113
|
+
def scope
|
114
|
+
model # Band
|
115
|
+
end
|
82
116
|
|
83
|
-
|
117
|
+
def model
|
118
|
+
:band.classify.constantize # Band
|
119
|
+
end
|
84
120
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
<td>Query (get/delete)</td>
|
89
|
-
<td>Query (post/patch/put)</td>
|
90
|
-
</tr>
|
91
|
-
<tr>
|
92
|
-
<td><code>true</code></td>
|
93
|
-
<td><code>Person.find(params[:id])</code></td>
|
94
|
-
<td><code>Person.find(params[:id]).attributes = person_params</code></td>
|
95
|
-
</tr>
|
96
|
-
<tr>
|
97
|
-
<td><code>false</code></td>
|
98
|
-
<td><code>Person.new</code></td>
|
99
|
-
<td><code>Person.new(person_params)</code></td>
|
100
|
-
</tr>
|
101
|
-
</table>
|
121
|
+
def assign?
|
122
|
+
action_name == 'update'
|
123
|
+
end
|
102
124
|
|
125
|
+
def attrs
|
126
|
+
if respond_to?(:band_params, true) && !request.get?
|
127
|
+
band_params
|
128
|
+
else
|
129
|
+
{}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
The resource is lazy, so it won't do anyband until the method is called.
|
103
134
|
|
104
|
-
|
135
|
+
## Configuration
|
105
136
|
|
106
|
-
|
137
|
+
It is possible to override each step with options. The acceptable options to the
|
138
|
+
`resource` macro are:
|
107
139
|
|
108
|
-
|
140
|
+
### `id`
|
141
|
+
|
142
|
+
In order to fetch a resource Resourcerer relies on the presence of an ID:
|
109
143
|
|
110
144
|
```ruby
|
111
|
-
|
145
|
+
# Default Behavior
|
146
|
+
resource :band, id: ->{ params[:band_id] || params[:id] }
|
112
147
|
```
|
113
148
|
|
114
|
-
|
149
|
+
You can override any option's default behavior by passing in a `Proc`:
|
115
150
|
|
116
151
|
```ruby
|
117
|
-
resource
|
152
|
+
resource :band, id: ->{ 42 }
|
118
153
|
```
|
119
154
|
|
120
|
-
|
155
|
+
Passing lambdas might not always be fun, so most options provide shortcuts that
|
156
|
+
might help make life easier:
|
121
157
|
|
122
158
|
```ruby
|
123
|
-
resource
|
159
|
+
resource :band, id: :custom_band_id
|
160
|
+
# same as
|
161
|
+
resource :band, id: ->{ params[:custom_band_id] }
|
162
|
+
|
163
|
+
resource :band, id: [:try_this_id, :or_maybe_that_id]
|
164
|
+
# same as
|
165
|
+
resource :band, id: ->{ params[:try_this_id] || params[:or_maybe_that_id] }
|
124
166
|
```
|
125
167
|
|
126
|
-
|
168
|
+
### `find`
|
169
|
+
|
170
|
+
If an ID was provided, Resourcerer will try to find the model:
|
127
171
|
|
128
172
|
```ruby
|
129
|
-
#
|
130
|
-
resource
|
173
|
+
# Default Behavior
|
174
|
+
resource :band, find: -> (id, scope) { scope.find(id) }
|
175
|
+
```
|
176
|
+
|
177
|
+
Where `scope` is a model scope, like `Band` or `User.active` or
|
178
|
+
`Post.published`. There's even a convenient shortcut for cases where the ID is
|
179
|
+
actually something else:
|
131
180
|
|
132
|
-
|
133
|
-
resource
|
181
|
+
```ruby
|
182
|
+
resource :band, find_by: :slug
|
183
|
+
# same as
|
184
|
+
resource :band, find: ->(slug, scope){ scope.find_by!(slug: slug) }
|
134
185
|
```
|
135
186
|
|
136
|
-
###
|
137
|
-
|
138
|
-
|
187
|
+
### `build`
|
188
|
+
|
189
|
+
When an ID is not present, Resourcerer tries to build an object for you:
|
139
190
|
|
140
|
-
You can also access every configuration option available above:
|
141
191
|
```ruby
|
142
|
-
|
143
|
-
|
144
|
-
find_by :name
|
145
|
-
find {|name| company.find_employee(name) }
|
146
|
-
build { company.new_employee }
|
147
|
-
assign { params.require(:employee).permit(:name) }
|
148
|
-
end
|
149
|
-
# is the same as:
|
150
|
-
resource(:employee, model: :person, finder_attribute: :name, finder: ->(name){ company.find_employee(name) }, builder: ->{ company.new_employee }, attributes: ->{ params.require(:employee).permit(:name) })
|
192
|
+
# Default Behavior
|
193
|
+
resource :band, build: ->(attrs, scope){ scope.new(band_params) }
|
151
194
|
```
|
152
|
-
The DSL is more convenient when you have an object oriented design and want to allow an object to handle its collections, or as a quick way to set the StrongParameters method.
|
153
195
|
|
154
|
-
|
155
|
-
setting the `finder_attribute` in the example abovie changes the `finder_param` to `person_name` instead of `person_id`, and the value of that parameter is provided to the finder block.
|
196
|
+
### `attrs`
|
156
197
|
|
157
|
-
|
198
|
+
This option is responsible for calulating params before passing them to the
|
199
|
+
build step. The default behavior was modeled with Strong Parameters in mind and
|
200
|
+
is somewhat smart: it calls the `band_params` controller method if it's
|
201
|
+
available and the request method is not `GET`. In all other cases it produces
|
202
|
+
an empty hash.
|
158
203
|
|
159
|
-
|
160
|
-
|
161
|
-
controller's setter methods. This example uses [presenter_rails](https://github.com/ElMassimo/presenter_rails).
|
204
|
+
You can easily specify which controller method you want it to call instead of
|
205
|
+
`band_params`, or just provide your own logic:
|
162
206
|
|
163
207
|
```ruby
|
164
|
-
resource
|
208
|
+
resource :band, attrs: :custom_band_params
|
209
|
+
resource :other_band, attrs: ->{ { foo: "bar" } }
|
165
210
|
|
166
|
-
|
167
|
-
self.article = Article.find_oldest
|
168
|
-
end
|
211
|
+
private
|
169
212
|
|
170
|
-
|
171
|
-
|
213
|
+
def custom_band_params
|
214
|
+
params.require(:band).permit(:name, :genre)
|
172
215
|
end
|
173
216
|
```
|
174
217
|
|
175
|
-
|
176
|
-
|
177
|
-
For times when you need custom behavior for resource finding, you can
|
178
|
-
create your own strategy by extending `Resourcerer::Strategy`:
|
218
|
+
Using the default model name conventions? `permit` can do that for you:
|
179
219
|
|
180
220
|
```ruby
|
181
|
-
|
182
|
-
|
221
|
+
resource :band, permit: [:name, :genre]
|
222
|
+
```
|
183
223
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
end
|
191
|
-
end
|
224
|
+
### `collection`
|
225
|
+
|
226
|
+
Defines the scope that's used in `find` and `build` steps:
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
resource :band, collection: ->{ current_user.bands }
|
192
230
|
```
|
193
231
|
|
194
|
-
|
232
|
+
### `model`
|
233
|
+
|
234
|
+
Allows you to specify the model class to use:
|
195
235
|
|
196
236
|
```ruby
|
197
|
-
resource
|
237
|
+
resource :band, model: ->{ AnotherBand }
|
238
|
+
resource :band, model: AnotherBand
|
239
|
+
resource :band, model: "AnotherBand"
|
240
|
+
resource :band, model: :another_band
|
198
241
|
```
|
199
242
|
|
200
|
-
|
201
|
-
### With [draper](http://github.com/drapergem/draper)
|
243
|
+
### `assign` and `assign?`
|
202
244
|
|
203
|
-
|
245
|
+
Allows you to specify whether the attributes should be assigned:
|
204
246
|
|
205
247
|
```ruby
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
248
|
+
resource :band, assign?: false
|
249
|
+
resource :band, assign?: [:edit, :update]
|
250
|
+
resource :band, assign?: ->{ current_user.admin? }
|
251
|
+
```
|
210
252
|
|
211
|
-
|
212
|
-
@person = Person.new(person_params)
|
213
|
-
if @person.save
|
214
|
-
redirect_to(@person)
|
215
|
-
else
|
216
|
-
@person = @person.decorate
|
217
|
-
render :new
|
218
|
-
end
|
219
|
-
end
|
253
|
+
and also how to assign them:
|
220
254
|
|
221
|
-
|
222
|
-
|
223
|
-
end
|
255
|
+
```ruby
|
256
|
+
resource :band, assign: ->(band, attrs) { band.set_info(attrs) }
|
224
257
|
|
225
|
-
def update
|
226
|
-
@person = Person.find(params[:id])
|
227
|
-
if @person.update_attributes(person_params)
|
228
|
-
redirect_to(@person)
|
229
|
-
else
|
230
|
-
@person = @person.decorate
|
231
|
-
render :edit
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
private
|
236
|
-
def person_params
|
237
|
-
params.require(:person).permit(:name)
|
238
|
-
end
|
239
|
-
end
|
240
258
|
```
|
241
259
|
|
242
|
-
|
260
|
+
|
261
|
+
## Advanced Configuration with `resourcerer_config`
|
262
|
+
|
263
|
+
You can define configuration presets with the `resourcerer_config` method to reuse
|
264
|
+
them later in different resource definitions.
|
243
265
|
|
244
266
|
```ruby
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
267
|
+
resourcerer_config :cool_find, find: ->{ very_cool_find_code }
|
268
|
+
resourcerer_config :cool_build, build: ->{ very_cool_build_code }
|
269
|
+
|
270
|
+
resource :band, using: [:cool_find, :cool_build]
|
271
|
+
resource :another_band, using: :cool_build
|
272
|
+
```
|
273
|
+
|
274
|
+
Options that are passed to `resource` will take precedence over the presets.
|
275
|
+
|
276
|
+
|
277
|
+
## Decorators or Presenters (like [draper](http://github.com/drapergem/draper))
|
278
|
+
|
279
|
+
If you use decorators, you'll be able to avoid [even more boilerplate](https://gist.github.com/ElMassimo/6775148c0d7364be111531a254b41ba9) if you throw [presenter_rails](https://github.com/ElMassimo/presenter_rails) in the mix:
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
class BandController < ApplicationController
|
283
|
+
resource(:band, permit: :name)
|
284
|
+
present(:band) { band.decorate }
|
251
285
|
|
252
286
|
def create
|
253
|
-
if
|
254
|
-
redirect_to(
|
287
|
+
if band.save
|
288
|
+
redirect_to(band)
|
255
289
|
else
|
256
290
|
render :new
|
257
291
|
end
|
258
292
|
end
|
259
293
|
|
260
294
|
def update
|
261
|
-
if
|
262
|
-
redirect_to(
|
295
|
+
if band.save
|
296
|
+
redirect_to(band)
|
263
297
|
else
|
264
298
|
render :edit
|
265
299
|
end
|
266
300
|
end
|
267
|
-
|
268
|
-
private
|
269
|
-
def person_params
|
270
|
-
params.require(:person).permit(:name)
|
271
|
-
end
|
272
301
|
end
|
273
302
|
```
|
274
303
|
|
275
|
-
### Comparison with [
|
304
|
+
### Comparison with [Decent Exposure](https://github.com/hashrocket/decent_exposure).
|
276
305
|
|
277
|
-
Resourcerer is heavily inspired on [
|
306
|
+
Resourcerer is heavily inspired on [Decent Exposure](https://github.com/hashrocket/decent_exposure), but it attempts to be simpler and more flexible by not focusing on exposing variables to the view context.
|
278
307
|
|
279
308
|
#### Similarities
|
280
|
-
Both allow you to find or initialize a
|
309
|
+
Both allow you to find or initialize a model and assign attributes, removing the boilerplate from most CRUD actions.
|
281
310
|
|
282
311
|
#### Differences
|
283
|
-
Resourcerer does not expose an object to the view in any way,
|
284
|
-
|
285
|
-
|
286
|
-
### Caveats
|
287
|
-
#### When using StrongParametersStrategy
|
288
|
-
Since attributes are assigned on every POST, PUT, and PATCH request, sometimes when using Strong Parameters it's not desirable that the attributes method is called. For that reason, the presence of `params[param_key]` is checked before assigning attributes.
|
289
|
-
##### Troubleshooting
|
290
|
-
- _The attributes are not being assigned_: Check that the resource name matches the param used in the attributes method, and set the `param_key` configuration if they are different.
|
291
|
-
- _Need an error to be thrown if the params are not present_: Use the `EagerStrongParametersStrategy`, available in the sample strategies in this repository, you can set it using the `strategy` configuration option.
|
312
|
+
Resourcerer does not expose an object to the view in any way, nor deal with decoratation. It also provides better support for strong parameters.
|
292
313
|
|
293
314
|
### Special Thanks
|
294
|
-
Resourcerer
|
315
|
+
Resourcerer is based on [DecentExposure](https://github.com/hashrocket/decent_exposure).
|