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