hai 0.0.2 → 0.0.3
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/Gemfile +1 -1
- data/Gemfile.lock +14 -2
- data/README.md +186 -10
- data/app/controllers/hai/rest_controller.rb +65 -0
- data/config/routes.rb +7 -0
- data/lib/hai/action_mods.rb +26 -0
- data/lib/hai/create.rb +41 -3
- data/lib/hai/delete.rb +17 -0
- data/lib/hai/graphql/create_mutations.rb +28 -9
- data/lib/hai/graphql/delete_mutations.rb +25 -0
- data/lib/hai/graphql/list_queries.rb +20 -13
- data/lib/hai/graphql/read_queries.rb +11 -3
- data/lib/hai/graphql/types.rb +88 -13
- data/lib/hai/graphql/update_mutations.rb +34 -0
- data/lib/hai/graphql.rb +17 -4
- data/lib/hai/policies.rb +26 -0
- data/lib/hai/railtie.rb +13 -0
- data/lib/hai/read.rb +82 -44
- data/lib/hai/tasks/graphql/filter_type.rake +8 -0
- data/lib/hai/tasks/hai.rake +2 -0
- data/lib/hai/types/arel/boolean_input_type.rb +11 -0
- data/lib/hai/types/arel/float_input_type.rb +10 -0
- data/lib/hai/types/arel/sort_input_type.rb +13 -0
- data/lib/hai/types/base_create.rb +14 -0
- data/lib/hai/types/mutation_error_type.rb +11 -0
- data/lib/hai/types/sort_input_type.rb +10 -0
- data/lib/hai/update.rb +25 -5
- data/lib/hai/version.rb +1 -1
- data/lib/hai.rb +20 -4
- data/todo.md +17 -10
- metadata +34 -3
- data/hai-0.0.1.gem +0 -0
data/lib/hai/graphql/types.rb
CHANGED
@@ -5,7 +5,8 @@ require "hai/types/arel/datetime_input_type"
|
|
5
5
|
module Hai
|
6
6
|
module GraphQL
|
7
7
|
module Types
|
8
|
-
module Arel
|
8
|
+
module Arel
|
9
|
+
end
|
9
10
|
ALLOWED_TYPES = ::GraphQL::Types.constants - %i[Relay JSON]
|
10
11
|
|
11
12
|
def self.included(base)
|
@@ -13,35 +14,109 @@ module Hai
|
|
13
14
|
end
|
14
15
|
|
15
16
|
module ClassMethods
|
16
|
-
def
|
17
|
-
|
17
|
+
def hai_types(*models)
|
18
|
+
base_types = models.map(&method(:define_base_type))
|
19
|
+
models.each do |model|
|
20
|
+
model.reflections.map(&method(:add_base_type_reflections))
|
21
|
+
end
|
22
|
+
|
23
|
+
models.map(&method(:define_input_object))
|
24
|
+
filter_types = models.map(&method(:define_filter_type))
|
25
|
+
models.each do |model|
|
26
|
+
model.reflections.map(&method(:add_filter_type_reflections))
|
27
|
+
end
|
28
|
+
|
29
|
+
filter_types.each do |filter_type|
|
30
|
+
filter_type.send(
|
31
|
+
:argument,
|
32
|
+
:or,
|
33
|
+
"[#{filter_type}]",
|
34
|
+
required: false
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def name_for_base_type(model)
|
40
|
+
"Types::#{model}Type"
|
41
|
+
end
|
42
|
+
|
43
|
+
def define_base_type(model)
|
44
|
+
return if const_defined? name_for_base_type(model)
|
18
45
|
|
19
46
|
klass = Class.new(::Types::BaseObject)
|
20
|
-
model.attribute_types.each do |attr,
|
21
|
-
klass.send(:field, attr, ::GraphQL::
|
47
|
+
model.attribute_types.each do |attr, type|
|
48
|
+
klass.send(:field, attr, Hai::GraphQL::TYPE_CAST[type.class])
|
49
|
+
rescue ArgumentError => e
|
50
|
+
binding.pry
|
22
51
|
end
|
23
52
|
|
24
53
|
::Types.const_set "#{model}Type", klass
|
54
|
+
end
|
25
55
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
56
|
+
def add_base_type_reflections(name, ref)
|
57
|
+
if name == ref.plural_name
|
58
|
+
name_for_base_type(ref.active_record).constantize.send(
|
59
|
+
:field,
|
60
|
+
name,
|
61
|
+
"[#{name_for_base_type(ref.klass)}]"
|
62
|
+
)
|
63
|
+
else
|
64
|
+
name_for_base_type(ref.active_record).constantize.send(
|
65
|
+
:field,
|
66
|
+
name,
|
67
|
+
name_for_base_type(ref.klass)
|
68
|
+
)
|
33
69
|
end
|
70
|
+
rescue NoMethodError => e
|
71
|
+
binding.pry
|
72
|
+
end
|
34
73
|
|
74
|
+
def define_input_object(model)
|
35
75
|
# input objects
|
36
76
|
klass = Class.new(::Types::BaseInputObject)
|
37
77
|
klass.description("Attributes for creating or updating a #{model}.")
|
38
78
|
model.attribute_types.each do |attr, type|
|
39
79
|
next if %w[id created_at updated_at].include?(attr)
|
40
80
|
|
41
|
-
klass.argument(
|
81
|
+
klass.argument(
|
82
|
+
attr,
|
83
|
+
Hai::GraphQL::TYPE_CAST[type.class],
|
84
|
+
required: false
|
85
|
+
)
|
42
86
|
end
|
43
87
|
::Types.const_set "#{model}Attributes", klass
|
44
88
|
end
|
89
|
+
|
90
|
+
def name_for_filter_type(model)
|
91
|
+
"#{model}FilterInputType"
|
92
|
+
end
|
93
|
+
|
94
|
+
def define_filter_type(model)
|
95
|
+
# Class Filter
|
96
|
+
filter_klass = Class.new(::GraphQL::Schema::InputObject)
|
97
|
+
model.attribute_types.each do |attr, type|
|
98
|
+
filter_klass.send(
|
99
|
+
:argument,
|
100
|
+
attr,
|
101
|
+
AREL_TYPE_CAST[type.class] ||
|
102
|
+
AREL_TYPE_CAST[type.class.superclass],
|
103
|
+
required: false
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
Object.const_set name_for_filter_type(model), filter_klass
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_filter_type_reflections(name, ref)
|
111
|
+
name_for_filter_type(ref.active_record).constantize.send(
|
112
|
+
:argument,
|
113
|
+
name,
|
114
|
+
name_for_filter_type(ref.klass),
|
115
|
+
required: false
|
116
|
+
)
|
117
|
+
rescue NoMethodError, RuntimeError => e
|
118
|
+
binding.pry
|
119
|
+
end
|
45
120
|
end
|
46
121
|
end
|
47
122
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Hai
|
2
|
+
module GraphQL
|
3
|
+
class UpdateMutations
|
4
|
+
class << self
|
5
|
+
def add(mutation_type, model)
|
6
|
+
define_resolver(model)
|
7
|
+
add_field(mutation_type, model)
|
8
|
+
end
|
9
|
+
|
10
|
+
def define_resolver(model)
|
11
|
+
klass = Class.new(Hai::GraphQL::Types::BaseCreate)
|
12
|
+
klass.send(:graphql_name, "Update#{model}")
|
13
|
+
klass.description("Mutation to Update #{model}.")
|
14
|
+
|
15
|
+
klass.argument(:attributes, "Types::#{model}Attributes")
|
16
|
+
klass.argument(:id, ::GraphQL::Types::ID)
|
17
|
+
|
18
|
+
klass.field(:result, ::Types.const_get("#{model}Type"))
|
19
|
+
|
20
|
+
klass.define_method(:resolve) do |id:, attributes:|
|
21
|
+
Hai::Update.new(model, context).execute(id: id, attributes: attributes.to_h)
|
22
|
+
end
|
23
|
+
|
24
|
+
Hai::GraphQL::Types.const_set("Update#{model}", klass)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_field(mutation_type, model)
|
28
|
+
mutation_type.field("update_#{model.name.downcase}",
|
29
|
+
mutation: Hai::GraphQL::Types.const_get("Update#{model}"))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/hai/graphql.rb
CHANGED
@@ -1,34 +1,47 @@
|
|
1
1
|
require "hai/graphql/read_queries"
|
2
2
|
require "hai/graphql/list_queries"
|
3
3
|
require "hai/graphql/create_mutations"
|
4
|
+
require "hai/graphql/update_mutations"
|
5
|
+
require "hai/graphql/delete_mutations"
|
4
6
|
require "hai/types/arel/int_input_type"
|
7
|
+
require "hai/types/arel/float_input_type"
|
5
8
|
require "hai/types/arel/string_input_type"
|
6
9
|
require "hai/types/arel/datetime_input_type"
|
10
|
+
require "hai/types/arel/boolean_input_type"
|
7
11
|
|
8
12
|
module Hai
|
9
13
|
module GraphQL
|
10
14
|
TYPE_CAST = {
|
11
15
|
ActiveModel::Type::Integer => ::GraphQL::Types::Int,
|
16
|
+
ActiveModel::Type::Float => ::GraphQL::Types::Float,
|
12
17
|
ActiveModel::Type::String => ::GraphQL::Types::String,
|
13
|
-
|
18
|
+
ActiveModel::Type::Boolean => ::GraphQL::Types::Boolean,
|
19
|
+
ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter =>
|
20
|
+
::GraphQL::Types::ISO8601DateTime
|
14
21
|
}.freeze
|
15
22
|
AREL_TYPE_CAST = {
|
16
23
|
ActiveModel::Type::Integer => Hai::GraphQL::Types::Arel::IntInputType,
|
24
|
+
ActiveModel::Type::Float => Hai::GraphQL::Types::Arel::FloatInputType,
|
17
25
|
ActiveModel::Type::String => Hai::GraphQL::Types::Arel::StringInputType,
|
18
|
-
|
26
|
+
ActiveModel::Type::Boolean => Hai::GraphQL::Types::Arel::BooleanInputType,
|
27
|
+
ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter =>
|
28
|
+
Hai::GraphQL::Types::Arel::DateTimeInputType
|
19
29
|
}.freeze
|
30
|
+
|
20
31
|
def self.included(base)
|
21
32
|
base.extend(ClassMethods)
|
22
33
|
end
|
23
34
|
|
24
35
|
module ClassMethods
|
25
|
-
def
|
36
|
+
def hai_query(model)
|
26
37
|
Hai::GraphQL::ReadQueries.add(self, model)
|
27
38
|
Hai::GraphQL::ListQueries.add(self, model)
|
28
39
|
end
|
29
40
|
|
30
|
-
def
|
41
|
+
def hai_mutation(model)
|
31
42
|
Hai::GraphQL::CreateMutations.add(self, model)
|
43
|
+
Hai::GraphQL::UpdateMutations.add(self, model)
|
44
|
+
Hai::GraphQL::DeleteMutations.add(self, model)
|
32
45
|
end
|
33
46
|
end
|
34
47
|
end
|
data/lib/hai/policies.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Hai
|
2
|
+
module Policies
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
def check_hai_policy(action, context)
|
8
|
+
return true unless (policy = self.class.policies[action])
|
9
|
+
|
10
|
+
policy.call(self, context)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def policies
|
15
|
+
@policies ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO: validate CRUD actions
|
19
|
+
def policy(action, &block)
|
20
|
+
policies[action] = lambda do |instance, context|
|
21
|
+
block.call(instance, context)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/hai/railtie.rb
ADDED
data/lib/hai/read.rb
CHANGED
@@ -1,42 +1,61 @@
|
|
1
1
|
module Hai
|
2
2
|
class Read
|
3
3
|
attr_accessor :model
|
4
|
-
attr_reader :table
|
4
|
+
attr_reader :table, :context
|
5
5
|
|
6
|
-
def initialize(model)
|
6
|
+
def initialize(model, context)
|
7
7
|
@model = model
|
8
|
+
@context = context
|
9
|
+
@context[:model] = model
|
8
10
|
@table = model.arel_table
|
9
11
|
end
|
10
12
|
|
11
13
|
# return [] or ActiveRecord::Relationship
|
12
|
-
def list(
|
13
|
-
|
14
|
-
offset = query_hash.delete(:offset)
|
15
|
-
filter = query_hash.delete(:filter)
|
16
|
-
|
17
|
-
reflection_queries = build_reflection_queries(filter) if filter
|
18
|
-
query = filter.present? ? model.where(build_query(filter)) : model.all
|
19
|
-
if filter
|
20
|
-
reflection_queries.each do |ref, q|
|
21
|
-
query = query.joins(ref).merge(q)
|
22
|
-
end
|
23
|
-
end
|
14
|
+
def list(filter: nil, limit: nil, offset: nil, sort: nil, **extra)
|
15
|
+
check_list_policy(context)
|
24
16
|
|
17
|
+
context[:arguments] = extra
|
18
|
+
query = build_filter(filter)
|
19
|
+
query = query.order({ sort.fetch(:field) => sort.fetch(:order) }) if sort
|
25
20
|
query = query.limit(limit) if limit
|
26
21
|
query = query.offset(offset) if offset
|
27
|
-
query
|
22
|
+
run_action_modification(query)
|
28
23
|
end
|
29
24
|
|
30
25
|
# return nil or model
|
31
26
|
def read(query_hash)
|
32
|
-
|
27
|
+
build_filter(query_hash).first.tap do |record|
|
28
|
+
if record.respond_to?(:check_hai_policy) &&
|
29
|
+
!record.check_hai_policy(:read, context)
|
30
|
+
raise UnauthorizedError
|
31
|
+
end
|
32
|
+
end
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
def check_read_policy
|
38
|
+
if model.const_defined?("Policies") && model::Policies.respond_to?(:read)
|
39
|
+
model::Policies.read(context)
|
40
|
+
else
|
41
|
+
true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_list_policy
|
46
|
+
if model.const_defined?("Policies") && model::Policies.respond_to?(:list)
|
47
|
+
model::Policies.list(context)
|
48
|
+
else
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def run_action_modification(query)
|
54
|
+
if model.const_defined?("Actions") && model::Actions.respond_to?(:list)
|
55
|
+
model::Actions.list(query, context)
|
56
|
+
else
|
57
|
+
query
|
58
|
+
end
|
40
59
|
end
|
41
60
|
|
42
61
|
def reflections
|
@@ -44,21 +63,53 @@ module Hai
|
|
44
63
|
end
|
45
64
|
|
46
65
|
def query_reflections(query_hash)
|
47
|
-
reflections
|
48
|
-
|
49
|
-
|
66
|
+
reflections
|
67
|
+
.each_with_object({}) do |(ref, _info), acc|
|
68
|
+
acc[ref] = query_hash.delete(ref)
|
69
|
+
end
|
70
|
+
.compact
|
50
71
|
end
|
51
72
|
|
52
73
|
def build_reflection_queries(query_hash)
|
53
|
-
reflections
|
54
|
-
|
55
|
-
|
56
|
-
|
74
|
+
reflections
|
75
|
+
.each_with_object({}) do |(ref, info), acc|
|
76
|
+
q_hash = query_hash.delete(ref)
|
77
|
+
acc[ref] = info.klass.where(
|
78
|
+
where_clause(info.klass.arel_table, q_hash)
|
79
|
+
) if q_hash
|
80
|
+
end
|
81
|
+
.compact
|
57
82
|
end
|
58
83
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
84
|
+
def build_joins(filter_hash)
|
85
|
+
reflections.map do |ref, _|
|
86
|
+
if filter_hash
|
87
|
+
.keys
|
88
|
+
.concat((filter_hash[:or] || []).flat_map(&:keys).uniq)
|
89
|
+
.include?(ref)
|
90
|
+
ref
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_filter(filter_hash)
|
96
|
+
return model.all unless filter_hash.present?
|
97
|
+
|
98
|
+
joins = build_joins(filter_hash)
|
99
|
+
reflection_queries = build_reflection_queries(filter_hash)
|
100
|
+
or_branch = filter_hash.delete(:or)
|
101
|
+
# build_reflection_queries mutates the filter_hash
|
102
|
+
query =
|
103
|
+
(
|
104
|
+
if filter_hash.present?
|
105
|
+
model.where(where_clause(model.arel_table, filter_hash))
|
106
|
+
else
|
107
|
+
model.all
|
108
|
+
end
|
109
|
+
)
|
110
|
+
|
111
|
+
joins.compact.each { |ref| query = query.left_joins(ref) }
|
112
|
+
reflection_queries.each { |_ref, q| query = query.merge(q) }
|
62
113
|
|
63
114
|
return query unless or_branch
|
64
115
|
|
@@ -66,7 +117,8 @@ module Hai
|
|
66
117
|
end
|
67
118
|
|
68
119
|
def add_sub_query(query, or_branch)
|
69
|
-
query.or(
|
120
|
+
or_branch.each { |q| query = query.or(build_filter(q)) }
|
121
|
+
query
|
70
122
|
end
|
71
123
|
|
72
124
|
def where_clause(table, query_hash)
|
@@ -79,19 +131,5 @@ module Hai
|
|
79
131
|
end
|
80
132
|
end
|
81
133
|
end
|
82
|
-
|
83
|
-
def limit; end
|
84
|
-
|
85
|
-
def offset; end
|
86
|
-
|
87
|
-
def having; end
|
88
|
-
|
89
|
-
def group; end
|
90
|
-
|
91
|
-
def order; end
|
92
|
-
|
93
|
-
def select; end
|
94
|
-
|
95
|
-
def distinct; end
|
96
134
|
end
|
97
135
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module Hai
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Arel
|
6
|
+
class SortInputType < ::GraphQL::Schema::InputObject
|
7
|
+
argument :field, ::GraphQL::Types::String # TODO: maybe add "types" dynamically?
|
8
|
+
argument :order, ::GraphQL::Types::String # TODO: change to enum DESC||ASC
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative "./mutation_error_type"
|
2
|
+
|
3
|
+
module Hai
|
4
|
+
module GraphQL
|
5
|
+
module Types
|
6
|
+
# TODO: make this base class configurable?
|
7
|
+
class BaseCreate < ::GraphQL::Schema::RelayClassicMutation
|
8
|
+
null true
|
9
|
+
|
10
|
+
field :errors, [String], null: false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Hai
|
2
|
+
module GraphQL
|
3
|
+
module Types
|
4
|
+
class SortInputType < ::GraphQL::Schema::InputObject
|
5
|
+
argument :field, ::GraphQL::Types::String # TODO: maybe add "types" dynamically?
|
6
|
+
argument :order, ::GraphQL::Types::String # TODO: change to enum DESC||ASC
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/hai/update.rb
CHANGED
@@ -1,15 +1,35 @@
|
|
1
1
|
module Hai
|
2
|
-
class
|
3
|
-
|
4
|
-
attr_reader :table
|
2
|
+
class Update
|
3
|
+
attr_reader :model, :context
|
5
4
|
|
6
|
-
def initialize(model)
|
5
|
+
def initialize(model, context)
|
7
6
|
@model = model
|
7
|
+
@context = context
|
8
8
|
end
|
9
9
|
|
10
10
|
def execute(id:, attributes:)
|
11
11
|
record = model.find(id)
|
12
|
-
record
|
12
|
+
return unauthorized_error unless check_policy(record)
|
13
|
+
|
14
|
+
if record.update(**attributes)
|
15
|
+
{ errors: [], result: record }
|
16
|
+
else
|
17
|
+
{ errors: record.errors.map(&:full_message), result: nil }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def unauthorized_error
|
24
|
+
{ errors: ["UnauthorizedError"], result: nil }
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_policy(instance)
|
28
|
+
if model.const_defined?("Policies") && model::Policies.respond_to?(:update)
|
29
|
+
model::Policies.update(instance, context)
|
30
|
+
else
|
31
|
+
true
|
32
|
+
end
|
13
33
|
end
|
14
34
|
end
|
15
35
|
end
|
data/lib/hai/version.rb
CHANGED
data/lib/hai.rb
CHANGED
@@ -1,12 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record"
|
4
|
+
# TODO: remove dependency on Graphql
|
5
|
+
require "graphql"
|
6
|
+
require_relative "hai/graphql"
|
7
|
+
require_relative "hai/graphql/types"
|
8
|
+
|
3
9
|
require_relative "hai/version"
|
10
|
+
|
4
11
|
require_relative "hai/read"
|
5
12
|
require_relative "hai/create"
|
6
|
-
require_relative "hai/
|
7
|
-
require_relative "hai/
|
13
|
+
require_relative "hai/update"
|
14
|
+
require_relative "hai/delete"
|
15
|
+
|
16
|
+
require_relative "hai/policies"
|
17
|
+
require_relative "hai/action_mods"
|
18
|
+
require "hai/railtie" if defined?(Rails)
|
8
19
|
|
9
20
|
module Hai
|
10
|
-
class Error < StandardError
|
11
|
-
|
21
|
+
class Error < StandardError
|
22
|
+
end
|
23
|
+
class Rest
|
24
|
+
class Engine < ::Rails::Engine
|
25
|
+
isolate_namespace Hai
|
26
|
+
end
|
27
|
+
end
|
12
28
|
end
|
data/todo.md
CHANGED
@@ -12,11 +12,11 @@
|
|
12
12
|
- [/] create
|
13
13
|
- [/] attributes
|
14
14
|
- [] required or not
|
15
|
-
- [] update
|
16
|
-
- [] id + attributes
|
17
|
-
- [] destroy
|
18
|
-
- [] id
|
19
|
-
- [] authorization [current_user]
|
15
|
+
- [/] update
|
16
|
+
- [/] id + attributes
|
17
|
+
- [/] destroy
|
18
|
+
- [/] id
|
19
|
+
- [/] authorization [current_user]
|
20
20
|
|
21
21
|
------------------------------------------
|
22
22
|
|
@@ -29,13 +29,20 @@
|
|
29
29
|
- [/] read
|
30
30
|
- [/] list
|
31
31
|
- [/] relationships
|
32
|
+
- [] ActiveRecord::RecordNotFound ?
|
32
33
|
- [/] create
|
33
|
-
- [
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
- [/] callback for current_user/context
|
35
|
+
- [] Errors
|
36
|
+
- [/] update
|
37
|
+
- [/] callback for policy
|
38
|
+
- [] Errors
|
39
|
+
- [/] destroy
|
40
|
+
- [] Errors
|
41
|
+
- [] errors
|
42
|
+
- [] subscriptions
|
37
43
|
------
|
38
|
-
- [
|
44
|
+
- [x] gemify
|
45
|
+
- [] automate release
|
39
46
|
|
40
47
|
## Post gem
|
41
48
|
- [] Multiple or queries
|