shamu 0.0.21 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
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