shamu 0.0.21 → 0.0.24

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.ruby-version +1 -1
  4. data/Gemfile +3 -1
  5. data/Gemfile.lock +70 -69
  6. data/app/README +1 -0
  7. data/config/environment.rb +1 -0
  8. data/lib/shamu/attributes.rb +26 -0
  9. data/lib/shamu/attributes/equality.rb +10 -1
  10. data/lib/shamu/entities/entity.rb +11 -0
  11. data/lib/shamu/entities/list_scope.rb +3 -0
  12. data/lib/shamu/entities/list_scope/sorting.rb +6 -1
  13. data/lib/shamu/entities/list_scope/window_paging.rb +1 -1
  14. data/lib/shamu/entities/null_entity.rb +18 -12
  15. data/lib/shamu/entities/opaque_id.rb +9 -8
  16. data/lib/shamu/error.rb +24 -3
  17. data/lib/shamu/events/active_record/migration.rb +0 -2
  18. data/lib/shamu/events/active_record/service.rb +3 -1
  19. data/lib/shamu/events/events_service.rb +2 -2
  20. data/lib/shamu/events/in_memory/async_service.rb +4 -2
  21. data/lib/shamu/events/in_memory/service.rb +3 -1
  22. data/lib/shamu/features/features_service.rb +3 -1
  23. data/lib/shamu/features/support.rb +1 -1
  24. data/lib/shamu/json_api/rails/controller.rb +1 -1
  25. data/lib/shamu/locale/en.yml +14 -2
  26. data/lib/shamu/security/active_record_policy.rb +2 -1
  27. data/lib/shamu/security/error.rb +1 -1
  28. data/lib/shamu/security/policy.rb +14 -4
  29. data/lib/shamu/security/principal.rb +22 -2
  30. data/lib/shamu/security/roles.rb +4 -3
  31. data/lib/shamu/services.rb +3 -1
  32. data/lib/shamu/services/active_record.rb +2 -1
  33. data/lib/shamu/services/active_record_crud.rb +43 -19
  34. data/lib/shamu/services/lazy_association.rb +50 -18
  35. data/lib/shamu/services/observable_support.rb +73 -0
  36. data/lib/shamu/services/observed_request.rb +76 -0
  37. data/lib/shamu/services/request.rb +12 -0
  38. data/lib/shamu/services/request_support.rb +24 -2
  39. data/lib/shamu/services/result.rb +62 -1
  40. data/lib/shamu/services/service.rb +58 -33
  41. data/lib/shamu/sessions/cookie_store.rb +3 -1
  42. data/lib/shamu/sessions/session_store.rb +2 -2
  43. data/lib/shamu/version.rb +1 -1
  44. data/shamu.gemspec +1 -1
  45. data/spec/lib/shamu/entities/entity_lookup_service_spec.rb +1 -1
  46. data/spec/lib/shamu/entities/list_scope/sorting_spec.rb +1 -1
  47. data/spec/lib/shamu/entities/null_entity_spec.rb +6 -1
  48. data/spec/lib/shamu/entities/opaque_id_spec.rb +13 -6
  49. data/spec/lib/shamu/entities/static_repository_spec.rb +2 -2
  50. data/spec/lib/shamu/rails/entity_spec.rb +1 -1
  51. data/spec/lib/shamu/rails/features_spec.rb +1 -1
  52. data/spec/lib/shamu/security/principal_spec.rb +25 -0
  53. data/spec/lib/shamu/services/active_record_crud_spec.rb +39 -4
  54. data/spec/lib/shamu/services/lazy_association_spec.rb +19 -6
  55. data/spec/lib/shamu/services/observable_support_spec.rb +55 -0
  56. data/spec/lib/shamu/services/observed_request_spec.rb +46 -0
  57. data/spec/lib/shamu/services/request_support_spec.rb +54 -3
  58. data/spec/lib/shamu/services/service_spec.rb +16 -27
  59. metadata +15 -5
@@ -51,8 +51,10 @@ module Shamu
51
51
 
