shamu 0.0.9 → 0.0.11
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/.ruby-version +1 -1
- data/Gemfile +5 -3
- data/bin/rake +17 -0
- data/bin/rspec +17 -0
- data/lib/shamu/attributes.rb +3 -1
- data/lib/shamu/events/active_record/migration.rb +6 -6
- data/lib/shamu/json_api/builder_methods/identifier.rb +18 -4
- data/lib/shamu/json_api/context.rb +3 -1
- data/lib/shamu/json_api/error.rb +7 -1
- data/lib/shamu/json_api/presenter.rb +23 -1
- data/lib/shamu/json_api/rails/controller.rb +195 -62
- data/lib/shamu/locale/en.yml +3 -1
- data/lib/shamu/rails/controller.rb +5 -2
- data/lib/shamu/rails/entity.rb +29 -15
- data/lib/shamu/rails/railtie.rb +12 -7
- data/lib/shamu/services/active_record.rb +2 -2
- data/lib/shamu/services/active_record_crud.rb +36 -38
- data/lib/shamu/services/error.rb +11 -1
- data/lib/shamu/services/lazy_transform.rb +9 -4
- data/lib/shamu/services/request_support.rb +5 -2
- data/lib/shamu/services/result.rb +40 -7
- data/lib/shamu/services/service.rb +17 -8
- data/lib/shamu/services/service_call_failed_error.rb +4 -0
- data/lib/shamu/version.rb +2 -2
- data/shamu.gemspec +4 -4
- data/spec/lib/shamu/json_api/builder_methods/identifier_spec.rb +45 -0
- data/spec/lib/shamu/json_api/rails/controller_spec.rb +141 -7
- data/spec/lib/shamu/json_api/rails/responder_spec.rb +9 -9
- data/spec/lib/shamu/rails/controller_spec.rb +4 -4
- data/spec/lib/shamu/rails/entity_spec.rb +34 -16
- data/spec/lib/shamu/rails/features_spec.rb +6 -6
- data/spec/lib/shamu/services/active_record_crud_spec.rb +12 -7
- data/spec/lib/shamu/services/lazy_transform_spec.rb +23 -14
- data/spec/lib/shamu/services/request_support_spec.rb +15 -1
- data/spec/lib/shamu/services/result_spec.rb +37 -1
- data/spec/lib/shamu/services/service_spec.rb +25 -14
- data/spec/spec_helper.rb +1 -1
- metadata +23 -17
data/lib/shamu/rails/railtie.rb
CHANGED
@@ -18,9 +18,14 @@ module Shamu
|
|
18
18
|
config.shamu.json_api.default_url_options = {}
|
19
19
|
|
20
20
|
if defined? ::ActionController
|
21
|
-
::ActionController::Base
|
22
|
-
::ActionController::
|
23
|
-
|
21
|
+
controller_classes = [ ::ActionController::Base ]
|
22
|
+
controller_classes << ::ActionController::API if defined? ::ActionController::API
|
23
|
+
|
24
|
+
controller_classes.each do |klass|
|
25
|
+
klass.send :include, Shamu::Rails::Controller
|
26
|
+
klass.send :include, Shamu::Rails::Entity
|
27
|
+
klass.send :include, Shamu::Rails::Features
|
28
|
+
end
|
24
29
|
|
25
30
|
Mime::Type.register Shamu::JsonApi::MIME_TYPE, :json_api
|
26
31
|
|
@@ -32,11 +37,11 @@ module Shamu
|
|
32
37
|
end
|
33
38
|
|
34
39
|
initializer "shamu.insert_middleware" do |app|
|
35
|
-
app.config.middleware.use
|
36
|
-
app.config.middleware.use
|
37
|
-
app.config.middleware.use
|
40
|
+
app.config.middleware.use Scorpion::Rack::Middleware
|
41
|
+
app.config.middleware.use Shamu::Rack::CookiesMiddleware
|
42
|
+
app.config.middleware.use Shamu::Rack::QueryParamsMiddleware
|
38
43
|
end
|
39
44
|
|
40
45
|
end
|
41
46
|
end
|
42
|
-
end
|
47
|
+
end
|
@@ -49,10 +49,10 @@ module Shamu
|
|
49
49
|
if relation.respond_to?( :by_list_scope )
|
50
50
|
relation.by_list_scope( list_scope )
|
51
51
|
else
|
52
|
-
fail "Can't scope a #{ relation.klass }. Add `scope :by_list_scope, ->(list_scope) { ... }` or
|
52
|
+
fail "Can't scope a #{ relation.klass }. Add `scope :by_list_scope, ->(list_scope) { ... }` or extend Shamu::Entities::ActiveRecord." # rubocop:disable Metrics/LineLength
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
end
|
57
57
|
end
|
58
|
-
end
|
58
|
+
end
|
@@ -29,12 +29,14 @@ module Shamu
|
|
29
29
|
# destroy
|
30
30
|
#
|
31
31
|
# # Build the entity class from the given record.
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
32
|
+
# build_entities do |records|
|
33
|
+
# records.map do |record|
|
34
|
+
# parent = lookup_association( record.parent_id, self ) do
|
35
|
+
# records.pluck( :parent_id )
|
36
|
+
# end
|
36
37
|
#
|
37
|
-
#
|
38
|
+
# scorpion.fetch UserEntity, { parent: parent }, {}
|
39
|
+
# end
|
38
40
|
# end
|
39
41
|
# end
|
40
42
|
module ActiveRecordCrud
|
@@ -97,17 +99,16 @@ module Shamu
|
|
97
99
|
# Creates instance and class level methods `entity_class` and
|
98
100
|
# `model_class`.
|
99
101
|
#
|
100
|
-
# See {.
|
102
|
+
# See {.build_entities} for build_entities block details.
|
101
103
|
#
|
102
104
|
# @param [Class] entity_class the {Entities::Entity} class that will be
|
103
105
|
# returned by finders and mutator methods.
|
104
106
|
# @param [Class] model_class the {ActiveRecord::Base} model
|
105
107
|
# @param [Array<Symbol>] methods the {DSL_METHODS DSL methods} to
|
106
108
|
# include (eg :create, :update, :find, etc.)
|
107
|
-
# @yield (
|
108
|
-
# @yieldparam [ActiveRecord::
|
109
|
-
#
|
110
|
-
# @yieldparam [ActiveRecord::Relation] records that are all being built
|
109
|
+
# @yield ( records )
|
110
|
+
# @yieldparam [ActiveRecord::Relation] records to be mapped to an
|
111
|
+
# entity.
|
111
112
|
# @yieldreturn [Entities::Entity] the entity projection for the given
|
112
113
|
# record.
|
113
114
|
# @return [void]
|
@@ -122,7 +123,7 @@ module Shamu
|
|
122
123
|
send method
|
123
124
|
end
|
124
125
|
|
125
|
-
|
126
|
+
build_entities( &block )
|
126
127
|
end
|
127
128
|
|
128
129
|
# @return [Class] the {Entities::Entity} class that the service will
|
@@ -172,7 +173,11 @@ module Shamu
|
|
172
173
|
# @return [void]
|
173
174
|
def change( method = :update, &block )
|
174
175
|
define_method method do |id, params = nil|
|
175
|
-
|
176
|
+
klass = request_class( method )
|
177
|
+
|
178
|
+
id, params = id.id, id if id.is_a?( klass ) && !params
|
179
|
+
|
180
|
+
with_request params, klass do |request|
|
176
181
|
record = model_class.find( id.to_model_id )
|
177
182
|
authorize! method, build_entity( record ), request
|
178
183
|
|
@@ -247,7 +252,7 @@ module Shamu
|
|
247
252
|
define_method :find do |id|
|
248
253
|
wrap_not_found do
|
249
254
|
record = yield( id )
|
250
|
-
authorize! :read,
|
255
|
+
authorize! :read, build_entities( record )
|
251
256
|
end
|
252
257
|
end
|
253
258
|
else
|
@@ -258,8 +263,8 @@ module Shamu
|
|
258
263
|
end
|
259
264
|
|
260
265
|
# Define a `lookup( *ids )` method that takes a list of entity ids to
|
261
|
-
# find. Calls {#
|
262
|
-
# {Entities::NullEntity} for ids that were not found.
|
266
|
+
# find. Calls {#build_entities} to map all found records to entities,
|
267
|
+
# or constructs a {Entities::NullEntity} for ids that were not found.
|
263
268
|
#
|
264
269
|
# @param [ActiveRecord::Relation] default_scope to use when finding
|
265
270
|
# records.
|
@@ -317,11 +322,8 @@ module Shamu
|
|
317
322
|
end
|
318
323
|
end
|
319
324
|
|
320
|
-
# Define a private `
|
321
|
-
# constructs an {Entities::Entity}
|
322
|
-
# `records` argument is used when constructing a list of entities so
|
323
|
-
# that associations can all be fetched once and cached while building
|
324
|
-
# the list of entities.
|
325
|
+
# Define a private `build_entities( records )` method that
|
326
|
+
# constructs an {Entities::Entity} for each of the given `records`.
|
325
327
|
#
|
326
328
|
# If no block is given, creates a simple builder that simply constructs
|
327
329
|
# an instance of the {.entity_class} passing `record: record` to the
|
@@ -329,28 +331,24 @@ module Shamu
|
|
329
331
|
#
|
330
332
|
# See {Service#lookup_association} for details on association caching.
|
331
333
|
#
|
332
|
-
# @yield (
|
333
|
-
# @yieldparam [ActiveRecord::
|
334
|
-
#
|
335
|
-
# @
|
336
|
-
# @yieldreturn [Entities::Entity] the entity projection for the given
|
337
|
-
# record.
|
334
|
+
# @yield ( records )
|
335
|
+
# @yieldparam [ActiveRecord::Relation] records to be mapped to
|
336
|
+
# entities.
|
337
|
+
# @yieldreturn [Array<Entities::Entity>] the projected entities.
|
338
338
|
# @return [void]
|
339
|
-
def
|
339
|
+
def build_entities( &block )
|
340
340
|
if block_given?
|
341
|
-
define_method :
|
341
|
+
define_method :build_entities, &block
|
342
342
|
else
|
343
|
-
define_method :
|
344
|
-
|
343
|
+
define_method :build_entities do |records|
|
344
|
+
records.map do |record|
|
345
|
+
entity = scorpion.fetch( entity_class, { record: record }, {} )
|
346
|
+
authorize! :read, entity
|
347
|
+
end
|
345
348
|
end
|
346
349
|
end
|
347
350
|
|
348
|
-
|
349
|
-
authorize! :read, build_entity_instance( record, records )
|
350
|
-
end
|
351
|
-
|
352
|
-
private :build_entity
|
353
|
-
private :build_entity_instance
|
351
|
+
private :build_entities
|
354
352
|
end
|
355
353
|
|
356
354
|
private
|
@@ -361,7 +359,7 @@ module Shamu
|
|
361
359
|
|
362
360
|
def inferred_resource_name
|
363
361
|
inferred = name || "Resource"
|
364
|
-
inferred.split( "::" ).last.sub /Service/, ""
|
362
|
+
inferred.split( "::" ).last.sub( /Service/, "" )
|
365
363
|
end
|
366
364
|
|
367
365
|
def inferred_namespace
|
@@ -375,4 +373,4 @@ module Shamu
|
|
375
373
|
|
376
374
|
end
|
377
375
|
end
|
378
|
-
end
|
376
|
+
end
|
data/lib/shamu/services/error.rb
CHANGED
@@ -20,5 +20,15 @@ module Shamu
|
|
20
20
|
super
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
class ServiceRequestFailedError < Error
|
25
|
+
attr_reader :result
|
26
|
+
|
27
|
+
def initialize( result )
|
28
|
+
@result = result
|
29
|
+
|
30
|
+
super translate( :service_request_failed, errors: result.errors.full_messages.join( ', ' ) )
|
31
|
+
end
|
32
|
+
end
|
23
33
|
end
|
24
|
-
end
|
34
|
+
end
|
@@ -7,8 +7,8 @@ module Shamu
|
|
7
7
|
include Enumerable
|
8
8
|
|
9
9
|
# @param [Enumerable] source enumerable to transform.
|
10
|
-
# @yieldparam [Object]
|
11
|
-
# @yieldreturn the transformed
|
10
|
+
# @yieldparam [Array<Object>] objects the original values.
|
11
|
+
# @yieldreturn the transformed values.
|
12
12
|
# @yield (object)
|
13
13
|
def initialize( source, &transformer )
|
14
14
|
@transformer = transformer
|
@@ -48,7 +48,7 @@ module Shamu
|
|
48
48
|
return @first if defined? @first
|
49
49
|
@first = begin
|
50
50
|
value = source.first
|
51
|
-
transformer.call( value ) unless value.nil?
|
51
|
+
raise_if_not_transformed( transformer.call( [ value ] ) ).first unless value.nil?
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -86,12 +86,17 @@ module Shamu
|
|
86
86
|
attr_reader :transformer
|
87
87
|
|
88
88
|
def transformed
|
89
|
-
@transformed ||=
|
89
|
+
@transformed ||= raise_if_not_transformed( transformer.call( source ) )
|
90
90
|
end
|
91
91
|
|
92
92
|
def transformed?
|
93
93
|
!!@transformed
|
94
94
|
end
|
95
|
+
|
96
|
+
def raise_if_not_transformed( transformed )
|
97
|
+
raise "Block to LazyTransform did not return an enumerable value" unless transformed.is_a? Enumerable
|
98
|
+
transformed
|
99
|
+
end
|
95
100
|
end
|
96
101
|
end
|
97
102
|
end
|
@@ -117,7 +117,10 @@ module Shamu
|
|
117
117
|
private
|
118
118
|
|
119
119
|
def request_class_namespace
|
120
|
-
@request_class_namespace ||= ( name || "" ).sub( /(Service)?$/, "
|
120
|
+
@request_class_namespace ||= ( name || "" ).sub( /(Service)?$/, "" )
|
121
|
+
.singularize
|
122
|
+
.concat( 'Request' )
|
123
|
+
.constantize
|
121
124
|
rescue NameError
|
122
125
|
self
|
123
126
|
end
|
@@ -145,4 +148,4 @@ module Shamu
|
|
145
148
|
end
|
146
149
|
end
|
147
150
|
end
|
148
|
-
end
|
151
|
+
end
|
@@ -7,6 +7,7 @@ module Shamu
|
|
7
7
|
class Result
|
8
8
|
extend ActiveModel::Translation
|
9
9
|
|
10
|
+
|
10
11
|
# ============================================================================
|
11
12
|
# @!group Attributes
|
12
13
|
#
|
@@ -17,17 +18,41 @@ module Shamu
|
|
17
18
|
# @return [Entities::Entity] the entity created or changed by the request.
|
18
19
|
attr_reader :entity
|
19
20
|
|
21
|
+
# @return [Entities::Entity] the entity created or changed by the request.
|
22
|
+
# @raise [ServiceRequestFailedError] if the result was not valid.
|
23
|
+
def entity!
|
24
|
+
valid!
|
25
|
+
entity
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Array<Object>] the values returned by the service call.
|
29
|
+
attr_reader :values
|
30
|
+
|
31
|
+
# @return [Object] the primary return value of the service call.
|
32
|
+
attr_reader :value
|
33
|
+
|
34
|
+
# @return [Object] the primary return value of the service call.
|
35
|
+
# @raise [ServiceRequestFailedError] if the result was not valid.
|
36
|
+
def value!
|
37
|
+
valid!
|
38
|
+
value
|
39
|
+
end
|
40
|
+
|
20
41
|
#
|
21
42
|
# @!endgroup Attributes
|
22
43
|
|
23
|
-
# @param [Array
|
24
|
-
#
|
44
|
+
# @param [Array<Object,#errors>] values an array of objects that
|
45
|
+
# represent the result of the service call. If they respond to `#errors`
|
46
|
+
# those errors will be included in {#errors} on the result object itself.
|
25
47
|
# @param [Request] request submitted to the service. If :not_set, uses
|
26
|
-
# the first {Request} object found in the `
|
48
|
+
# the first {Request} object found in the `values`.
|
27
49
|
# @param [Entities::Entity] entity submitted to the service. If :not_set,
|
28
|
-
# uses the first {
|
29
|
-
def initialize( *
|
30
|
-
|
50
|
+
# uses the first {Entity} object found in the `values`.
|
51
|
+
def initialize( *values, request: :not_set, entity: :not_set )
|
52
|
+
@values = values
|
53
|
+
@value = values.first
|
54
|
+
|
55
|
+
values.each do |source|
|
31
56
|
request = source if request == :not_set && source.is_a?( Services::Request )
|
32
57
|
entity = source if entity == :not_set && source.is_a?( Entities::Entity )
|
33
58
|
|
@@ -61,8 +86,16 @@ module Shamu
|
|
61
86
|
( request && request.model_name ) || ( entity && entity.model_name ) || ActiveModel::Name.new( self, nil, "Request" ) # rubocop:disable Metrics/LineLength
|
62
87
|
end
|
63
88
|
|
89
|
+
# @return [self]
|
90
|
+
# @raise [ServiceRequestFailedError] if the result was not valid.
|
91
|
+
def valid!
|
92
|
+
raise ServiceRequestFailedError.new( self ) unless valid?
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
64
96
|
private
|
65
97
|
|
98
|
+
|
66
99
|
def append_error_source( source )
|
67
100
|
return unless source.respond_to?( :errors )
|
68
101
|
|
@@ -72,4 +105,4 @@ module Shamu
|
|
72
105
|
end
|
73
106
|
end
|
74
107
|
end
|
75
|
-
end
|
108
|
+
end
|
@@ -78,6 +78,16 @@ module Shamu
|
|
78
78
|
|
79
79
|
private
|
80
80
|
|
81
|
+
# Maps a single record to an entity. Requires a `build_entities` method
|
82
|
+
# that maps an enumerable set of records to entities.
|
83
|
+
#
|
84
|
+
# @param [Object] record to map.
|
85
|
+
# @return [Entity] the mapped entity.
|
86
|
+
def build_entity( record )
|
87
|
+
mapped = build_entities( [ record ] )
|
88
|
+
mapped && mapped.first
|
89
|
+
end
|
90
|
+
|
81
91
|
# @!visibility public
|
82
92
|
# Takes a raw enumerable list of records and transforms them to a proper
|
83
93
|
# {Entities::List}.
|
@@ -85,10 +95,9 @@ module Shamu
|
|
85
95
|
# As simple as the method is, it also serves as a hook for mixins to add
|
86
96
|
# additional behavior when processing lists.
|
87
97
|
#
|
88
|
-
# If a block is not provided, looks for a method `
|
89
|
-
# records
|
90
|
-
#
|
91
|
-
# being transformed.
|
98
|
+
# If a block is not provided, looks for a method `build_entities(
|
99
|
+
# records )` that maps a set of records to their corresponding
|
100
|
+
# entities.
|
92
101
|
#
|
93
102
|
# @param [Enumerable] records the raw list of records.
|
94
103
|
# @yield (record)
|
@@ -99,8 +108,8 @@ module Shamu
|
|
99
108
|
def entity_list( records, &transformer )
|
100
109
|
return Entities::List.new [] unless records
|
101
110
|
unless transformer
|
102
|
-
fail "Either provide a block or add a private method `def
|
103
|
-
transformer ||=
|
111
|
+
fail "Either provide a block or add a private method `def build_entities( records )` to #{ self.class.name }." unless respond_to?( :build_entities, true ) # rubocop:disable Metrics/LineLength
|
112
|
+
transformer ||= method( :build_entities )
|
104
113
|
end
|
105
114
|
|
106
115
|
Entities::List.new LazyTransform.new( records, &transformer )
|
@@ -325,7 +334,7 @@ module Shamu
|
|
325
334
|
|
326
335
|
# @!visibility public
|
327
336
|
#
|
328
|
-
# @overload result( *
|
337
|
+
# @overload result( *values, request: nil, entity: nil )
|
329
338
|
# @param (see Result#initialize)
|
330
339
|
# @return [Result]
|
331
340
|
def result( *args )
|
@@ -354,4 +363,4 @@ module Shamu
|
|
354
363
|
end
|
355
364
|
end
|
356
365
|
end
|
357
|
-
end
|
366
|
+
end
|
data/lib/shamu/version.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Shamu
|
3
3
|
# The primary version number
|
4
|
-
VERSION_NUMBER = "0.0.
|
4
|
+
VERSION_NUMBER = "0.0.11".freeze
|
5
5
|
|
6
6
|
# Version suffix such as 'beta' or 'alpha'
|
7
7
|
VERSION_SUFFIX = "".freeze
|
8
8
|
|
9
9
|
# Published version number
|
10
10
|
VERSION = "#{ VERSION_NUMBER }#{ VERSION_SUFFIX }".freeze
|
11
|
-
end
|
11
|
+
end
|
data/shamu.gemspec
CHANGED
@@ -20,11 +20,11 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.required_ruby_version = ">= 2.2.0"
|
21
21
|
|
22
22
|
|
23
|
-
spec.add_dependency "activemodel", "
|
24
|
-
spec.add_dependency "activesupport", "
|
25
|
-
spec.add_dependency "scorpion-ioc", "~> 0.
|
23
|
+
spec.add_dependency "activemodel", ">= 5.0"
|
24
|
+
spec.add_dependency "activesupport", ">= 5.0"
|
25
|
+
spec.add_dependency "scorpion-ioc", "~> 0.6"
|
26
26
|
spec.add_dependency "multi_json", "~> 1.11.2"
|
27
|
-
spec.add_dependency "rack", "
|
27
|
+
spec.add_dependency "rack", ">= 1"
|
28
28
|
spec.add_dependency "listen", "~> 3"
|
29
29
|
spec.add_dependency "crc32", "~> 1"
|
30
30
|
spec.add_dependency "loofah", "~> 2"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module BuilderMethodsIdentifierSpec
|
4
|
+
class Builder
|
5
|
+
include Shamu::JsonApi::BuilderMethods::Identifier
|
6
|
+
|
7
|
+
attr_reader :output
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@output = {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Shamu::JsonApi::BuilderMethods::Identifier do
|
16
|
+
let( :builder ) { BuilderMethodsIdentifierSpec::Builder.new }
|
17
|
+
|
18
|
+
it "it uses #json_type if available" do
|
19
|
+
type = double( json_type: "magic" )
|
20
|
+
|
21
|
+
builder.identifier( type )
|
22
|
+
expect( builder.output[ :type ] ).to eq "magic"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "it uses #model_name if available" do
|
26
|
+
type = double( model_name: double( element: "record" ) )
|
27
|
+
|
28
|
+
builder.identifier( type )
|
29
|
+
expect( builder.output[ :type ] ).to eq "record"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "it uses class name as last resort" do
|
33
|
+
builder.identifier( BuilderMethodsIdentifierSpec::Builder )
|
34
|
+
|
35
|
+
expect( builder.output[ :type ] ).to eq "builder"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "gets ID from type ifid not provided" do
|
39
|
+
resource = double( id: 56, json_type: "double" )
|
40
|
+
|
41
|
+
builder.identifier resource
|
42
|
+
expect( builder.output[ :id ] ).to eq 56
|
43
|
+
expect( builder.output[ :type ] ).to eq "double"
|
44
|
+
end
|
45
|
+
end
|