sweet_actions 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/PITCHME.md +224 -0
  8. data/PITCHME.yaml +1 -0
  9. data/README.md +257 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/lib/.DS_Store +0 -0
  14. data/lib/generators/.DS_Store +0 -0
  15. data/lib/generators/rails/.DS_Store +0 -0
  16. data/lib/generators/rails/actions_generator.rb +21 -0
  17. data/lib/generators/rails/resource_override.rb +10 -0
  18. data/lib/generators/rails/templates/.DS_Store +0 -0
  19. data/lib/generators/rails/templates/collect.rb.erb +11 -0
  20. data/lib/generators/rails/templates/create.rb.erb +15 -0
  21. data/lib/generators/rails/templates/destroy.rb.erb +15 -0
  22. data/lib/generators/rails/templates/show.rb.erb +11 -0
  23. data/lib/generators/rails/templates/update.rb.erb +11 -0
  24. data/lib/generators/sweet_actions/install_generator.rb +18 -0
  25. data/lib/generators/sweet_actions/templates/collect_action.rb +10 -0
  26. data/lib/generators/sweet_actions/templates/create_action.rb +14 -0
  27. data/lib/generators/sweet_actions/templates/destroy_action.rb +14 -0
  28. data/lib/generators/sweet_actions/templates/initializer.rb +9 -0
  29. data/lib/generators/sweet_actions/templates/show_action.rb +10 -0
  30. data/lib/generators/sweet_actions/templates/update_action.rb +10 -0
  31. data/lib/sweet_actions.rb +42 -0
  32. data/lib/sweet_actions/.DS_Store +0 -0
  33. data/lib/sweet_actions/action_factory.rb +64 -0
  34. data/lib/sweet_actions/api_action.rb +63 -0
  35. data/lib/sweet_actions/authorization_concerns.rb +23 -0
  36. data/lib/sweet_actions/collect_action.rb +11 -0
  37. data/lib/sweet_actions/configuration.rb +9 -0
  38. data/lib/sweet_actions/controller_concerns.rb +10 -0
  39. data/lib/sweet_actions/create_action.rb +6 -0
  40. data/lib/sweet_actions/destroy_action.rb +19 -0
  41. data/lib/sweet_actions/exceptions.rb +5 -0
  42. data/lib/sweet_actions/railtie.rb +8 -0
  43. data/lib/sweet_actions/read_concerns.rb +14 -0
  44. data/lib/sweet_actions/rest_concerns.rb +47 -0
  45. data/lib/sweet_actions/rest_serializer_concerns.rb +54 -0
  46. data/lib/sweet_actions/routes_helpers.rb +40 -0
  47. data/lib/sweet_actions/save_concerns.rb +51 -0
  48. data/lib/sweet_actions/show_action.rb +9 -0
  49. data/lib/sweet_actions/update_action.rb +6 -0
  50. data/lib/sweet_actions/version.rb +3 -0
  51. data/sweet_actions-0.1.0.gem +0 -0
  52. data/sweet_actions.gemspec +36 -0
  53. metadata +139 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3904d237f789cd3db0a4dc78e4f9adecc8bf3b9a