52
52
  # @param [String] private_key the private key used to verify cookie
53
53
  # values.
54
- initialize do |private_key = Shamu::Security.private_key, **|
54
+ def initialize( private_key = Shamu::Security.private_key )
55
55
  @private_key = private_key
56
+
57
+ super()
56
58
  end
57
59
 
58
60
  # (see SessionStore#fetch)
@@ -5,8 +5,8 @@ module Shamu
5
5
  # requests.
6
6
  module SessionStore
7
7
 
8
- def self.create( scorpion, *args, **dependencies, &block )
9
- return scorpion.fetch Shamu::Sessions::CookieStore, *args, **dependencies, &block if defined? Rack
8
+ def self.create( scorpion, *args, &block )
9
+ return scorpion.fetch Shamu::Sessions::CookieStore, *args, &block if defined? Rack
10
10
 
11
11
  fail "Configure a Shamu::Sessions::SessionStore in your scorpion setup."
12
12
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Shamu
4
4
  # The primary version number
5
- VERSION_NUMBER = "0.0.21"
5
+ VERSION_NUMBER = "0.0.24"
6
6
 
7
7
  # Version suffix such as 'beta' or 'alpha'
8
8
  VERSION_SUFFIX = ""
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_dependency "activemodel", ">= 5.0"
25
25
  spec.add_dependency "activesupport", ">= 5.0"
26
- spec.add_dependency "scorpion-ioc", "~> 0.6"
26
+ spec.add_dependency "scorpion-ioc", "~> 1.0.1"
27
27
  spec.add_dependency "multi_json", "~> 1.11"
28
28
  spec.add_dependency "rack", ">= 1"
29
29
  spec.add_dependency "listen", "~> 3"
@@ -5,7 +5,7 @@ require_relative "entity_lookup_models"
5
5
  describe Shamu::Entities::EntityLookupService do
6
6
 
7
7
  let( :service ) do
8
- scorpion.new( Shamu::Entities::EntityLookupService, { "Water" => EntityLookupServiceSpecs::CustomService }, {} )
8
+ scorpion.new( Shamu::Entities::EntityLookupService, "Water" => EntityLookupServiceSpecs::CustomService )
9
9
  end
10
10
 
11
11
  describe "#service_class_for" do
@@ -40,7 +40,7 @@ describe Shamu::Entities::ListScope::Paging do
40
40
  end
41
41
 
42
42
  it "includes sorting values in to_param" do
43
- expect( klass.new( sort_by: :name ).params ).to eq sort_by: { name: :asc }
43
+ expect( klass.new( sort_by: :name ).params ).to include sort_by: { name: :asc }
44
44
  end
45
45
 
46
46
  it "is not sorted with defaults" do
@@ -90,5 +90,10 @@ describe Shamu::Entities::NullEntity do
90
90
  it "inherits from the entity class" do
91
91
  expect( Shamu::Entities::NullEntity.for( klass ) ).to be < klass
92
92
  end
93
+
94
+ it "uses same model name" do
95
+ expect( Shamu::Entities::NullEntity.for( klass ).model_name ).to be klass.model_name
96
+ end
97
+
93
98
  end
94
- end
99
+ end
@@ -2,10 +2,21 @@ require "spec_helper"
2
2
 
3
3
  describe Shamu::Entities::OpaqueId do
4
4
 
5
+ describe ".opaque_iid" do
6
+ it "encodes the entity path" do
7
+ # Patients::Patient[1]
8
+ expect( Shamu::Entities::OpaqueId.opaque_id( "Patients::Patient[1]" ) ).to eq "UGF0aWVudHM6OlBhdGllbnRbMV0"
9
+ end
10
+ end
11
+
5
12
  describe ".to_model_id" do
6
13
  it "gets the encoded id for a valid opaque id" do
7
14
  # Patients::Patient[1]
