cathode 0.0.1 → 0.1.0
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/README.md +346 -125
- data/Rakefile +1 -0
- data/app/controllers/cathode/base_controller.rb +28 -45
- data/app/models/cathode/token.rb +21 -0
- data/config/routes.rb +16 -0
- data/db/migrate/20140425164100_create_cathode_tokens.rb +11 -0
- data/lib/cathode.rb +0 -13
- data/lib/cathode/_version.rb +2 -1
- data/lib/cathode/action.rb +197 -39
- data/lib/cathode/action_dsl.rb +60 -0
- data/lib/cathode/base.rb +81 -12
- data/lib/cathode/create_request.rb +21 -0
- data/lib/cathode/custom_request.rb +5 -0
- data/lib/cathode/debug.rb +25 -0
- data/lib/cathode/destroy_request.rb +13 -0
- data/lib/cathode/engine.rb +9 -0
- data/lib/cathode/exceptions.rb +20 -0
- data/lib/cathode/index_request.rb +40 -0
- data/lib/cathode/object_collection.rb +49 -0
- data/lib/cathode/query.rb +24 -0
- data/lib/cathode/railtie.rb +21 -0
- data/lib/cathode/request.rb +139 -7
- data/lib/cathode/resource.rb +50 -19
- data/lib/cathode/resource_dsl.rb +46 -0
- data/lib/cathode/show_request.rb +13 -0
- data/lib/cathode/update_request.rb +26 -0
- data/lib/cathode/version.rb +112 -23
- data/lib/tasks/cathode_tasks.rake +5 -4
- data/spec/dummy/app/api/api.rb +0 -0
- data/spec/dummy/app/models/payment.rb +3 -0
- data/spec/dummy/app/models/product.rb +1 -0
- data/spec/dummy/app/models/sale.rb +5 -0
- data/spec/dummy/app/models/salesperson.rb +3 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20140409183635_create_sales.rb +11 -0
- data/spec/dummy/db/migrate/20140423172419_create_salespeople.rb +11 -0
- data/spec/dummy/db/migrate/20140424181343_create_payments.rb +10 -0
- data/spec/dummy/db/schema.rb +31 -1
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +1167 -0
- data/spec/dummy/log/test.log +180602 -0
- data/spec/dummy/spec/factories/payments.rb +8 -0
- data/spec/dummy/spec/factories/products.rb +1 -1
- data/spec/dummy/spec/factories/sales.rb +9 -0
- data/spec/dummy/spec/factories/salespeople.rb +7 -0
- data/spec/dummy/spec/requests/requests_spec.rb +434 -0
- data/spec/lib/cathode/action_spec.rb +136 -0
- data/spec/lib/cathode/base_spec.rb +34 -0
- data/spec/lib/cathode/create_request_spec.rb +40 -0
- data/spec/lib/cathode/custom_request_spec.rb +31 -0
- data/spec/lib/cathode/debug_spec.rb +25 -0
- data/spec/lib/cathode/destroy_request_spec.rb +28 -0
- data/spec/lib/cathode/index_request_spec.rb +62 -0
- data/spec/lib/cathode/object_collection_spec.rb +66 -0
- data/spec/lib/cathode/query_spec.rb +28 -0
- data/spec/lib/cathode/request_spec.rb +58 -0
- data/spec/lib/cathode/resource_spec.rb +482 -0
- data/spec/lib/cathode/show_request_spec.rb +23 -0
- data/spec/lib/cathode/update_request_spec.rb +41 -0
- data/spec/lib/cathode/version_spec.rb +416 -0
- data/spec/models/cathode/token_spec.rb +62 -0
- data/spec/spec_helper.rb +8 -2
- data/spec/support/factories/payments.rb +3 -0
- data/spec/support/factories/sale.rb +3 -0
- data/spec/support/factories/salespeople.rb +3 -0
- data/spec/support/factories/token.rb +3 -0
- data/spec/support/helpers.rb +13 -2
- metadata +192 -47
- data/app/helpers/cathode/application_helper.rb +0 -4
- data/spec/dummy/app/api/dummy_api.rb +0 -4
- data/spec/integration/api_spec.rb +0 -88
- data/spec/lib/action_spec.rb +0 -140
- data/spec/lib/base_spec.rb +0 -28
- data/spec/lib/request_spec.rb +0 -5
- data/spec/lib/resources_spec.rb +0 -78
- data/spec/lib/versioning_spec.rb +0 -104
data/Rakefile
CHANGED
@@ -1,47 +1,30 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
model.all
|
30
|
-
end
|
31
|
-
|
32
|
-
def resource
|
33
|
-
model.find params[:id]
|
34
|
-
end
|
35
|
-
|
36
|
-
def resource_params
|
37
|
-
params[controller_name.singularize]
|
38
|
-
end
|
39
|
-
|
40
|
-
def model
|
41
|
-
controller_name.classify.constantize
|
42
|
-
end
|
43
|
-
|
44
|
-
def process_access_filter
|
45
|
-
|
1
|
+
module Cathode
|
2
|
+
# Defines a basic controller for all Cathode controllers to inherit from.
|
3
|
+
# Intercepts all Rails requests and sends them off to {Request} with the
|
4
|
+
# context to to be processed.
|
5
|
+
class BaseController < ActionController::Base
|
6
|
+
%w(index show create update destroy custom).each do |method|
|
7
|
+
define_method method do
|
8
|
+
make_request
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def make_request
|
15
|
+
if Cathode::Base.tokens_required
|
16
|
+
authenticate_or_request_with_http_token do |token|
|
17
|
+
Token.find_by token: token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
request = Cathode::Request.create self
|
22
|
+
|
23
|
+
render json: request._body, status: request._status unless performed?
|
24
|
+
end
|
25
|
+
|
26
|
+
def resource_params
|
27
|
+
params[controller_name.singularize]
|
28
|
+
end
|
46
29
|
end
|
47
30
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Cathode
|
2
|
+
# Defines a token model to hold the API tokens.
|
3
|
+
class Token < ActiveRecord::Base
|
4
|
+
after_initialize :generate_token
|
5
|
+
|
6
|
+
validates :token, uniqueness: true
|
7
|
+
|
8
|
+
# Expires the token by deactivating it and updating its `expired_at` field.
|
9
|
+
# @return [Token] self
|
10
|
+
def expire
|
11
|
+
update active: false, expired_at: Time.now
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def generate_token
|
18
|
+
self.token = SecureRandom.hex
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,2 +1,18 @@
|
|
1
|
+
def attach_resources(resources)
|
2
|
+
resources.each do |resource|
|
3
|
+
method = resource.singular ? :resource : :resources
|
4
|
+
send method, resource.name, controller: resource.controller_prefix.underscore, only: resource.default_actions.map(&:name) do
|
5
|
+
resource.custom_actions.each do |action|
|
6
|
+
match action.name => action.name, action: 'custom', via: action.http_method
|
7
|
+
end
|
8
|
+
attach_resources(resource._resources)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
1
13
|
Cathode::Engine.routes.draw do
|
14
|
+
Cathode::Base.versions.each do |version|
|
15
|
+
attach_resources(version._resources)
|
16
|
+
end
|
17
|
+
match '*path' => 'base#custom', via: [:get, :post, :put, :delete]
|
2
18
|
end
|
data/lib/cathode.rb
CHANGED
@@ -1,14 +1 @@
|
|
1
|
-
require 'cathode/engine'
|
2
1
|
require 'cathode/base'
|
3
|
-
require 'cathode/exceptions'
|
4
|
-
|
5
|
-
module Cathode
|
6
|
-
class Engine < ::Rails::Engine
|
7
|
-
config.generators do |g|
|
8
|
-
g.test_framework :rspec, :fixture => false
|
9
|
-
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
|
10
|
-
g.assets false
|
11
|
-
g.helper false
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
data/lib/cathode/_version.rb
CHANGED
data/lib/cathode/action.rb
CHANGED
@@ -1,40 +1,78 @@
|
|
1
1
|
module Cathode
|
2
|
+
# An `Action` can be added to a {Resource} or a {Version} and contains the
|
3
|
+
# default behavior of that action (if it is a default action), or the override
|
4
|
+
# behavior if it is a custom action or an overridden default action.
|
2
5
|
class Action
|
6
|
+
include ActionDsl
|
7
|
+
|
8
|
+
attr_accessor :strong_params
|
3
9
|
attr_reader :action_access_filter,
|
10
|
+
:action_block,
|
11
|
+
:http_method,
|
4
12
|
:name,
|
13
|
+
:override_block,
|
5
14
|
:resource
|
6
15
|
|
16
|
+
delegate :parent, to: :resource
|
7
17
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
class << self
|
19
|
+
# Creates an action by initializing the appropriate subclass
|
20
|
+
# @param action [Symbol] The action's name
|
21
|
+
# @param resource [Resource] The resource the action belongs to
|
22
|
+
# @param params [Hash] An optional params hash
|
23
|
+
# @param block The action's properties, defined with the {ActionDsl}
|
24
|
+
# @return [IndexAction, ShowAction, CreateAction, UpdateAction,
|
25
|
+
# DestroyAction, CustomAction] The subclassed action
|
26
|
+
def create(action, resource, params = nil, &block)
|
27
|
+
klass = case action
|
28
|
+
when :index
|
29
|
+
IndexAction
|
30
|
+
when :show
|
31
|
+
ShowAction
|
32
|
+
when :create
|
33
|
+
CreateAction
|
34
|
+
when :update
|
35
|
+
UpdateAction
|
36
|
+
when :destroy
|
37
|
+
DestroyAction
|
38
|
+
else
|
39
|
+
CustomAction
|
40
|
+
end
|
41
|
+
klass.new(action, resource, params, &block)
|
42
|
+
end
|
22
43
|
end
|
23
44
|
|
24
|
-
|
45
|
+
# Initializes the action
|
46
|
+
# @param action [Symbol] The action's name
|
47
|
+
# @param resource [Resource] The resource the action belongs to
|
48
|
+
# @param params [Hash] An optional params hash
|
49
|
+
# @param block The action's properties, defined with the {ActionDsl}
|
50
|
+
def initialize(action, resource, params = {}, &block)
|
25
51
|
@name, @resource = action, resource
|
52
|
+
@allowed_subactions = []
|
26
53
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
54
|
+
if block_given?
|
55
|
+
if params[:override]
|
56
|
+
override &block
|
57
|
+
else
|
58
|
+
if [:index, :show, :create, :update, :destroy].include? action
|
59
|
+
instance_eval &block
|
60
|
+
else
|
61
|
+
@action_block = block
|
62
|
+
end
|
63
|
+
end
|
33
64
|
end
|
34
65
|
|
35
|
-
|
66
|
+
@http_method = params[:method] if params.present?
|
36
67
|
|
37
|
-
|
68
|
+
after_initialize if respond_to? :after_initialize
|
69
|
+
end
|
70
|
+
|
71
|
+
# Whether a subaction is permitted
|
72
|
+
# @param subaction [Symbol] The subaction's name
|
73
|
+
# @return [Boolean]
|
74
|
+
def allowed?(subaction)
|
75
|
+
@allowed_subactions.include? subaction
|
38
76
|
end
|
39
77
|
|
40
78
|
private
|
@@ -46,37 +84,157 @@ module Cathode
|
|
46
84
|
def access_filter(&filter)
|
47
85
|
@action_access_filter = filter
|
48
86
|
end
|
87
|
+
|
88
|
+
def allows(*subactions)
|
89
|
+
@allowed_subactions = subactions
|
90
|
+
end
|
91
|
+
|
92
|
+
def replace(&block)
|
93
|
+
@action_block = block
|
94
|
+
end
|
95
|
+
|
96
|
+
def override(&block)
|
97
|
+
@override_block = block
|
98
|
+
end
|
99
|
+
|
100
|
+
def overridden?
|
101
|
+
override_block.present?
|
102
|
+
end
|
103
|
+
|
104
|
+
def after_resource_initialized
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
# Requires that the action has an attributes block defined or one is defined
|
109
|
+
# at the resource level
|
110
|
+
module RequiresStrongParams
|
111
|
+
# Raises an error if there is no attributes block defined
|
112
|
+
def after_resource_initialized
|
113
|
+
if strong_params.nil?
|
114
|
+
if resource.strong_params.present?
|
115
|
+
@strong_params = resource.strong_params
|
116
|
+
else
|
117
|
+
fail UnknownAttributesError, "An attributes block was not specified for `#{name}' action on resource `#{resource.name}'"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
super
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Raises an error if the action was defined on a resource whose parent
|
126
|
+
# doesn't have an association to the resource
|
127
|
+
module RequiresAssociation
|
128
|
+
# Defines the possible associations as `:resources` (`has_many`) or
|
129
|
+
# `:resource` (`has_one`)
|
130
|
+
def association_keys
|
131
|
+
[
|
132
|
+
resource.name.to_s.singularize.to_sym,
|
133
|
+
resource.name.to_s.pluralize.to_sym
|
134
|
+
]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Determines whether an expected association is present.
|
138
|
+
def after_resource_initialized
|
139
|
+
if parent.present? && !overridden?
|
140
|
+
reflections = parent.model.reflections
|
141
|
+
|
142
|
+
if association_keys.map { |key| reflections.include?(key) }.none?
|
143
|
+
raise MissingAssociationError, error_message
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Raises an error if the action was defined on a resource whose parent
|
152
|
+
# doesn't have a `has_one` association to the resource
|
153
|
+
module RequiresHasOneAssociation
|
154
|
+
include RequiresAssociation
|
155
|
+
|
156
|
+
# Defines the possible associations as `:resource` (`has_one`)
|
157
|
+
def association_keys
|
158
|
+
[resource.name.to_s.singularize.to_sym]
|
159
|
+
end
|
160
|
+
|
161
|
+
# Provides an error message to display if the association is missing.
|
162
|
+
def error_message
|
163
|
+
"Can't use default :#{name} action on `#{parent.name}' without a has_one `#{resource.name.to_s.singularize}' association"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Raises an error if the action was defined on a resource whose parent
|
168
|
+
# doesn't have a `has_many` association to the resource
|
169
|
+
module RequiresHasManyAssociation
|
170
|
+
include RequiresAssociation
|
171
|
+
|
172
|
+
# Defines the possible associations as `:resources` (`has_many`).
|
173
|
+
def association_keys
|
174
|
+
[resource.name.to_s.pluralize.to_sym]
|
175
|
+
end
|
176
|
+
|
177
|
+
# Provides an error message to display if the association is missing.
|
178
|
+
def error_message
|
179
|
+
"Can't use default :#{name} action on `#{parent.name}' without a has_many or has_and_belongs_to_many `#{resource.name.to_s.singularize}' association"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Requires the resource to define custom action behavior if it is a singular
|
184
|
+
# resource.
|
185
|
+
module RequiresCustomActionForSingular
|
186
|
+
# Raises an error if the resource is singular and attempting to use the
|
187
|
+
# default action without a parent resource
|
188
|
+
def after_resource_initialized
|
189
|
+
if resource.singular && !overridden? && resource.parent.nil?
|
190
|
+
raise Cathode::ActionBehaviorMissingError,
|
191
|
+
"Can't use default :#{name} action on singular resource `#{resource.name}'"
|
192
|
+
end
|
193
|
+
|
194
|
+
super
|
195
|
+
end
|
196
|
+
end
|
49
197
|
end
|
50
198
|
|
199
|
+
# Provides additional behavior for index actions.
|
51
200
|
class IndexAction < Action
|
52
|
-
|
53
|
-
|
54
|
-
end
|
201
|
+
include RequiresCustomActionForSingular
|
202
|
+
include RequiresHasManyAssociation
|
55
203
|
end
|
56
204
|
|
205
|
+
# Provides additional behavior for show actions.
|
57
206
|
class ShowAction < Action
|
58
|
-
|
59
|
-
|
60
|
-
end
|
207
|
+
include RequiresCustomActionForSingular
|
208
|
+
include RequiresHasOneAssociation
|
61
209
|
end
|
62
210
|
|
211
|
+
# Provides additional behavior for create actions.
|
63
212
|
class CreateAction < Action
|
64
|
-
|
65
|
-
|
66
|
-
|
213
|
+
include RequiresCustomActionForSingular
|
214
|
+
include RequiresStrongParams
|
215
|
+
include RequiresAssociation
|
67
216
|
end
|
68
217
|
|
218
|
+
# Provides additional behavior for update actions.
|
69
219
|
class UpdateAction < Action
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
record.reload
|
74
|
-
end
|
220
|
+
include RequiresCustomActionForSingular
|
221
|
+
include RequiresStrongParams
|
222
|
+
include RequiresHasOneAssociation
|
75
223
|
end
|
76
224
|
|
225
|
+
# Provides additional behavior for destroy actions.
|
77
226
|
class DestroyAction < Action
|
78
|
-
|
79
|
-
|
227
|
+
include RequiresCustomActionForSingular
|
228
|
+
include RequiresHasOneAssociation
|
229
|
+
end
|
230
|
+
|
231
|
+
# Provides additional behavior for non-default actions.
|
232
|
+
class CustomAction < Action
|
233
|
+
# Raises an error if the action was defined without an HTTP method
|
234
|
+
def after_initialize
|
235
|
+
if http_method.nil?
|
236
|
+
raise RequestMethodMissingError, "You must specify an HTTP method (get, put, post, delete) for action `#{@name}'"
|
237
|
+
end
|
80
238
|
end
|
81
239
|
end
|
82
240
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Cathode
|
2
|
+
# Holds the domain-specific language (DSL) for describing actions.
|
3
|
+
module ActionDsl
|
4
|
+
# Lists the actions that are default (i.e., `index`, `show`, `create`,
|
5
|
+
# `update`, and `destroy`)
|
6
|
+
# @return [Array] The default actions
|
7
|
+
def default_actions
|
8
|
+
actions.select { |action| DEFAULT_ACTIONS.include? action.name }
|
9
|
+
end
|
10
|
+
|
11
|
+
# Lists the actions that are not default
|
12
|
+
# @return [Array] The custom actions
|
13
|
+
def custom_actions
|
14
|
+
actions - default_actions
|
15
|
+
end
|
16
|
+
|
17
|
+
# Lists all the actions; initializes an empty `ObjectCollection` if there
|
18
|
+
# aren't any yet
|
19
|
+
# @return [Array] The actions
|
20
|
+
def actions
|
21
|
+
@actions ||= ObjectCollection.new
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def action(action, params = {}, &block)
|
27
|
+
actions << Action.create(action, self, params, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(action_name, &block)
|
31
|
+
action action_name, method: :get, &block
|
32
|
+
end
|
33
|
+
|
34
|
+
def post(action_name, &block)
|
35
|
+
action action_name, method: :post, &block
|
36
|
+
end
|
37
|
+
|
38
|
+
def put(action_name, &block)
|
39
|
+
action action_name, method: :put, &block
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(action_name, &block)
|
43
|
+
action action_name, method: :delete, &block
|
44
|
+
end
|
45
|
+
|
46
|
+
def replace_action(action_name, &block)
|
47
|
+
action action_name do
|
48
|
+
replace(&block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def override_action(action_name, params = {}, &block)
|
53
|
+
action action_name, params.merge(override: true), &block
|
54
|
+
end
|
55
|
+
|
56
|
+
def attributes(&block)
|
57
|
+
@strong_params = block
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|