4
+ data.tar.gz: 4231ace1119c75b32e0c57dd8f7ac33e1e02051a
5
+ SHA512:
6
+ metadata.gz: abc3087589ceed04b51f4e60b7fcb2485c4f96049bc7c029d3b9feaca16f1449d7518c328631f9c4a3dc8420d78c84f8701d48085e4361ccbfaa991450ff54af
7
+ data.tar.gz: 57c3d381618502950e5e1301ede3de0d048890cb86a18280924632f661d9dd504c5ae50e7381918363cb76bb170747f79267e21f0c7f43ab202c796f46d361aa
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sweet_actions.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Ryan Francis
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.
@@ -0,0 +1,224 @@
1
+ ---
2
+
3
+ # Sweet Actions
4
+ #### a Ruby on Rails Gem
5
+
6
+ ---
7
+
8
+ ## Let's REST not STRESS
9
+
10
+ Implementing a REST JSON API should be:
11
+
12
+ - easy
13
+ - consistent
14
+ - secure
15
+ - extendable
16
+
17
+ ---
18
+
19
+ ## Job to be Done
20
+
21
+ What is the Job that REST is solving?
22
+
23
+ - Users need to interact with our database
24
+ - They can do so by either creating a new record or reading, updating, or destroying an existing record (CRUD)
25
+
26
+ ---
27
+
28
+ For example, given an **Events** resource:
29
+
30
+ - See an event
31
+ - See a list of events
32
+ - Create an event
33
+ - Update an event
34
+ - Delete an event
35
+
36
+ ---
37
+
38
+ ## The Big 5
39
+
40
+ - show
41
+ - index
42
+ - create
43
+ - update
44
+ - destroy
45
+
46
+ ---
47
+
48
+ ## Good News
49
+
50
+ Consistency = easy to program
51
+
52
+ ...or at least it should be
53
+
54
+ ---
55
+
56
+ ## Current Process
57
+
58
+ We are focused on the **resource** instead of the **action**
59
+
60
+ ```ruby
61
+ class EventsController
62
+ def index; end
63
+ def show; end
64
+ def create; end
65
+ def update; end
66
+ def destroy; end
67
+ end
68
+ ```
69
+
70
+ **resource = class, actions = methods**
71
+
72
+ ---
73
+
74
+ ## But wait...
75
+
76
+ Which do you think has more in common?
77
+
78
+ 1. events#create <=> events#index
79
+ 2. events#create <=> articles#create
80
+
81
+ ---
82
+
83
+ ```ruby
84
+ class EventsController
85
+ def create
86
+ event = Event.new(event_params)
87
+ raise NotAuthorized unless can?(:create, event)
88
+
89
+ if event.save
90
+ # success
91
+ else
92
+ # failure
93
+ end
94
+ end
95
+
96
+ def index
97
+ events = Event.where(date: >= Date.today)
98
+ serialize(events)
99
+ end
100
+ end
101
+ ```
102
+
103
+ ---
104
+
105
+ ```ruby
106
+ class EventsController
107
+ def create
108
+ event = Event.new(event_params)
109
+ raise NotAuthorized unless can?(:create, event)
110
+
111
+ if event.save
112
+ # success
113
+ else
114
+ # failure
115
+ end
116
+ end
117
+ end
118
+
119
+ class ArticlesController
120
+ def create
121
+ article = Article.new(article_params)
122
+ raise NotAuthorized unless can?(:create, article)
123
+
124
+ if article.save
125
+ # success
126
+ else
127
+ # failure
128
+ end
129
+ end
130
+ end
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Answer: #2
136
+
137
+ ```ruby
138
+ 'events#create' == 'articles#create'
139
+ 'events#create' != 'events#index'
140
+ ```
141
+
142
+ ---
143
+
144
+ ### Actions as First Class Citizens
145
+
146
+ Since the actions have more in common with each other than they do with the resources to which they belong, it is the actions that should be objects instead of the resource.
147
+
148
+ ---
149
+
150
+ ```ruby
151
+ class CreateAction < SweetActions::CreateAction
152
+ def action
153
+ resource = set_resource
154
+ resource.save ? success : failure
155
+ end
156
+
157
+ private
158
+
159
+ def set_resource
160
+ resource_class.new(resource_params)
161
+ end
162
+
163
+ def resource_params
164
+ decanter.new(params[resource_name])
165
+ end
166
+ end
167
+ ```
168
+
169
+ ---
170
+
171
+ ```ruby
172
+ module Events
173
+ class Create < CreateAction
174
+ def after_save
175
+ UserMailer.send_event_confirmation.deliver_later
176
+ end
177
+ end
178
+ end
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Demo
184
+
185
+ 1. Install gem
186
+ 2. Generate resource (model, decanter, serializer, actions)
187
+ 3. Add routes
188
+
189
+ ---
190
+
191
+ ## 1. Install Gem
192
+
193
+ Gemfile:
194
+
195
+ ```ruby
196
+ gem 'sweet_actions'
197
+ ```
198
+
199
+ Terminal:
200
+
201
+ ```
202
+ bundle
203
+ ```
204
+
205
+ ---
206
+
207
+ ## 2. Generate Resource
208
+
209
+ ```
210
+ rails g model Event title:name start_date:date
211
+ rails g decanter Event title:name start_date:date
212
+ rails g serializer Event title:name start_date:date
213
+ rails g actions Events
214
+ ```
215
+
216
+ ---
217
+
218
+ ## 3. Add Routes
219
+
220
+ ```ruby
221
+ Rails.application.routes.draw do
222
+ create_sweet_actions(:events)
223
+ end
224
+ ```
@@ -0,0 +1 @@
1
+ # theme : black
@@ -0,0 +1,257 @@
1
+ # Sweet Actions
2
+
3
+ ## The Idea
4
+
5
+ In a RESTful context, controller actions tend to have more in common with the same actions belonging to other resources than other actions belonging to the same resource. For example, let's say we have two resources in our app: Events and Articles.
6
+
7
+ Which do you think has more in common in terms of programming logic?
8
+
9
+ 1. events#create <=> events#index
10
+ 2. events#create <=> articles#create
11
+
12
+ We would argue that #2 has more in common than #1. Both events#create and articles#create need to do the following:
13
+
14
+ 1. Authorize the transaction
15
+ 2. Validate the data
16
+ 3. Persist the new record
17
+ 4. Respond with new record (if successful) or error information (if unsuccessful) in the JSON
18
+
19
+ By organizing our actions as methods inside a resource based controller like below, we don't have the opportunity to take advantage of basic Object Oriented programming concepts like Inheritance and Modules.
20
+
21
+ ```ruby
22
+ class EventsController < ApplicationController
23
+ # more similar to articles#create than events#index
24
+ def create
25
+ event = Event.new(event_params)
26
+ raise NotAuthorized unless can?(:create, event)
27
+
28
+ if event.save
29
+ UserMailer.new_event_confirmation(event).deliver_later
30
+ serialize(event)
31
+ else
32
+ serialize_errors(event)
33
+ end
34
+ end
35
+
36
+ # more similar to articles#index than events#create
37
+ def index
38
+ events = Event.where(date: >= Date.today)
39
+ serialize(events)
40
+ end
41
+ end
42
+
43
+ class ArticlesController < ApplicationController
44
+ # more similar to events#create than articles#index
45
+ def create
46
+ article = Article.new(article_params)
47
+ raise NotAuthorized unless can?(:create, article)
48
+
49
+ if article.save
50
+ serialize(article)
51
+ else
52
+ serialize_errors(article)
53
+ end
54
+ end
55
+
56
+ # more similar to events#index than articles#create
57
+ def index
58
+ articles = Article.where(published: true)
59
+ serialize(articles)
60
+ end
61
+ end
62
+ ```
63
+
64
+ Instead, we propose a strategy that looks more like the following:
65
+
66
+ ```ruby
67
+ # generic logic for create (sweet_actions gem)
68
+ module SweetActions
69
+ class CreateAction < ApiAction
70
+ def action
71
+ @resource = set_resource
72
+ authorize
73
+ validate_and_save ? success : failure
74
+ end
75
+
76
+ # ...
77
+ end
78
+ end
79
+
80
+ # app logic for create (app/actions/create_action.rb)
81
+ class CreateAction < SweetActions::CreateAction
82
+ def set_resource
83
+ resource_class.new(resource_params)
84
+ end
85
+
86
+ def authorized?
87
+ can?(:create, resource)
88
+ end
89
+ end
90
+
91
+ # resource logic for create (app/actions/events/create.rb)
92
+ module Events
93
+ class Create < CreateAction
94
+ def after_save
95
+ UserMailer.new_event_confirmation(resource).deliver_later
96
+ end
97
+ end
98
+ end
99
+ ```
100
+
101
+ With this structure, we essentially have three levels of abstraction:
102
+
103
+ - Generic create logic: SweetActions::CreateActions
104
+ - App create logic: CreateAction
105
+ - Resource create logic: Events::Create
106
+
107
+
108
+ As you can see, we can abstract most of the `create` logic to be shared across resources, which means you **only need to write the code that is unique about this create action vs. other create actions**.
109
+
110
+ ## Default REST Actions
111
+
112
+ For a given resource...
113
+ - Collect: list items
114
+ - Create: create new item
115
+ - Show: show item
116
+ - Update: update item
117
+ - Destroy: delete item
118
+
119
+ Many of these actions have shared behavior, which we abstract for you:
120
+ - All require serialization of the resource
121
+ - Create and Update need to be able to properly respond with error information when save does not succeed
122
+ - Create and Update rely on decanted params
123
+ - All require authorization (cancancan)
124
+
125
+ ## Automatic REST API
126
+
127
+ Given an Event model, one can do the following and get a basic RESTful API (assuming we have [decanter](https://github.com/launchpadlab/decanter) and [AMS](https://github.com/rails-api/active_model_serializers):
128
+
129
+ - rails g model Event name:string start_date:date
130
+ - rails g decanter Event name:string start_date:date
131
+ - rails g serializer Event name:string start_date:date
132
+ - add the new resource in routes
133
+
134
+ With that, you have the following at your disposal:
135
+
136
+ Collect: get '/events'
137
+ Create: post '/events'
138
+ Show: get '/events/:id'
139
+ Update: put '/events/:id'
140
+ Destroy: delete '/events/:id'
141
+
142
+ Each of these will respond with a consistent JSON format, including when saves don't succeed.
143
+
144
+ ## Overriding Default Actions
145
+
146
+ Should you choose to override the default behavior (defined in app/sweet_actions/defaults/, ), you need only create your own action like so:
147
+
148
+ app/sweet_actions/events/collect.rb
149
+
150
+ ```
151
+ module Events
152
+ class Collect < SweetActions::CollectAction
153
+ def set_resource
154
+ Event.all.limit(10)
155
+ end
156
+
157
+ def authorized?
158
+ can?(:read, resource)
159
+ end
160
+ end
161
+ end
162
+ ```
163
+
164
+ app/sweet_actions/events/create.rb
165
+
166
+ ```
167
+ module Events
168
+ class Create < CreateAction
169
+ def set_resource
170
+ Event.new(resource_params)
171
+ end
172
+
173
+ def authorized?
174
+ can?(:create, resource)
175
+ end
176
+
177
+ def save
178
+ resource.save
179
+ end
180
+
181
+ def after_save
182
+ SiteMailer.notify_user
183
+ end
184
+ end
185
+ end
186
+ ```
187
+
188
+ app/sweet_actions/events/show.rb
189
+
190
+ ```
191
+ module Events
192
+ class Show < ShowAction
193
+ def set_resource
194
+ Event.find(params[:id])
195
+ end
196
+
197
+ def authorized?
198
+ can?(:read, resource)
199
+ end
200
+ end
201
+ end
202
+ ```
203
+
204
+ app/sweet_actions/events/update.rb
205
+
206
+ ```
207
+ module Events
208
+ class Update < UpdateAction
209
+ def set_resource
210
+ Event.find(params[:id])
211
+ end
212
+
213
+ def authorized?
214
+ can?(:update, resource)
215
+ end
216
+
217
+ def save
218
+ resource.update(resource_params)
219
+ end
220
+ end
221
+ end
222
+ ```
223
+
224
+ app/sweet_actions/events/destroy.rb
225
+
226
+ ```
227
+ module Events
228
+ class Destroy < DestroyAction
229
+ def set_resource
230
+ Event.find(params[:id])
231
+ end
232
+
233
+ def authorized?
234
+ can?(:destroy, resource)
235
+ end
236
+
237
+ def destroy
238
+ resource.destroy
239
+ end
240
+ end
241
+ end
242
+ ```
243
+
244
+ ## Installation
245
+
246
+ Gemfile:
247
+
248
+ ```
249
+ gem 'sweet_actions'
250
+ ```
251
+
252
+ Terminal:
253
+
254
+ ```
255
+ bundle
256
+ bundle exec rails g sweet_actions:install
257
+ ```