8
- expect( Shamu::Entities::OpaqueId.to_model_id( "::UGF0aWVudHM6OlBhdGllbnRbMV0=" ) ).to eq 1
15
+ expect( Shamu::Entities::OpaqueId.to_model_id( "UGF0aWVudHM6OlBhdGllbnRbMV0" ) ).to eq 1
16
+ end
17
+
18
+ it "gets the encoded id for a valid opaque id with mod 4 = 0" do
19
+ expect( Shamu::Entities::OpaqueId.to_model_id( "RW50aXR5TG9va3VwU2VydmljZVNwZWNzOjpFeGFtcGxlWzVd" ) ).to eq 5
9
20
  end
10
21
 
11
22
  it "is int for raw ids" do
@@ -15,16 +26,12 @@ describe Shamu::Entities::OpaqueId do
15
26
 
16
27
  describe ".opaque_id?" do
17
28
  it "recognizes encoded ids" do
18
- expect( Shamu::Entities::OpaqueId.opaque_id?( "::UGF0aWVudHM6OlBhdGllbnRbMV0=" ) ).to be_truthy
29
+ expect( Shamu::Entities::OpaqueId.opaque_id?( "UGF0aWVudHM6OlBhdGllbnRbMV0" ) ).to be_truthy
19
30
  end
20
31
 
21
32
  it "does not recognize raw numbers" do
22
33
  expect( Shamu::Entities::OpaqueId.opaque_id?( "123" ) ).to be_falsy
23
34
  end
24
-
25
- it "does not recognize base64 encoded" do
26
- expect( Shamu::Entities::OpaqueId.opaque_id?( "UGF0aWVudHM6OlBhdGllbnRbMV0=" ) ).to be_falsy
27
- end
28
35
  end
29
36
 
30
37
  end
@@ -10,8 +10,8 @@ end
10
10
  describe Shamu::Entities::StaticRepository do
11
11
  let( :entities ) do
12
12
  [
13
- scorpion.fetch( StaticRepositorySpec::Entity, { id: 10, name: "First" }, {} ),
14
- scorpion.fetch( StaticRepositorySpec::Entity, { id: 20, name: "Last" }, {} ),
13
+ scorpion.fetch( StaticRepositorySpec::Entity, id: 10, name: "First" ),
14
+ scorpion.fetch( StaticRepositorySpec::Entity, id: 20, name: "Last" ),
15
15
  ]
16
16
  end
17
17
  let( :repository ) do
@@ -6,7 +6,7 @@ module LoadEntitySpec
6
6
  include Shamu::Services::RequestSupport
7
7
 
8
8
  def find( id )
9
- scorpion.fetch ExampleEntity, { id: id }, {}
9
+ scorpion.fetch ExampleEntity, id: id
10
10
  end
11
11
 
12
12
  def list( params = nil )
@@ -11,7 +11,7 @@ describe Shamu::Rails::Features, type: :controller do
11
11
  end
12
12
 
13
13
  hunt( :features_service, Shamu::Features::FeaturesService ) do
14
- scorpion.new Shamu::Features::FeaturesService, File.expand_path( "../features.yml", __FILE__ ), {}
14
+ scorpion.new Shamu::Features::FeaturesService, File.expand_path( "../features.yml", __FILE__ )
15
15
  end
16
16
 
17
17
  hunt( :session_store, Shamu::Sessions::CookieStore )
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Security::Principal do
4
+
5
+ describe "#scoped?" do
6
+ it "is true for any scope when not limited" do
7
+ principal = Shamu::Security::Principal.new scopes: nil
8
+
9
+ expect( principal ).to be_scoped :all
10
+ expect( principal ).to be_scoped :bananas
11
+ end
12
+
13
+ it "is true for given scope" do
14
+ principal = Shamu::Security::Principal.new scopes: [ :admin ]
15
+
16
+ expect( principal ).to be_scoped :admin
17
+ end
18
+
19
+ it "is false for ungiven scope" do
20
+ principal = Shamu::Security::Principal.new scopes: [ :admin ]
21
+
22
+ expect( principal ).not_to be_scoped :bananas
23
+ end
24
+ end
25
+ end
@@ -31,6 +31,7 @@ module ActiveRecordCrudSpec
31
31
 
32
32
  class Service < Shamu::Services::Service
