simple_resource_controller 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +409 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/simple_resource_controller/configurator.rb +7 -0
- data/lib/simple_resource_controller/controller.rb +303 -0
- data/lib/simple_resource_controller/railtie.rb +19 -0
- data/lib/simple_resource_controller/version.rb +3 -0
- data/lib/simple_resource_controller.rb +7 -0
- data/simple_resource_controller.gemspec +29 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cb28b4c9ce2a17fae1264f71a4091cd4913ed6fc2aeb727b395c27764ffe0f60
|
4
|
+
data.tar.gz: 16aaee6c115b1a59ba7d083290db63693103da35099cf24f17296a4ec133cf4c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7a845c5c753d2b4a7c14dd38c4b216a72d23a5d8b85bd4a2f7db591e4f9bed28d1b5121a59c61bb93bc17e8388235013d8c77f0045c76e8051f4d064cae328b4
|
7
|
+
data.tar.gz: 7c8b9ad5385472b79a5cc96fb09e81858e96a9201382257130e62b388b6812260ea93aee060578b10808e9e69b5432d8ee481a9cc4762ddafd11c95dca91e32d
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at olegz@jetruby.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
group :development, :test do
|
4
|
+
gem 'sqlite3'
|
5
|
+
gem 'rails'
|
6
|
+
gem 'byebug'
|
7
|
+
gem 'pry'
|
8
|
+
gem 'responders', '~> 2.0'
|
9
|
+
gem 'kaminari'
|
10
|
+
gem 'has_scope'
|
11
|
+
end
|
12
|
+
|
13
|
+
group :test do
|
14
|
+
gem 'rspec-rails'
|
15
|
+
gem 'database_cleaner'
|
16
|
+
gem 'simplecov'
|
17
|
+
gem 'mutant-rspec'
|
18
|
+
end
|
19
|
+
|
20
|
+
# Specify your gem's dependencies in simple_resource_controller.gemspec
|
21
|
+
gemspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Oleg Zaporozhchenko
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,409 @@
|
|
1
|
+
# SimpleResourceController
|
2
|
+
|
3
|
+
The simple_resource_controller gem is a lightweight analog of the good old inherited_resources. The main purpose is to save developers time for writing simple CRUD controllers.
|
4
|
+
|
5
|
+
**Important! Please remember, that this tool was created to help you and not vice versa. Do not try to fight with it. At the end, it’s just a stupid code of the stupid crud controllers.**
|
6
|
+
|
7
|
+
The main ideas
|
8
|
+
1. This gem allows you to create CRUD controller with minimum configuration
|
9
|
+
2. You do not need to cover these controllers with any tests because gem already covered well
|
10
|
+
2. It just a simple tool with a set of important configurations. It does not want to be a God tool and cover all your use cases
|
11
|
+
3. After reading this documentation you will get a clear understanding how it works under the hood.
|
12
|
+
4. The best practices guide
|
13
|
+
|
14
|
+
The last point is the most important. When a tool makes so many “magic” it should provide a best practices guide.
|
15
|
+
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'simple_resource_controller'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install simple_resource_controller
|
32
|
+
|
33
|
+
Require it in your `config/application.rb`
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require 'simple_resource_controller'
|
37
|
+
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
A short example
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class AnotherArticlesController < ApplicationController
|
45
|
+
resource_actions :crud
|
46
|
+
resource_class 'Article'
|
47
|
+
paginate_collection 10
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def after_save_redirect_path
|
52
|
+
admin_articles_path
|
53
|
+
end
|
54
|
+
alias :after_destroy_redirect_path :after_save_redirect_path
|
55
|
+
|
56
|
+
def after_save_messages
|
57
|
+
{ notice: 'Saved!' }
|
58
|
+
end
|
59
|
+
|
60
|
+
def permitted_params
|
61
|
+
params.require(:article).permit(:title)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
### Controller configuration
|
67
|
+
|
68
|
+
The first required configuration is an action name.
|
69
|
+
|
70
|
+
**Please note! The actions definition are inherited, so be careful with the base classes.**
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
resource_actions :index, :show, :new, :create
|
74
|
+
resource_actions :crud # an alias for all actions
|
75
|
+
```
|
76
|
+
|
77
|
+
Other settings
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
resource_class ‘User’
|
81
|
+
```
|
82
|
+
|
83
|
+
Allow specifying the model class. Will get from controller name by default.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
paginate_collection 10
|
87
|
+
```
|
88
|
+
|
89
|
+
Will use the kaminari gem for pagination. It will add `page(params[:page]).per(params[:per_page] || 10)` to your scopes.
|
90
|
+
|
91
|
+
Of course, I could make this config more flexible, but it contradicts the gem philosophy. Keep it as simple as possible. Creating controllers with that tool should be simple and new developers should not spend much time trying to understand what all these configs mean.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
resource_context :current_user
|
95
|
+
```
|
96
|
+
|
97
|
+
The last and more complex setting is the `resource_context`. Please avoid it if it isn’t clear to you how it works inside.
|
98
|
+
|
99
|
+
But before describing it I would like to talk about some issues of the inherited_resources gem. The Inherited resources was one of my favorite gems for a long time. But when my team grows I got that for Rails newcomers is so hard to dive in code written with it. Hard to understand what all these `begin_of_association_chain`, `end_of_association_chain` and the other methods do. How to combine them together and use it properly. At the end it’s just a stupid CRUD controller, developers should not spend much time to figure out how it works. But this is the issue both gems are trying to solve - do not spend any time on simple things.
|
100
|
+
|
101
|
+
So, let's back to the `resource_context` option. It the most “magic” config, but it has only two possible usages.
|
102
|
+
|
103
|
+
1. There is a single parameter. In that case, the method with parameter name be the base and gem will build the relation from the controller name
|
104
|
+
2. There are several parameters. In this case, it also uses the method with parameter name as a base. But there is no any additional magic here. All next params will specify the chain (In the first case it was defined by controller name)
|
105
|
+
|
106
|
+
It’s hard to get it from the description, but the examples below are pretty clear. Please note, that the commented part is the code generated by the gem.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class ArticlesController < ApplicationController
|
110
|
+
resource_actions :index
|
111
|
+
|
112
|
+
#def index
|
113
|
+
#@articles = Article.all
|
114
|
+
#end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
```ruby
|
118
|
+
class ArticlesController < ApplicationController
|
119
|
+
resource_actions :index
|
120
|
+
resource_context :current_user
|
121
|
+
|
122
|
+
# current_user - from params
|
123
|
+
# .articles - from controller name
|
124
|
+
#def index
|
125
|
+
#@articles = current_user.articles
|
126
|
+
#end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
```ruby
|
130
|
+
class ArticlesController < ApplicationController
|
131
|
+
resource_actions :index
|
132
|
+
resource_context :current_user, :articles, :recent
|
133
|
+
|
134
|
+
# no magic with controller name, explicit chain
|
135
|
+
#def index
|
136
|
+
#@articles = current_user.articles.recent
|
137
|
+
#end
|
138
|
+
end
|
139
|
+
```
|
140
|
+
```ruby
|
141
|
+
class ArticlesController < ApplicationController
|
142
|
+
resource_actions :index
|
143
|
+
resource_context :category, :articles
|
144
|
+
|
145
|
+
#def index
|
146
|
+
#@articles = category.articles
|
147
|
+
#end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def category
|
152
|
+
current_user.categories.find(params[:category_id])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
If the method doesn’t exist it could try to generate it by supposing that this is a case of the nested resources.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class ArticlesController < ApplicationController
|
161
|
+
resource_actions :index
|
162
|
+
resource_context :category, :articles
|
163
|
+
|
164
|
+
#def index
|
165
|
+
#@articles = Category.find(params[:category_id]).articles
|
166
|
+
#end
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
The last example details
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
class ArticlesController < ApplicationController
|
174
|
+
resource_actions :index
|
175
|
+
resource_context :category, :articles
|
176
|
+
#def index
|
177
|
+
#@articles = Category.find(params[:category_id]).articles
|
178
|
+
#end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
Because the `ArticlesController` doesn’t define a `def category; end` method gem will try to build the class name and find the record by `params[:category_id]`
|
183
|
+
`
|
184
|
+
|
185
|
+
The has_scope gem also will automatically work if any scopes specified.
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
class ArticlesController < ApplicationController
|
189
|
+
resource_actions :index
|
190
|
+
resource_context :category, :articles
|
191
|
+
paginage_collection 10
|
192
|
+
has_scope :recent, type: :boolean
|
193
|
+
#def index
|
194
|
+
#@articles = apply_scopes(Category.find(params[:category_id]).articles).page(params[:page]).per(params[:per_page] || per_page)
|
195
|
+
#end
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
That's all. There are no other available settings. Really simple, isn’t it?
|
200
|
+
|
201
|
+
### The template methods
|
202
|
+
|
203
|
+
Gem was developed with the Template pattern, so there are a few methods you can override your needs.
|
204
|
+
|
205
|
+
#### The required overrides.
|
206
|
+
|
207
|
+
You need to define next methods only if you use related actions
|
208
|
+
```ruby
|
209
|
+
def after_save_redirect_path
|
210
|
+
raise 'Not Implemented'
|
211
|
+
end
|
212
|
+
|
213
|
+
def after_destroy_redirect_path
|
214
|
+
raise 'Not Implemented'
|
215
|
+
end
|
216
|
+
|
217
|
+
def permitted_params
|
218
|
+
raise 'Not Implemented'
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
#### Flash messages
|
223
|
+
|
224
|
+
By default there are no any messages
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
def after_create_messages
|
228
|
+
after_save_messages
|
229
|
+
end
|
230
|
+
|
231
|
+
def after_update_messages
|
232
|
+
after_save_messages
|
233
|
+
end
|
234
|
+
|
235
|
+
def after_destroy_messages
|
236
|
+
nil
|
237
|
+
end
|
238
|
+
|
239
|
+
def after_save_messages
|
240
|
+
nil
|
241
|
+
end
|
242
|
+
```
|
243
|
+
|
244
|
+
You can define any of these methods, They should return a Hash with types and messages
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
def after_destroy_messages
|
248
|
+
{ success: 'Record was successfully deleted' }
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
#### Fetch data methods
|
253
|
+
|
254
|
+
If the `resource_context` is not enough or you find it too complex, just redefine the basic methods `collection` and `resource`. Both already defined as helper methods and could be used inside views. Don’t forget about the memoization.
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
def resource
|
258
|
+
@article ||= Article.find(params[:id])
|
259
|
+
end
|
260
|
+
|
261
|
+
def collection
|
262
|
+
@articles ||= Article.all
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
#### Controller actions
|
267
|
+
|
268
|
+
Gem based on the responders one, so you can easy redefine the basic methods. It has the same interface as the inherited_resources.
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
class ArticlesController < ApplicationController
|
272
|
+
resource_actions :update
|
273
|
+
|
274
|
+
def update
|
275
|
+
@article = Article.find(params[:id])
|
276
|
+
@article.category = Category.find(params[:category_id])
|
277
|
+
|
278
|
+
update!
|
279
|
+
end
|
280
|
+
end
|
281
|
+
```
|
282
|
+
```ruby
|
283
|
+
class ArticlesController < ApplicationController
|
284
|
+
resource_actions :update
|
285
|
+
|
286
|
+
def update
|
287
|
+
update! do |format|
|
288
|
+
unless resource.errors.empty? # failure
|
289
|
+
format.html { redirect_to project_url(resource) }
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
```
|
295
|
+
```ruby
|
296
|
+
class ArticlesController < ApplicationController
|
297
|
+
resource_actions :update
|
298
|
+
|
299
|
+
def update
|
300
|
+
update! do |success, failure|
|
301
|
+
failure.html { redirect_to project_url(resourcet) }
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
#### A bit more about flashes and redirects
|
308
|
+
|
309
|
+
Instead of
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
class ArticlesController < ApplicationController
|
313
|
+
resource_actions :create
|
314
|
+
private
|
315
|
+
def after_create_messages
|
316
|
+
{notice: ‘Some message’}
|
317
|
+
end
|
318
|
+
end
|
319
|
+
```
|
320
|
+
|
321
|
+
You can write
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
class ArticlesController < ApplicationController
|
325
|
+
|
326
|
+
resource_actions :create
|
327
|
+
|
328
|
+
def create
|
329
|
+
create! notice: ‘Some message’
|
330
|
+
end
|
331
|
+
end
|
332
|
+
```
|
333
|
+
|
334
|
+
The second example looks more elegant, but it’s much easy to write tests for the first one. Because for the first example you just need to ensure that the `after_create_messages` return a correct hash. But for the second one, you will need to write a test for the `create` method from scratch because you redefine it and can’t rely on the gem tests.
|
335
|
+
|
336
|
+
### Possible issues
|
337
|
+
|
338
|
+
I’ve used a contract development to raise exceptions if something configured wrong. Please check the exceptions below you can get.
|
339
|
+
|
340
|
+
#### `Not Implemented`
|
341
|
+
|
342
|
+
You didn’t define the template methods. Please check the documentation above.
|
343
|
+
|
344
|
+
#### `Messages should be specified with Hash`
|
345
|
+
|
346
|
+
Flash messages should be configured as a hash
|
347
|
+
|
348
|
+
#### `Please install Kaminari`
|
349
|
+
|
350
|
+
You are trying to use pagination but gem kaminary is missing
|
351
|
+
|
352
|
+
#### `#{context} hasn't relation #{scope}`
|
353
|
+
|
354
|
+
Wrong usage of the `resource_context`. Please remember that there is only single parameter it will build the second one from the controller name
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
class ArticlesController < ApplicationController
|
358
|
+
resource_actions :index
|
359
|
+
resource_context :current_user
|
360
|
+
|
361
|
+
#def index
|
362
|
+
#@articles = current_user.articles
|
363
|
+
#end
|
364
|
+
end
|
365
|
+
```
|
366
|
+
|
367
|
+
So, the User model should have `has_many : articles` relation
|
368
|
+
|
369
|
+
#### `Undefined context method #{context_method}`
|
370
|
+
|
371
|
+
Wrong usage of the `resource_context`. If there is no method with the same name or relevant param - it will cause that exception. Please check the documentation about `resource_context`
|
372
|
+
|
373
|
+
#### `Could not find model #{class_name} by param #{context_method}_id`
|
374
|
+
|
375
|
+
The similar exception to the previous one, but in that case, there is a relevant parameter, but there is no a Rails model with relevant name.
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
class ArticlesController < ApplicationController
|
379
|
+
resource_actions :index
|
380
|
+
resource_context :category, :articles
|
381
|
+
|
382
|
+
#def index
|
383
|
+
#@articles = Category.find(params[:category_id]).articles
|
384
|
+
#end
|
385
|
+
end
|
386
|
+
```
|
387
|
+
|
388
|
+
You will get this exception if the Category model is missing.
|
389
|
+
|
390
|
+
|
391
|
+
#### ```Too many Rails magic. Please check your code```
|
392
|
+
|
393
|
+
This is my favorite one. The `responders` gem follows the next logic - if a record is valid it means that it was saved to the database. But there is a case when too many Rails magic can cause valid records which weren’t saved to the DB. Yeah, sometimes Rails hurts =)
|
394
|
+
If you will get this exception - check your ActiveRecord callbacks, your code has some smells.
|
395
|
+
|
396
|
+
|
397
|
+
## Contributing
|
398
|
+
|
399
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/c3gdlk/simple_resource_controller. This project is intended to be a safe, welcoming space for collaboration.
|
400
|
+
|
401
|
+
Not work for now =((
|
402
|
+
```
|
403
|
+
bundle exec mutant --include lib --require simple_resource_controller --use rspec "SimpleResourceController*"
|
404
|
+
```
|
405
|
+
|
406
|
+
|
407
|
+
## License
|
408
|
+
|
409
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "simple_resource_controller"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
module SimpleResourceController
|
2
|
+
module Controller
|
3
|
+
module Index
|
4
|
+
def index(options={}, &block)
|
5
|
+
respond_with collection, options, &block
|
6
|
+
end
|
7
|
+
alias :index! :index
|
8
|
+
end
|
9
|
+
|
10
|
+
module Show
|
11
|
+
def show(options={}, &block)
|
12
|
+
respond_with resource, options, &block
|
13
|
+
end
|
14
|
+
alias :show! :show
|
15
|
+
end
|
16
|
+
|
17
|
+
module New
|
18
|
+
def new(options={}, &block)
|
19
|
+
build_resource
|
20
|
+
respond_with resource, options, &block
|
21
|
+
end
|
22
|
+
alias :new! :new
|
23
|
+
end
|
24
|
+
|
25
|
+
module Create
|
26
|
+
def create(options={}, &block)
|
27
|
+
build_resource
|
28
|
+
success = save_resource_and_respond!(options, &block)
|
29
|
+
ensure
|
30
|
+
setup_flash_messages(after_create_messages) if success
|
31
|
+
end
|
32
|
+
alias :create! :create
|
33
|
+
end
|
34
|
+
|
35
|
+
module Edit
|
36
|
+
def edit(options={}, &block)
|
37
|
+
respond_with resource, options, &block
|
38
|
+
end
|
39
|
+
alias :edit! :edit
|
40
|
+
end
|
41
|
+
|
42
|
+
module Update
|
43
|
+
def update(options={}, &block)
|
44
|
+
success = save_resource_and_respond!(options, &block)
|
45
|
+
ensure
|
46
|
+
setup_flash_messages(after_update_messages) if success
|
47
|
+
end
|
48
|
+
alias :update! :update
|
49
|
+
end
|
50
|
+
|
51
|
+
module Destroy
|
52
|
+
def destroy(options={}, &block)
|
53
|
+
destroy_resource_and_respond!(options, &block)
|
54
|
+
ensure
|
55
|
+
setup_flash_messages(after_destroy_messages)
|
56
|
+
end
|
57
|
+
alias :destroy! :destroy
|
58
|
+
end
|
59
|
+
|
60
|
+
module CommonMethods
|
61
|
+
private
|
62
|
+
|
63
|
+
def collection
|
64
|
+
return instance_variable_get(:"@#{collection_name}") if instance_variable_get(:"@#{collection_name}").present?
|
65
|
+
instance_variable_set(:"@#{collection_name}", apply_scopes_and_pagination(association_chain))
|
66
|
+
end
|
67
|
+
|
68
|
+
def apply_scopes_and_pagination(chain)
|
69
|
+
if respond_to?(:apply_scopes, true)
|
70
|
+
chain = apply_scopes(chain)
|
71
|
+
end
|
72
|
+
|
73
|
+
per_page = self.class.paginate_collection_config
|
74
|
+
|
75
|
+
if per_page.present?
|
76
|
+
raise 'Please install Kaminari' unless defined?(Kaminari)
|
77
|
+
|
78
|
+
chain = chain.page(params[:page]).per(params[:per_page] || per_page)
|
79
|
+
else
|
80
|
+
chain = chain.all
|
81
|
+
end
|
82
|
+
|
83
|
+
chain
|
84
|
+
end
|
85
|
+
|
86
|
+
def collection_name
|
87
|
+
resource_name.pluralize
|
88
|
+
end
|
89
|
+
|
90
|
+
def resource_class_name
|
91
|
+
self.class.resource_class_config || self.class.name.split('::').last.gsub('Controller', '').singularize
|
92
|
+
end
|
93
|
+
|
94
|
+
def resource_class
|
95
|
+
resource_class_name.constantize
|
96
|
+
end
|
97
|
+
|
98
|
+
def resource_name
|
99
|
+
resource_class_name.underscore
|
100
|
+
end
|
101
|
+
|
102
|
+
def resource_relation_name
|
103
|
+
resource_class_name.underscore.pluralize
|
104
|
+
end
|
105
|
+
|
106
|
+
def association_chain
|
107
|
+
self.class.resource_context_config.present? ? association_chain_by_context : resource_class
|
108
|
+
end
|
109
|
+
|
110
|
+
def association_chain_by_context
|
111
|
+
specified_scopes.reduce(default_context) do |context, scope|
|
112
|
+
raise "#{context.inspect} hasn't relation #{scope}" unless context.respond_to? scope
|
113
|
+
|
114
|
+
context.public_send(scope)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def default_context
|
119
|
+
context_method = self.class.resource_context_config.first
|
120
|
+
|
121
|
+
if self.respond_to? context_method, true
|
122
|
+
context = send(context_method)
|
123
|
+
elsif params["#{context_method}_id"].present?
|
124
|
+
class_name = context_method.to_s.classify
|
125
|
+
context_class = class_name.safe_constantize
|
126
|
+
|
127
|
+
if context_class.present?
|
128
|
+
context = context_class.find(params["#{context_method}_id"])
|
129
|
+
else
|
130
|
+
raise "Could not find model #{class_name} by param #{context_method}_id"
|
131
|
+
end
|
132
|
+
else
|
133
|
+
raise "Undefined context method #{context_method}"
|
134
|
+
end
|
135
|
+
|
136
|
+
context
|
137
|
+
end
|
138
|
+
|
139
|
+
def specified_scopes
|
140
|
+
if self.class.resource_context_config.size > 1
|
141
|
+
self.class.resource_context_config[1..-1]
|
142
|
+
else
|
143
|
+
[resource_relation_name.to_sym]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def build_resource
|
148
|
+
return instance_variable_get(:"@#{resource_name}") if instance_variable_get(:"@#{resource_name}").present?
|
149
|
+
|
150
|
+
new_instance = association_chain.is_a?(ActiveRecord::Relation) ? association_chain.build : association_chain.new
|
151
|
+
instance_variable_set(:"@#{resource_name}", new_instance)
|
152
|
+
end
|
153
|
+
|
154
|
+
def resource
|
155
|
+
return instance_variable_get(:"@#{resource_name}") if instance_variable_get(:"@#{resource_name}").present?
|
156
|
+
instance_variable_set(:"@#{resource_name}", association_chain.find(params[:id]))
|
157
|
+
end
|
158
|
+
|
159
|
+
def destroy_resource_and_respond!(options={}, &block)
|
160
|
+
resource.destroy
|
161
|
+
|
162
|
+
unless block_given? || options[:location].present?
|
163
|
+
options[:location] = after_destroy_redirect_path
|
164
|
+
end
|
165
|
+
|
166
|
+
respond_with resource, options, &block
|
167
|
+
end
|
168
|
+
|
169
|
+
def save_resource_and_respond!(options={}, &block)
|
170
|
+
result = resource.update(permitted_params)
|
171
|
+
|
172
|
+
# process not saved result because of failed callback or another ActiveRecord magic
|
173
|
+
if resource.valid? && !result.present?
|
174
|
+
resource.errors[:base] << resource_wasnt_saved_message
|
175
|
+
end
|
176
|
+
|
177
|
+
unless block_given? || options[:location].present?
|
178
|
+
options[:location] = after_save_redirect_path
|
179
|
+
end
|
180
|
+
|
181
|
+
respond_with resource, options, &block
|
182
|
+
|
183
|
+
result
|
184
|
+
end
|
185
|
+
|
186
|
+
def resource_wasnt_saved_message
|
187
|
+
raise 'Too many Rails magic. Please check your code'
|
188
|
+
end
|
189
|
+
|
190
|
+
def after_create_messages
|
191
|
+
after_save_messages
|
192
|
+
end
|
193
|
+
|
194
|
+
def after_update_messages
|
195
|
+
after_save_messages
|
196
|
+
end
|
197
|
+
|
198
|
+
def after_destroy_messages
|
199
|
+
nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def after_save_messages
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
|
206
|
+
def setup_flash_messages(messages)
|
207
|
+
return unless messages.present?
|
208
|
+
|
209
|
+
raise 'Messages should be specified with Hash' unless messages.is_a?(Hash)
|
210
|
+
|
211
|
+
messages.each do |key, message|
|
212
|
+
flash[key] = message
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# :nocov:
|
217
|
+
def after_save_redirect_path
|
218
|
+
raise 'Not Implemented'
|
219
|
+
end
|
220
|
+
# :nocov:
|
221
|
+
|
222
|
+
# :nocov:
|
223
|
+
def after_destroy_redirect_path
|
224
|
+
raise 'Not Implemented'
|
225
|
+
end
|
226
|
+
# :nocov:
|
227
|
+
|
228
|
+
# :nocov:
|
229
|
+
def permitted_params
|
230
|
+
raise 'Not Implemented'
|
231
|
+
end
|
232
|
+
# :nocov:
|
233
|
+
end
|
234
|
+
|
235
|
+
module Accessors
|
236
|
+
def resource_class_config
|
237
|
+
@resource_class_config
|
238
|
+
end
|
239
|
+
|
240
|
+
def resource_class(value)
|
241
|
+
@resource_class_config = value
|
242
|
+
end
|
243
|
+
|
244
|
+
def resource_context_config
|
245
|
+
@resource_context_config
|
246
|
+
end
|
247
|
+
|
248
|
+
def resource_context(*values)
|
249
|
+
@resource_context_config = values
|
250
|
+
end
|
251
|
+
|
252
|
+
def paginate_collection_config
|
253
|
+
@paginate_collection_config
|
254
|
+
end
|
255
|
+
|
256
|
+
def paginate_collection(value)
|
257
|
+
@paginate_collection_config = value
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class ActionsBuilder
|
262
|
+
DEPENDENCIES_MAP = {
|
263
|
+
index: Index,
|
264
|
+
show: Show,
|
265
|
+
new: New,
|
266
|
+
create: Create,
|
267
|
+
edit: Edit,
|
268
|
+
update: Update,
|
269
|
+
destroy: Destroy
|
270
|
+
}.freeze
|
271
|
+
|
272
|
+
HELPER_METHODS = [:resource, :collection].freeze
|
273
|
+
|
274
|
+
ALL_ACTIONS_ALIAS = :crud
|
275
|
+
|
276
|
+
def self.build(controller_class, actions)
|
277
|
+
unless actions.include?(ALL_ACTIONS_ALIAS)
|
278
|
+
raise 'Unknown action name' unless (actions - DEPENDENCIES_MAP.keys).size.zero?
|
279
|
+
end
|
280
|
+
|
281
|
+
controller_class.extend Accessors
|
282
|
+
|
283
|
+
loaded_modules = [CommonMethods]
|
284
|
+
|
285
|
+
if actions.include?(ALL_ACTIONS_ALIAS)
|
286
|
+
loaded_modules += DEPENDENCIES_MAP.values
|
287
|
+
else
|
288
|
+
loaded_modules += actions.map { |action_name| DEPENDENCIES_MAP[action_name] }
|
289
|
+
end
|
290
|
+
|
291
|
+
loaded_modules.uniq.each do |loaded_module|
|
292
|
+
controller_class.include loaded_module
|
293
|
+
end
|
294
|
+
|
295
|
+
HELPER_METHODS.each do |method_name|
|
296
|
+
controller_class.helper_method method_name
|
297
|
+
end
|
298
|
+
|
299
|
+
controller_class.respond_to :html
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SimpleResourceController
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer 'insert SimpleResourceController to ActionController' do
|
6
|
+
ActiveSupport.on_load :action_controller do
|
7
|
+
SimpleResourceController::Railtie.insert
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Railtie
|
13
|
+
def self.insert
|
14
|
+
if defined?(::ActionController)
|
15
|
+
::ActionController::Base.extend(Configurator)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'simple_resource_controller/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "simple_resource_controller"
|
8
|
+
spec.version = SimpleResourceController::VERSION
|
9
|
+
spec.authors = ["Oleg Zaporozhchenko"]
|
10
|
+
spec.email = ["c3.gdlk@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Simple gem for resource controllers"
|
13
|
+
spec.description = "This gem allows to write explicit resource controllers"
|
14
|
+
spec.homepage = "https://github.com/c3gdlk/simple_resource_controller"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|coverage)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
|
26
|
+
spec.add_dependency "railties", ">= 3.2"
|
27
|
+
spec.add_dependency "actionpack", ">= 3.2"
|
28
|
+
spec.add_dependency "responders", "~> 2.0"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_resource_controller
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Oleg Zaporozhchenko
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-06-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: railties
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: actionpack
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: responders
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.0'
|
97
|
+
description: This gem allows to write explicit resource controllers
|
98
|
+
email:
|
99
|
+
- c3.gdlk@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".travis.yml"
|
107
|
+
- CODE_OF_CONDUCT.md
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- lib/simple_resource_controller.rb
|
115
|
+
- lib/simple_resource_controller/configurator.rb
|
116
|
+
- lib/simple_resource_controller/controller.rb
|
117
|
+
- lib/simple_resource_controller/railtie.rb
|
118
|
+
- lib/simple_resource_controller/version.rb
|
119
|
+
- simple_resource_controller.gemspec
|
120
|
+
homepage: https://github.com/c3gdlk/simple_resource_controller
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubygems_version: 3.0.4
|
140
|
+
signing_key:
|
141
|
+
specification_version: 4
|
142
|
+
summary: Simple gem for resource controllers
|
143
|
+
test_files: []
|