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/lib/cathode/resource.rb
CHANGED
@@ -1,39 +1,70 @@
|
|
1
1
|
module Cathode
|
2
|
+
# A `Resource` is paired with a Rails model and describes the actions that the
|
3
|
+
# resource responds to.
|
2
4
|
class Resource
|
3
|
-
|
4
|
-
|
5
|
+
include ActionDsl
|
6
|
+
include ResourceDsl
|
5
7
|
|
6
|
-
|
8
|
+
attr_reader :controller_prefix,
|
9
|
+
:model,
|
10
|
+
:name,
|
11
|
+
:parent,
|
12
|
+
:singular,
|
13
|
+
:strong_params
|
14
|
+
|
15
|
+
# Creates a new resource.
|
16
|
+
# @param resource_name [Symbol] The resource's name
|
17
|
+
# @param params [Hash] An optional params hash, e.g. `{ actions: :all }`
|
18
|
+
# @param context [Resource] An optional parent resource
|
19
|
+
# @param block The resource's actions and properties, defined with the
|
20
|
+
# {ActionDsl} and {ResourceDsl}
|
21
|
+
def initialize(resource_name, params = nil, context = nil, &block)
|
7
22
|
require_resource_constant! resource_name
|
8
23
|
|
9
|
-
params ||= {
|
24
|
+
params ||= {}
|
25
|
+
params[:actions] ||= []
|
10
26
|
|
11
27
|
@name = resource_name
|
12
28
|
|
13
|
-
|
29
|
+
camelized_resource = resource_name.to_s.camelize
|
14
30
|
|
15
|
-
@
|
16
|
-
|
17
|
-
|
18
|
-
|
31
|
+
@controller_prefix = if context.present? && context.is_a?(Resource)
|
32
|
+
@parent = context
|
33
|
+
"#{context.controller_prefix}#{camelized_resource}"
|
34
|
+
else
|
35
|
+
camelized_resource
|
19
36
|
end
|
20
|
-
self.instance_eval &block if block_given?
|
21
|
-
actions = @actions
|
22
37
|
|
23
|
-
|
24
|
-
|
38
|
+
controller_name = "#{@controller_prefix}Controller"
|
39
|
+
unless Cathode.const_defined? controller_name
|
40
|
+
Cathode.const_set controller_name, Class.new(Cathode::BaseController)
|
25
41
|
end
|
26
|
-
end
|
27
42
|
|
28
|
-
|
43
|
+
@actions = ObjectCollection.new
|
44
|
+
actions_to_add = params[:actions] == :all ? DEFAULT_ACTIONS : params[:actions]
|
45
|
+
actions_to_add.each { |action_name| action action_name }
|
46
|
+
|
47
|
+
@singular = params[:singular]
|
48
|
+
|
49
|
+
instance_eval(&block) if block_given?
|
29
50
|
|
30
|
-
|
31
|
-
|
51
|
+
@actions.each do |action|
|
52
|
+
action.after_resource_initialized if action.respond_to? :after_resource_initialized
|
53
|
+
end
|
54
|
+
|
55
|
+
if @strong_params.present? && actions.find(:create).nil? && actions.find(:update).nil?
|
56
|
+
raise UnknownActionError,
|
57
|
+
'An attributes block was specified without a :create or :update action'
|
58
|
+
end
|
32
59
|
end
|
33
60
|
|
61
|
+
private
|
62
|
+
|
34
63
|
def require_resource_constant!(resource_name)
|
35
|
-
|
36
|
-
|
64
|
+
constant = resource_name.to_s.singularize.camelize
|
65
|
+
@model = constant.safe_constantize
|
66
|
+
if @model.nil?
|
67
|
+
fail UnknownResourceError, "Could not find constant `#{constant}' for resource `#{resource_name}'"
|
37
68
|
end
|
38
69
|
end
|
39
70
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Cathode
|
2
|
+
# Holds the domain-specific language (DSL) for describing resources.
|
3
|
+
module ResourceDsl
|
4
|
+
# Lists all the resources; initializes an empty `ObjectCollection` if there
|
5
|
+
# aren't any yet
|
6
|
+
# @return [Array] The resources
|
7
|
+
def _resources
|
8
|
+
@_resources ||= ObjectCollection.new
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def resources(resource_name, params = {}, &block)
|
14
|
+
add_resource resource_name, false, params, &block
|
15
|
+
end
|
16
|
+
|
17
|
+
def resource(resource_name, params = {}, &block)
|
18
|
+
add_resource resource_name, true, params, &block
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_resource(resource_name, singular, params, &block)
|
22
|
+
params ||= {}
|
23
|
+
params = params.merge(singular: singular)
|
24
|
+
existing_resource = _resources.find resource_name
|
25
|
+
new_resource = Resource.new(resource_name, params, self, &block)
|
26
|
+
|
27
|
+
if existing_resource.present?
|
28
|
+
existing_resource.actions.add new_resource.actions.objects
|
29
|
+
else
|
30
|
+
@_resources << new_resource
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_resource(resources)
|
35
|
+
resources = [resources] unless resources.is_a?(Array)
|
36
|
+
|
37
|
+
resources.each do |resource|
|
38
|
+
if _resources.find(resource).nil?
|
39
|
+
fail UnknownResourceError, "Unknown resource `#{resource}'"
|
40
|
+
end
|
41
|
+
|
42
|
+
@_resources.delete(resource)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Cathode
|
2
|
+
# Defines the default behavior for a show request.
|
3
|
+
class ShowRequest < Request
|
4
|
+
# Determine the default action to use depending on the resource. If the
|
5
|
+
# resource is singular, set the body to the parent's associated record.
|
6
|
+
# Otherwise, lookup the record directly.
|
7
|
+
def default_action_block
|
8
|
+
proc do
|
9
|
+
body record
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Cathode
|
2
|
+
# Defines the default behavior for an update request.
|
3
|
+
class UpdateRequest < Request
|
4
|
+
# Sets the default action to update a resource. If the resource is
|
5
|
+
# singular, updates the parent's associated resource. Otherwise, updates the
|
6
|
+
# resource directly.
|
7
|
+
def default_action_block
|
8
|
+
proc do
|
9
|
+
begin
|
10
|
+
record = if resource.singular
|
11
|
+
parent_model = resource.parent.model.find(parent_resource_id)
|
12
|
+
parent_model.send resource.name
|
13
|
+
else
|
14
|
+
record = model.find(params[:id])
|
15
|
+
end
|
16
|
+
|
17
|
+
record.update(instance_eval(&@strong_params))
|
18
|
+
body record.reload
|
19
|
+
rescue ActionController::ParameterMissing => error
|
20
|
+
body error.message
|
21
|
+
status :bad_request
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/cathode/version.rb
CHANGED
@@ -1,30 +1,41 @@
|
|
1
|
-
require 'semantic'
|
2
|
-
|
3
1
|
module Cathode
|
2
|
+
# A `Version` encapsulates a specific SemVer-compliant version of the API with
|
3
|
+
# a set of resources and actions.
|
4
4
|
class Version
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@@all = {}
|
9
|
-
|
10
|
-
def initialize(version_number, &block)
|
11
|
-
@version = Semantic::Version.new Version.standardize(version_number)
|
12
|
-
@resources = {}
|
13
|
-
|
14
|
-
self.instance_eval &block if block_given?
|
5
|
+
include ActionDsl
|
6
|
+
include ResourceDsl
|
15
7
|
|
16
|
-
|
17
|
-
|
8
|
+
attr_reader :ancestor,
|
9
|
+
:version
|
18
10
|
|
19
|
-
|
20
|
-
resources[resource].actions[params[:action].to_sym].perform params
|
21
|
-
end
|
11
|
+
@all = []
|
22
12
|
|
23
13
|
class << self
|
24
|
-
|
25
|
-
|
14
|
+
attr_reader :all
|
15
|
+
|
16
|
+
# Defines a new version.
|
17
|
+
# @param version_number [String, Fixnum, Float] A number or string
|
18
|
+
# representing a SemVer-compliant version number. If a `Fixnum` or
|
19
|
+
# `Float` is passed, it will be converted to a string before being
|
20
|
+
# evaluated for SemVer compliance, so passing `1.5` is equivalent to
|
21
|
+
# passing `'1.5.0'`.
|
22
|
+
# @param block A block defining the version's resources and actions, and
|
23
|
+
# has access to the methods in the {Cathode::ActionDsl} and {Cathode::ResourceDsl}
|
24
|
+
# @return [Version]
|
25
|
+
def define(version_number, &block)
|
26
|
+
version = Version.find(version_number)
|
27
|
+
if version.present?
|
28
|
+
version.instance_eval(&block)
|
29
|
+
else
|
30
|
+
version = self.new(version_number, &block)
|
31
|
+
end
|
32
|
+
version
|
26
33
|
end
|
27
34
|
|
35
|
+
# Polyfills a version number snippet to be SemVer-compliant.
|
36
|
+
# @param rough_version [String] A version number snippet such as '1' or
|
37
|
+
# '2.5'
|
38
|
+
# @return [String] The SemVer-compliant version number
|
28
39
|
def standardize(rough_version)
|
29
40
|
version_parts = rough_version.to_s.split '.'
|
30
41
|
if version_parts.count < 2
|
@@ -35,15 +46,93 @@ module Cathode
|
|
35
46
|
version_parts.join '.'
|
36
47
|
end
|
37
48
|
|
38
|
-
|
39
|
-
|
49
|
+
# Looks up a version by version number.
|
50
|
+
# @param version_number [String] The version to find
|
51
|
+
# @return [Version, nil] The version if found, `nil` if there is no such
|
52
|
+
# version
|
53
|
+
def find(version_number)
|
54
|
+
Version.all.detect { |v| v.version == standardize(version_number) }
|
55
|
+
rescue ArgumentError
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# Whether a given version exists
|
60
|
+
# @param version_number [String] The version to check
|
61
|
+
# @return [Boolean]
|
62
|
+
def exists?(version_number)
|
63
|
+
find(version_number).present?
|
40
64
|
end
|
41
65
|
end
|
42
66
|
|
67
|
+
# Initializes a new version.
|
68
|
+
# @param version_number [String] A SemVer-compliant version number.
|
69
|
+
# @param block A block defining the version's resources and actions, and
|
70
|
+
# has access to the methods in the {Cathode::ActionDsl} and {Cathode::ResourceDsl}
|
71
|
+
def initialize(version_number, &block)
|
72
|
+
@version = Semantic::Version.new Version.standardize(version_number)
|
73
|
+
|
74
|
+
if Version.all.present?
|
75
|
+
@ancestor = Version.all.last
|
76
|
+
@_resources = DeepClone.clone @ancestor._resources
|
77
|
+
actions.add ancestor.actions.objects
|
78
|
+
end
|
79
|
+
|
80
|
+
instance_eval(&block) if block_given?
|
81
|
+
|
82
|
+
Version.all << self
|
83
|
+
end
|
84
|
+
|
85
|
+
# Whether a resource is defined on the version.
|
86
|
+
# @param resource [Symbol] The resource's name
|
87
|
+
# @return [Boolean]
|
88
|
+
def resource?(resource)
|
89
|
+
_resources.names.include? resource.to_sym
|
90
|
+
end
|
91
|
+
|
92
|
+
# Whether an action is defined on a resource on the version.
|
93
|
+
# @param resource [Symbol] The resource's name
|
94
|
+
# @param action [Symbol] The action's name
|
95
|
+
# @return [Boolean]
|
96
|
+
def action?(resource, action)
|
97
|
+
resource = resource.to_sym
|
98
|
+
action = action.to_sym
|
99
|
+
|
100
|
+
return false unless resource?(resource)
|
101
|
+
|
102
|
+
_resources.find(resource).actions.names.include? action
|
103
|
+
end
|
104
|
+
|
43
105
|
private
|
44
106
|
|
45
|
-
def
|
46
|
-
|
107
|
+
def remove_action(*args)
|
108
|
+
if args.last.is_a?(Hash)
|
109
|
+
resource_name = args.last[:from]
|
110
|
+
resource = @_resources.find(resource_name)
|
111
|
+
actions_to_remove = args.take args.size - 1
|
112
|
+
|
113
|
+
if resource.nil?
|
114
|
+
fail UnknownResourceError, "Unknown resource `#{resource_name}' on ancestor version #{ancestor.version}"
|
115
|
+
end
|
116
|
+
|
117
|
+
actions_to_remove.each do |action|
|
118
|
+
if resource.actions.find(action).nil?
|
119
|
+
fail UnknownActionError, "Unknown action `#{action}' on resource `#{resource_name}'"
|
120
|
+
end
|
121
|
+
|
122
|
+
resource.actions.delete action
|
123
|
+
end
|
124
|
+
else
|
125
|
+
args.each do |action|
|
126
|
+
if actions.find(action).nil?
|
127
|
+
fail UnknownActionError, "Unknown action `#{action}' on ancestor version #{ancestor.version}"
|
128
|
+
end
|
129
|
+
|
130
|
+
actions.delete action
|
131
|
+
end
|
132
|
+
end
|
47
133
|
end
|
134
|
+
|
135
|
+
alias_method :remove_resources, :remove_resource
|
136
|
+
alias_method :remove_actions, :remove_action
|
48
137
|
end
|
49
138
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
namespace :cathode do
|
2
|
+
task :info do
|
3
|
+
puts Cathode::Debug.info
|
4
|
+
end
|
5
|
+
end
|
File without changes
|
Binary file
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -11,7 +11,22 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
14
|
+
ActiveRecord::Schema.define(version: 20140425164100) do
|
15
|
+
|
16
|
+
create_table "cathode_tokens", force: true do |t|
|
17
|
+
t.boolean "active", default: true
|
18
|
+
t.datetime "expired_at"
|
19
|
+
t.string "token"
|
20
|
+
t.datetime "created_at"
|
21
|
+
t.datetime "updated_at"
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table "payments", force: true do |t|
|
25
|
+
t.integer "amount"
|
26
|
+
t.integer "sale_id"
|
27
|
+
t.datetime "created_at"
|
28
|
+
t.datetime "updated_at"
|
29
|
+
end
|
15
30
|
|
16
31
|
create_table "products", force: true do |t|
|
17
32
|
t.string "title"
|
@@ -20,4 +35,19 @@ ActiveRecord::Schema.define(version: 20140404222551) do
|
|
20
35
|
t.datetime "updated_at"
|
21
36
|
end
|
22
37
|
|
38
|
+
create_table "sales", force: true do |t|
|
39
|
+
t.integer "product_id"
|
40
|
+
t.integer "subtotal"
|
41
|
+
t.integer "taxes"
|
42
|
+
t.datetime "created_at"
|
43
|
+
t.datetime "updated_at"
|
44
|
+
t.integer "salesperson_id"
|
45
|
+
end
|
46
|
+
|
47
|
+
create_table "salespeople", force: true do |t|
|
48
|
+
t.string "name"
|
49
|
+
t.datetime "created_at"
|
50
|
+
t.datetime "updated_at"
|
51
|
+
end
|
52
|
+
|
23
53
|
end
|