33
33
  include Shamu::Services::ActiveRecordCrud
34
+ include Shamu::Services::ObservableSupport
34
35
 
35
36
  resource FavoriteEntity, ActiveRecordSpec::Favorite
36
37
  define_crud
@@ -170,6 +171,14 @@ describe Shamu::Services::ActiveRecordCrud do
170
171
 
171
172
  service.create
172
173
  end
174
+
175
+ it "is observable" do
176
+ expect do |b|
177
+ service.observe( &b )
178
+
179
+ service.create request
180
+ end.to yield_control
181
+ end
173
182
  end
174
183
 
175
184
  describe ".change" do
@@ -232,6 +241,14 @@ describe Shamu::Services::ActiveRecordCrud do
232
241
 
233
242
  service.update entity
234
243
  end
244
+
245
+ it "is observable" do
246
+ expect do |b|
247
+ service.observe( &b )
248
+
249
+ service.update entity
250
+ end.to yield_control
251
+ end
235
252
  end
236
253
 
237
254
  describe ".finders" do
@@ -284,8 +301,8 @@ describe Shamu::Services::ActiveRecordCrud do
284
301
  it "yields to block if block given" do
285
302
  find_klass = Class.new( klass )
286
303
  expect do |b|
287
- find_klass.define_find do |_|
288
- b.to_proc.call
304
+ find_klass.define_find do |id|
305
+ b.to_proc.call id
289
306
  ActiveRecordSpec::Favorite.all.first
290
307
  end
291
308
  scorpion.new( find_klass ).find( entity.id )
@@ -311,6 +328,11 @@ describe Shamu::Services::ActiveRecordCrud do
311
328
  end
312
329
 
313
330
  it "uses entity_lookup_list" do
331
+ # #create caches the result so bypass the cache
332
+ expect( service ).to receive( :cached_lookup ) do |ids, &block|
333
+ block.call( ids )
334
+ end
335
+
314
336
  expect( service ).to receive( :entity_lookup_list ).and_call_original
315
337
  service.lookup( entity.id )
316
338
  end
@@ -347,6 +369,11 @@ describe Shamu::Services::ActiveRecordCrud do
347
369
  end
348
370
 
349
371
  it "calls #authorize_relation" do
372
+ # #create caches the result so bypass the cache
373
+ expect( service ).to receive( :cached_lookup ) do |ids, &block|
374
+ block.call( ids )
375
+ end
376
+
350
377
  expect( service ).to receive( :authorize_relation ).with(
351
378
  :read,
352
379
  kind_of( ActiveRecord::Relation )
@@ -422,7 +449,7 @@ describe Shamu::Services::ActiveRecordCrud do
422
449
  expect( records ).to receive( :find ).and_return record
423
450
 
424
451
  yield_klass = Class.new( klass ) do
425
- define_destroy records do
452
+ define_destroy :destroy, records do
426
453
  Shamu::Services::Result.new
427
454
  end
428
455
  end
@@ -440,6 +467,14 @@ describe Shamu::Services::ActiveRecordCrud do
440
467
 
441
468
  service.destroy entity.id
442
469
  end
470
+
471
+ it "is observable" do
472
+ expect do |b|
473
+ service.observe( &b )
474
+
475
+ service.destroy entity
476
+ end.to yield_control
477
+ end
443
478
  end
444
479
 
445
480
  describe ".build_entities" do
@@ -454,7 +489,7 @@ describe Shamu::Services::ActiveRecordCrud do
454
489
  Class.new( super() ) do
455
490
  define_build_entities do |records|
456
491
  records.map do |record|
457
- scorpion.fetch ec, { record: record }, {}
492
+ scorpion.fetch ec, record: record
458
493
  end
459
494
  end
460
495
  public :build_entities
@@ -1,31 +1,44 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Shamu::Services::LazyAssociation do
4
+ let( :lazy_class ) { Shamu::Services::LazyAssociation.class_for( Shamu::Entities::Entity ) }
5
+
4
6
  it "calls block to look up association" do
5
7
  assoc = double
6
8
  expect( assoc ).to receive( :label )
7
- lazy = Shamu::Services::LazyAssociation.new( 1 ) { assoc }
9
+ lazy = lazy_class.new( 1 ) { assoc }
8
10
  lazy.label
9
11
  end
10
12
 
11
13
  it "delegates ==" do
12
14
  assoc = double
13
- lazy = Shamu::Services::LazyAssociation.new( 1 ) { assoc }
15
+ lazy = lazy_class.new( 1 ) { assoc }
14
16
  expect( lazy ).to eq assoc
15
17
  end
16
18
 
17
19
  it "does not delegate id" do
18
20
  assoc = double
19
21
  expect( assoc ).not_to receive( :id )
20
- lazy = Shamu::Services::LazyAssociation.new( 1 ) { assoc }
22
+ lazy = lazy_class.new( 1 ) { assoc }
21
23
  lazy.id
22
24
  end
23
25
 
24
26
  it "has the same class as original object" do
25
- assoc = double
27
+ assoc = double Shamu::Entities::Entity
26
28
  expect( assoc ).to receive( :to_entity ).and_return assoc
27
- lazy = Shamu::Services::LazyAssociation.new( 1 ) { assoc }
29
+ lazy = lazy_class.new( 1 ) { assoc }
28
30
 
29
31
  expect( lazy.to_entity ).to be_kind_of assoc.class
30
32
  end
31
- end
33
+
34
+ it "instance of" do
35
+ lazy = lazy_class.new( 1 ) { Shamu::Entities::Entity.new }
36
+
37
+ expect( lazy ).to be_a Shamu::Entities::Entity
38
+ end
39
+
40
+ it "satisfies case compare" do
41
+ lazy = lazy_class.new( 1 ) { Shamu::Entities::Entity.new }
42
+ expect( Shamu::Entities::Entity === lazy ).to be_truthy
43
+ end
44
+ end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+ require "shamu/active_record"
3
+
4
+ class ObservableService < Shamu::Services::Service
5
+ include Shamu::Services::ObservableSupport
6
+
7
+ public :with_observers, :notify_observers
8
+ end
9
+
10
+ describe Shamu::Services::ObservableSupport do
11
+ let( :service ) { scorpion.new ObservableService }
12
+ let( :request ) { Shamu::Services::Request.new }
13
+ let( :action ) { Shamu::Services::ObservedRequest.new request: request }
14
+
15
+
16
+ describe "#notify_observers" do
17
+ it "notifies all registered observers" do
18
+ expect do |b|
19
+ service.observe( &b )
20
+ service.notify_observers action
21
+ end.to yield_control
22
+ end
23
+ end
24
+
25
+ describe "#with_observers" do
26
+ it "notifies observers" do
27
+ expect do |b|
28
+ service.observe( &b )
29
+ service.with_observers request do |req|
30
+ Shamu::Services::Result.new request: req
31
+ end
32
+ end.to yield_control
33
+ end
34
+
35
+ it "does not yield if action was canceled" do
36
+ expect do |b|
37
+ service.observe do |a|
38
+ a.request_cancel Shamu::Services::Result.new
39
+ end
40
+ service.with_observers( request, &b )
41
+ end.not_to yield_control
42
+ end
43
+
44
+ it "reports an error indicating the request was canceled" do
45
+ service.observe do |a|
46
+ a.request_cancel Shamu::Services::Result.new
47
+ end
48
+
49
+ result = service.with_observers request
50
+
51
+ expect( result ).not_to be_valid
52
+ expect( result.errors.full_messages ).to include /canceled/
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Shamu::Services::ObservedRequest do
5
+ let( :request ) { double Shamu::Services::Request }
6
+ let( :success ) { double Shamu::Services::Result, valid?: true }
7
+ let( :failure ) { double Shamu::Services::Result, valid?: false }
8
+
9
+ let( :action ) do
10
+ Shamu::Services::ObservedRequest.new request: request
11
+ end
12
+
13
+ before( :each ) do
14
+ [ success, failure ].each do |result|
15
+ allow( result ).to receive( :join )
16
+ end
17
+ end
18
+
19
+ describe "#complete" do
20
+ context "#on_canceled" do
21
+ it "is invoked when canceled" do
22
+ expect do |b|
23
+ action.on_canceled( &b )
24
+ action.complete success, true
25
+ end.to yield_control
26
+ end
27
+
28
+ it "is not invoked if not canceled" do
29
+ expect do |b|
30
+ action.on_canceled( &b )
31
+ action.complete success, false
32
+ end.not_to yield_control
33
+ end
34
+
35
+ it "joins results" do
36
+ result = double Shamu::Services::Result
37
+ expect( failure ).to receive( :join ).with( result )
38
+ action.on_canceled do |_|
39
+ result
40
+ end
41
+
42
+ action.complete failure, true
43
+ end
44
+ end
45
+ end
46
+ end
@@ -4,20 +4,24 @@ require "shamu/services"
4
4
  module RequestSupportSpec
5
5
  class Service < Shamu::Services::Service
6
6
  include Shamu::Services::RequestSupport
7
+ public :extract_params
7
8
 
8
9
  def process( params )
9
10
  with_request( params, Request::Change ) do |request|
10
11
  request_hook
11
- next error( :base, "nope" ) if request.level < 0
12
+ next request.error( :base, "nope" ) if request.level < 0
12
13
 
13
14
  record = OpenStruct.new( request.to_attributes )
14
- scorpion.fetch RequestSupportSpec::Entity, { record: record }, {}
15
+ scorpion.fetch RequestSupportSpec::Entity, record: record
15
16
  end
16
17
  end
17
18
 
18
19
  def partial_process( params )
19
- with_partial_request( params, Request::Change ) do |_|
20
+ with_partial_request( params, Request::Change ) do |request|
20
21
  request_hook
22
+
23
+ record = OpenStruct.new( request.to_attributes )
24
+ scorpion.fetch RequestSupportSpec::Entity, record: record
21
25
  end
22
26
  end
23
27
 
@@ -156,6 +160,12 @@ describe Shamu::Services::RequestSupport do
156
160
 
157
161
  expect( result.request.errors ).not_to be_empty
158
162
  end
163
+
164
+ it "updates the cache after change" do
165
+ expect( service ).to receive( :recache_entity )
166
+
167
+ service.partial_process( request_params )
168
+ end
159
169
  end
160
170
 
161
171
  describe "#request_for" do
@@ -164,4 +174,45 @@ describe Shamu::Services::RequestSupport do
164
174
  expect( request ).to be_a Shamu::Services::Request
165
175
  end
166
176
  end
177
+
178
+ describe "#extract_params" do
179
+ it "supports string hash as only param" do
180
+ id, params = service.extract_params( { "id" => 5 }, nil )
181
+
182
+ expect( id ).to eq 5
183
+ expect( params ).to eq id: 5
184
+ end
185
+
186
+ it "supports string hash as second param" do
187
+ id, params = service.extract_params( 9, "id" => 5 )
188
+
189
+ expect( id ).to eq 9
190
+ expect( params ).to eq id: 5
191
+ end
192
+
193
+ it "supports symbol hash as only param" do
194
+ id, params = service.extract_params( { id: 5 }, nil )
195
+
196
+ expect( id ).to eq 5
197
+ expect( params ).to eq id: 5
198
+ end
199
+
200
+ it "supports symbol hash as second param" do
201
+ id, params = service.extract_params( 9, id: 5 )
202
+
203
+ expect( id ).to eq 9
204
+ expect( params ).to eq id: 5
205
+ end
206
+
207
+ it "supports entity as first param" do
208
+ record = OpenStruct.new( id: 5 )
209
+ entity = scorpion.fetch RequestSupportSpec::Entity, record: record
210
+
211
+ id, params = service.extract_params( entity, nil )
212
+
213
+ expect( id ).to eq entity
214
+ expect( params ).to be_nil
215
+ end
216
+ end
217
+
167
218
  end